From a33603d646b0caee7bd867ffcfc39fab7de9bb87 Mon Sep 17 00:00:00 2001 From: Tchiller <2923006+Tchiller@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:28:53 +0100 Subject: [PATCH 1/4] Fix: Import FN field from vCard 4.0 when N field is missing - Remove incorrect logic that treated FN as organization name - Use FN field directly as contact name when N field is absent - Fixes #465 --- .../fossify/contacts/helpers/VcfImporter.kt | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt index d966813d9..680145b2d 100644 --- a/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt @@ -55,9 +55,18 @@ class VcfImporter(val activity: SimpleActivity) { for (ezContact in ezContacts) { val structuredName = ezContact.structuredName val prefix = structuredName?.prefixes?.firstOrNull() ?: "" - val firstName = structuredName?.given ?: "" - val middleName = structuredName?.additionalNames?.firstOrNull() ?: "" - val surname = structuredName?.family ?: "" + var firstName = structuredName?.given ?: "" + var middleName = structuredName?.additionalNames?.firstOrNull() ?: "" + var surname = structuredName?.family ?: "" + + if (structuredName == null && ezContact.formattedName?.value?.isNotBlank() == true) { + val fn = ezContact.formattedName.value.trim() + val parts = fn.split("\\s+".toRegex(), limit = 2) + + firstName = parts.getOrNull(0) ?: "" + surname = parts.getOrNull(1) ?: "" + } + val suffix = structuredName?.suffixes?.firstOrNull() ?: "" val nickname = ezContact.nickname?.values?.firstOrNull() ?: "" var photoUri = "" @@ -268,16 +277,6 @@ class VcfImporter(val activity: SimpleActivity) { ringtone = ringtone ) - // if there is no N and ORG fields at the given contact, only FN, treat it as an organization - if ( - contact.getNameToDisplay().isEmpty() - && contact.organization.isEmpty() - && ezContact.formattedName?.value?.isNotEmpty() == true - ) { - contact.organization.company = ezContact.formattedName.value - contact.mimetype = CommonDataKinds.Organization.CONTENT_ITEM_TYPE - } - if (contact.isABusinessContact()) { contact.mimetype = CommonDataKinds.Organization.CONTENT_ITEM_TYPE } From 946921a235439963bd082c15d521e090429015b4 Mon Sep 17 00:00:00 2001 From: Tchiller <2923006+Tchiller@users.noreply.github.com> Date: Tue, 3 Feb 2026 22:38:38 +0100 Subject: [PATCH 2/4] Update CHANGELOG for vCard FN field fix --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9a92d320..d3676186a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- Fixed import of vCard 4.0 contacts with only FN field ([#465]) ## [1.6.0] - 2026-01-30 ### Added From cc5abfaa19b1708fc222a87124a892ff95fb5cdc Mon Sep 17 00:00:00 2001 From: Tchiller <2923006+Tchiller@users.noreply.github.com> Date: Tue, 19 May 2026 20:23:28 +0200 Subject: [PATCH 3/4] Keep FN verbatim as name fallback instead of splitting it --- .../fossify/contacts/helpers/VcfImporter.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt index 680145b2d..15b0ecc0e 100644 --- a/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt @@ -56,18 +56,24 @@ class VcfImporter(val activity: SimpleActivity) { val structuredName = ezContact.structuredName val prefix = structuredName?.prefixes?.firstOrNull() ?: "" var firstName = structuredName?.given ?: "" - var middleName = structuredName?.additionalNames?.firstOrNull() ?: "" - var surname = structuredName?.family ?: "" - - if (structuredName == null && ezContact.formattedName?.value?.isNotBlank() == true) { - val fn = ezContact.formattedName.value.trim() - val parts = fn.split("\\s+".toRegex(), limit = 2) + val middleName = structuredName?.additionalNames?.firstOrNull() ?: "" + val surname = structuredName?.family ?: "" + val suffix = structuredName?.suffixes?.firstOrNull() ?: "" - firstName = parts.getOrNull(0) ?: "" - surname = parts.getOrNull(1) ?: "" + // vCard 4.0 makes the structured name (N) optional while the + // formatted name (FN) is mandatory. When N is missing or yields + // no usable parts, fall back to FN. FN is a free-form display + // string with no guaranteed word order, so we keep it intact in + // firstName instead of guessing a given/family split, which + // would silently corrupt names like "von Neumann" or non-Western + // name orders. + val formattedName = ezContact.formattedName?.value?.trim().orEmpty() + if (firstName.isBlank() && middleName.isBlank() && surname.isBlank() + && formattedName.isNotBlank() + ) { + firstName = formattedName } - val suffix = structuredName?.suffixes?.firstOrNull() ?: "" val nickname = ezContact.nickname?.values?.firstOrNull() ?: "" var photoUri = "" From 05cfc0168d835adafc9d746ad5be304377a58379 Mon Sep 17 00:00:00 2001 From: Tchiller <2923006+Tchiller@users.noreply.github.com> Date: Tue, 19 May 2026 22:59:33 +0200 Subject: [PATCH 4/4] Split FN fallback condition to satisfy detekt ComplexCondition --- .../kotlin/org/fossify/contacts/helpers/VcfImporter.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt b/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt index 15b0ecc0e..fea4382e7 100644 --- a/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt +++ b/app/src/main/kotlin/org/fossify/contacts/helpers/VcfImporter.kt @@ -67,10 +67,11 @@ class VcfImporter(val activity: SimpleActivity) { // firstName instead of guessing a given/family split, which // would silently corrupt names like "von Neumann" or non-Western // name orders. + val hasStructuredName = firstName.isNotBlank() + || middleName.isNotBlank() + || surname.isNotBlank() val formattedName = ezContact.formattedName?.value?.trim().orEmpty() - if (firstName.isBlank() && middleName.isBlank() && surname.isBlank() - && formattedName.isNotBlank() - ) { + if (!hasStructuredName && formattedName.isNotBlank()) { firstName = formattedName }