@cap-kit/people 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CapKitPeople.podspec +20 -0
- package/LICENSE +21 -0
- package/Package.swift +28 -0
- package/README.md +1177 -0
- package/android/build.gradle +101 -0
- package/android/src/main/AndroidManifest.xml +4 -0
- package/android/src/main/java/io/capkit/people/PeopleImpl.kt +1003 -0
- package/android/src/main/java/io/capkit/people/PeopleObserver.kt +80 -0
- package/android/src/main/java/io/capkit/people/PeoplePlugin.kt +766 -0
- package/android/src/main/java/io/capkit/people/config/PeopleConfig.kt +44 -0
- package/android/src/main/java/io/capkit/people/error/PeopleError.kt +90 -0
- package/android/src/main/java/io/capkit/people/error/PeopleErrorMessages.kt +39 -0
- package/android/src/main/java/io/capkit/people/logger/PeopleLogger.kt +85 -0
- package/android/src/main/java/io/capkit/people/models/ContactModels.kt +64 -0
- package/android/src/main/java/io/capkit/people/utils/PeopleUtils.kt +133 -0
- package/android/src/main/res/.gitkeep +0 -0
- package/dist/docs.json +1449 -0
- package/dist/esm/definitions.d.ts +775 -0
- package/dist/esm/definitions.js +31 -0
- package/dist/esm/definitions.js.map +1 -0
- package/dist/esm/index.d.ts +15 -0
- package/dist/esm/index.js +18 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/web.d.ts +120 -0
- package/dist/esm/web.js +252 -0
- package/dist/esm/web.js.map +1 -0
- package/dist/plugin.cjs +300 -0
- package/dist/plugin.cjs.map +1 -0
- package/dist/plugin.js +303 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Sources/PeoplePlugin/PeopleImpl.swift +463 -0
- package/ios/Sources/PeoplePlugin/PeoplePlugin.swift +627 -0
- package/ios/Sources/PeoplePlugin/PrivacyInfo.xcprivacy +13 -0
- package/ios/Sources/PeoplePlugin/Utils/PeopleUtils.swift +120 -0
- package/ios/Sources/PeoplePlugin/Version.swift +16 -0
- package/ios/Sources/PeoplePlugin/config/PeopleConfig.swift +56 -0
- package/ios/Sources/PeoplePlugin/error/PeopleError.swift +89 -0
- package/ios/Sources/PeoplePlugin/error/PeopleErrorMessages.swift +25 -0
- package/ios/Sources/PeoplePlugin/logger/PeopleLogging.swift +69 -0
- package/ios/Sources/PeoplePlugin/models/ContactModels.swift +68 -0
- package/ios/Tests/PeoplePluginTests/PeoplePluginTests.swift +10 -0
- package/package.json +119 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
package io.capkit.people.config
|
|
2
|
+
|
|
3
|
+
import com.getcapacitor.Plugin
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Plugin configuration holder for the People plugin.
|
|
7
|
+
*
|
|
8
|
+
* This class is responsible for reading and parsing static configuration values
|
|
9
|
+
* defined under the `plugins.People` key in `capacitor.config.ts`.
|
|
10
|
+
*
|
|
11
|
+
* Architectural rules:
|
|
12
|
+
* - Read once during plugin initialization in the load() phase.
|
|
13
|
+
* - Configuration values are read-only at runtime.
|
|
14
|
+
* - Consumed only by native code.
|
|
15
|
+
*/
|
|
16
|
+
class PeopleConfig(
|
|
17
|
+
plugin: Plugin,
|
|
18
|
+
) {
|
|
19
|
+
// -----------------------------------------------------------------------------
|
|
20
|
+
// Properties
|
|
21
|
+
// -----------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Enables verbose native logging via PeopleLogger.
|
|
25
|
+
*
|
|
26
|
+
* When true, additional debug information and lifecycle events are printed to Logcat.
|
|
27
|
+
* This setting is read-only and applied during plugin initialization.
|
|
28
|
+
*
|
|
29
|
+
* Default: false
|
|
30
|
+
*/
|
|
31
|
+
val verboseLogging: Boolean
|
|
32
|
+
|
|
33
|
+
// -----------------------------------------------------------------------------
|
|
34
|
+
// Initialization
|
|
35
|
+
// -----------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
init {
|
|
38
|
+
// Access the plugin-specific configuration object provided by Capacitor bridge
|
|
39
|
+
val config = plugin.getConfig()
|
|
40
|
+
|
|
41
|
+
// Extract verboseLogging flag with a safe default fallback
|
|
42
|
+
verboseLogging = config.getBoolean("verboseLogging", false)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
package io.capkit.people.error
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native error model for the People plugin (Android).
|
|
5
|
+
*
|
|
6
|
+
* Architectural rules:
|
|
7
|
+
* - Must NOT reference Capacitor APIs.
|
|
8
|
+
* - Must NOT reference JavaScript directly.
|
|
9
|
+
* - Must be throwable from the Implementation (Impl) layer.
|
|
10
|
+
* - Mapping to JS-facing error codes happens ONLY in the Plugin layer.
|
|
11
|
+
*/
|
|
12
|
+
sealed class PeopleError(
|
|
13
|
+
message: String,
|
|
14
|
+
) : Throwable(message) {
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Specific Error Types
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Feature or capability is not available due to device or configuration limitations.
|
|
21
|
+
* Maps to the 'UNAVAILABLE' error code in JavaScript.
|
|
22
|
+
*/
|
|
23
|
+
class Unavailable(
|
|
24
|
+
message: String,
|
|
25
|
+
) : PeopleError(message)
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The user cancelled an interactive flow (e.g., contact picker).
|
|
29
|
+
* Maps to the 'CANCELLED' error code in JavaScript.
|
|
30
|
+
*/
|
|
31
|
+
class Cancelled(
|
|
32
|
+
message: String,
|
|
33
|
+
) : PeopleError(message)
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Required permission was denied or not granted by the user.
|
|
37
|
+
* Maps to the 'PERMISSION_DENIED' error code in JavaScript.
|
|
38
|
+
*/
|
|
39
|
+
class PermissionDenied(
|
|
40
|
+
message: String,
|
|
41
|
+
) : PeopleError(message)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Plugin failed to initialize or perform a required native operation.
|
|
45
|
+
* Maps to the 'INIT_FAILED' error code in JavaScript.
|
|
46
|
+
*/
|
|
47
|
+
class InitFailed(
|
|
48
|
+
message: String,
|
|
49
|
+
) : PeopleError(message)
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Invalid or malformed input was provided by the caller.
|
|
53
|
+
* Maps to the 'INVALID_INPUT' error code in JavaScript.
|
|
54
|
+
*/
|
|
55
|
+
class InvalidInput(
|
|
56
|
+
message: String,
|
|
57
|
+
) : PeopleError(message)
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Invalid or unsupported input type was provided to the native implementation.
|
|
61
|
+
* Maps to the 'UNKNOWN_TYPE' error code in JavaScript.
|
|
62
|
+
*/
|
|
63
|
+
class UnknownType(
|
|
64
|
+
message: String,
|
|
65
|
+
) : PeopleError(message)
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The requested resource does not exist (e.g., contact or group not found).
|
|
69
|
+
* Maps to the 'NOT_FOUND' error code in JavaScript.
|
|
70
|
+
*/
|
|
71
|
+
class NotFound(
|
|
72
|
+
message: String,
|
|
73
|
+
) : PeopleError(message)
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* The operation conflicts with the current state (e.g., read-only group).
|
|
77
|
+
* Maps to the 'CONFLICT' error code in JavaScript.
|
|
78
|
+
*/
|
|
79
|
+
class Conflict(
|
|
80
|
+
message: String,
|
|
81
|
+
) : PeopleError(message)
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The operation did not complete within the expected time.
|
|
85
|
+
* Maps to the 'TIMEOUT' error code in JavaScript.
|
|
86
|
+
*/
|
|
87
|
+
class Timeout(
|
|
88
|
+
message: String,
|
|
89
|
+
) : PeopleError(message)
|
|
90
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
package io.capkit.people.error
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Canonical error messages shared across platforms.
|
|
5
|
+
* Keep these strings identical on iOS and Android.
|
|
6
|
+
*/
|
|
7
|
+
object PeopleErrorMessages {
|
|
8
|
+
const val PERMISSION_DENIED = "Permission denied"
|
|
9
|
+
const val ID_REQUIRED = "id is required"
|
|
10
|
+
const val QUERY_REQUIRED = "query is required"
|
|
11
|
+
const val EVENT_NAME_REQUIRED = "eventName is required"
|
|
12
|
+
const val GROUP_NAME_REQUIRED = "Group name is required"
|
|
13
|
+
const val GROUP_ID_REQUIRED = "Group ID is required"
|
|
14
|
+
const val CONTACT_IDS_REQUIRED = "Contact IDs required"
|
|
15
|
+
const val CONTACT_DATA_REQUIRED = "Contact data required"
|
|
16
|
+
const val AT_LEAST_ONE_WRITABLE_FIELD_REQUIRED = "At least one writable contact field is required"
|
|
17
|
+
const val CONTACT_ID_REQUIRED = "Contact ID is required"
|
|
18
|
+
const val REQUIRED_FIELDS_MISSING = "Required fields missing"
|
|
19
|
+
const val IDS_REQUIRED = "IDs required"
|
|
20
|
+
|
|
21
|
+
fun unsupportedEventName(value: String) = "Unsupported eventName: $value"
|
|
22
|
+
|
|
23
|
+
const val MISSING_CONTACTS_USAGE_DESCRIPTION = "Missing NSContactsUsageDescription in Info.plist"
|
|
24
|
+
const val NO_CONTACT_URI_RETURNED = "No contact URI returned"
|
|
25
|
+
const val USER_CANCELLED_SELECTION = "User cancelled selection"
|
|
26
|
+
const val FAILED_TO_PARSE_CONTACT_FROM_URI = "Failed to parse contact from URI"
|
|
27
|
+
const val ERROR_PROCESSING_CONTACT = "Error processing contact"
|
|
28
|
+
|
|
29
|
+
fun unsupportedProjectionField(value: String) = "Unsupported projection field: $value"
|
|
30
|
+
|
|
31
|
+
const val CONTACT_NOT_FOUND = "Contact not found"
|
|
32
|
+
const val FAILED = "Failed"
|
|
33
|
+
const val FAILED_TO_CREATE_GROUP = "Failed to create group"
|
|
34
|
+
const val FAILED_TO_DELETE_GROUP = "Failed to delete group"
|
|
35
|
+
const val FAILED_TO_RETRIEVE_CONTACT = "Failed to retrieve contact"
|
|
36
|
+
const val FAILED_TO_CREATE_CONTACT = "Failed to create contact"
|
|
37
|
+
const val UPDATE_FAILED = "Update failed"
|
|
38
|
+
const val MERGE_FAILED = "Merge failed"
|
|
39
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
package io.capkit.people.logger
|
|
2
|
+
|
|
3
|
+
import android.util.Log
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Centralized logging utility for the People plugin.
|
|
7
|
+
*
|
|
8
|
+
* This logging provides a single entry point for all native logs
|
|
9
|
+
* and supports runtime-controlled verbose logging.
|
|
10
|
+
*
|
|
11
|
+
* The goal is to avoid scattering `if (verbose)` checks across
|
|
12
|
+
* business logic and keep logging behavior consistent.
|
|
13
|
+
*/
|
|
14
|
+
object PeopleLogger {
|
|
15
|
+
/**
|
|
16
|
+
* Logcat tag used for all plugin logs.
|
|
17
|
+
* Helps filtering logs during debugging.
|
|
18
|
+
*/
|
|
19
|
+
private const val TAG = "⚡️ People"
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Controls whether debug logs are printed.
|
|
23
|
+
*
|
|
24
|
+
* This flag should be set once during plugin initialization
|
|
25
|
+
* based on configuration values.
|
|
26
|
+
*/
|
|
27
|
+
var verbose: Boolean = false
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Prints a debug / verbose log message.
|
|
31
|
+
*
|
|
32
|
+
* This method should be used for development-time diagnostics
|
|
33
|
+
* and is automatically silenced when [verbose] is false.
|
|
34
|
+
*
|
|
35
|
+
* @param messages One or more message fragments to be concatenated.
|
|
36
|
+
*/
|
|
37
|
+
fun debug(vararg messages: String) {
|
|
38
|
+
if (verbose) {
|
|
39
|
+
log(TAG, Log.DEBUG, *messages)
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Prints an error log message.
|
|
45
|
+
*
|
|
46
|
+
* Error logs are always printed regardless of [verbose] state.
|
|
47
|
+
*
|
|
48
|
+
* @param message Human-readable error description.
|
|
49
|
+
* @param e Optional exception for stack trace logging.
|
|
50
|
+
*/
|
|
51
|
+
fun error(
|
|
52
|
+
message: String,
|
|
53
|
+
e: Throwable? = null,
|
|
54
|
+
) {
|
|
55
|
+
val sb = StringBuilder(message)
|
|
56
|
+
if (e != null) {
|
|
57
|
+
sb.append(" | Error: ").append(e.message)
|
|
58
|
+
}
|
|
59
|
+
Log.e(TAG, sb.toString(), e)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Internal low-level log dispatcher.
|
|
64
|
+
*
|
|
65
|
+
* Joins message fragments and forwards them to Android's Log API
|
|
66
|
+
* using the specified priority.
|
|
67
|
+
*/
|
|
68
|
+
fun log(
|
|
69
|
+
tag: String,
|
|
70
|
+
level: Int,
|
|
71
|
+
vararg messages: String,
|
|
72
|
+
) {
|
|
73
|
+
val sb = StringBuilder()
|
|
74
|
+
for (msg in messages) {
|
|
75
|
+
sb.append(msg).append(" ")
|
|
76
|
+
}
|
|
77
|
+
when (level) {
|
|
78
|
+
Log.DEBUG -> Log.d(tag, sb.toString())
|
|
79
|
+
Log.INFO -> Log.i(tag, sb.toString())
|
|
80
|
+
Log.WARN -> Log.w(tag, sb.toString())
|
|
81
|
+
Log.ERROR -> Log.e(tag, sb.toString())
|
|
82
|
+
else -> Log.v(tag, sb.toString())
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
package io.capkit.people.models
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native representation of a Contact.
|
|
5
|
+
*
|
|
6
|
+
* Architectural rules:
|
|
7
|
+
* - This class MUST remain agnostic to Capacitor JSObjects.
|
|
8
|
+
* - Used exclusively by the Impl layer to orchestrate native data.
|
|
9
|
+
* - Mapped to JS payloads only within the Utility or Plugin layer.
|
|
10
|
+
*/
|
|
11
|
+
data class ContactData(
|
|
12
|
+
val id: String,
|
|
13
|
+
val lookupKey: String? = null,
|
|
14
|
+
val displayName: String? = null,
|
|
15
|
+
val organization: ContactOrganization? = null, // Added for parity
|
|
16
|
+
val phones: List<ContactField> = emptyList(),
|
|
17
|
+
val emails: List<ContactField> = emptyList(),
|
|
18
|
+
val addresses: List<PostalAddress> = emptyList(), // Extraction logic needed
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Native representation of a contact's organization.
|
|
23
|
+
*/
|
|
24
|
+
data class ContactOrganization(
|
|
25
|
+
val company: String?,
|
|
26
|
+
val title: String?,
|
|
27
|
+
val department: String?,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// -----------------------------------------------------------------------------
|
|
31
|
+
// Support Models
|
|
32
|
+
// -----------------------------------------------------------------------------
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Represents a generic contact field (e.g., Phone or Email).
|
|
36
|
+
* Includes a label (type) and the actual value.
|
|
37
|
+
*/
|
|
38
|
+
data class ContactField(
|
|
39
|
+
val label: String?,
|
|
40
|
+
val value: String?,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Native representation of a physical postal address.
|
|
45
|
+
* Maps to structured postal data from the Android Contacts provider.
|
|
46
|
+
*/
|
|
47
|
+
data class PostalAddress(
|
|
48
|
+
val label: String?,
|
|
49
|
+
val street: String?,
|
|
50
|
+
val city: String?,
|
|
51
|
+
val region: String?,
|
|
52
|
+
val postcode: String?,
|
|
53
|
+
val country: String?,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Native representation of a contact group.
|
|
58
|
+
*/
|
|
59
|
+
data class GroupData(
|
|
60
|
+
val id: String,
|
|
61
|
+
val name: String,
|
|
62
|
+
val source: String,
|
|
63
|
+
val readOnly: Boolean,
|
|
64
|
+
)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
package io.capkit.people.utils
|
|
2
|
+
|
|
3
|
+
import android.database.Cursor
|
|
4
|
+
import android.provider.ContactsContract
|
|
5
|
+
import io.capkit.people.error.PeopleError
|
|
6
|
+
import io.capkit.people.models.ContactData
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Utility helpers for the People plugin.
|
|
10
|
+
*
|
|
11
|
+
* This object provides centralized logic for data transformation,
|
|
12
|
+
* cursor management, and JSON marshalling between native and bridge layers.
|
|
13
|
+
*/
|
|
14
|
+
object PeopleUtils {
|
|
15
|
+
// -----------------------------------------------------------------------------
|
|
16
|
+
// Projection Validation
|
|
17
|
+
// -----------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
private val supportedProjections =
|
|
20
|
+
setOf(
|
|
21
|
+
"name",
|
|
22
|
+
"organization",
|
|
23
|
+
"phones",
|
|
24
|
+
"emails",
|
|
25
|
+
"addresses",
|
|
26
|
+
)
|
|
27
|
+
// "image" is intentionally not supported on Android and must be rejected by validation.
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Validates that all requested projection fields are supported.
|
|
31
|
+
* Throws PeopleError.UnknownType if a field is invalid.
|
|
32
|
+
*/
|
|
33
|
+
fun validateProjection(projection: List<String>) {
|
|
34
|
+
for (field in projection) {
|
|
35
|
+
if (!supportedProjections.contains(field)) {
|
|
36
|
+
throw PeopleError.UnknownType("Unsupported projection field: $field")
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Normalizes native labels into a shared canonical set:
|
|
43
|
+
* mobile, home, work, main, fax, other.
|
|
44
|
+
*/
|
|
45
|
+
fun normalizeLabel(label: String?): String {
|
|
46
|
+
val normalized = label?.trim()?.lowercase() ?: return "other"
|
|
47
|
+
if (normalized.contains("mobile") || normalized.contains("cell")) return "mobile"
|
|
48
|
+
if (normalized.contains("home")) return "home"
|
|
49
|
+
if (normalized.contains("work")) return "work"
|
|
50
|
+
if (normalized.contains("main")) return "main"
|
|
51
|
+
if (normalized.contains("fax")) return "fax"
|
|
52
|
+
return "other"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// -----------------------------------------------------------------------------
|
|
56
|
+
// MimeType Constants
|
|
57
|
+
// -----------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
const val MIME_NAME = ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE
|
|
60
|
+
const val MIME_PHONE = ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE
|
|
61
|
+
const val MIME_EMAIL = ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
|
|
62
|
+
const val MIME_ORG = ContactsContract.CommonDataKinds.Organization.CONTENT_ITEM_TYPE
|
|
63
|
+
const val MIME_ADDRESS = ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
|
|
64
|
+
const val MIME_NOTE = ContactsContract.CommonDataKinds.Note.CONTENT_ITEM_TYPE
|
|
65
|
+
const val MIME_WEBSITE = ContactsContract.CommonDataKinds.Website.CONTENT_ITEM_TYPE
|
|
66
|
+
|
|
67
|
+
// -----------------------------------------------------------------------------
|
|
68
|
+
// Cursor Helpers
|
|
69
|
+
// -----------------------------------------------------------------------------
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Safely retrieves a String from a Cursor column.
|
|
73
|
+
* Returns null if the column is missing or data is unavailable.
|
|
74
|
+
*/
|
|
75
|
+
fun Cursor.getString(columnName: String): String? {
|
|
76
|
+
val index = this.getColumnIndex(columnName)
|
|
77
|
+
if (index == -1) return null
|
|
78
|
+
return this.getString(index)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Safely retrieves an Int from a Cursor column.
|
|
83
|
+
* Returns null if the column is missing or data is unavailable.
|
|
84
|
+
*/
|
|
85
|
+
fun Cursor.getInt(columnName: String): Int? {
|
|
86
|
+
val index = this.getColumnIndex(columnName)
|
|
87
|
+
if (index == -1) return null
|
|
88
|
+
return this.getInt(index)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// -----------------------------------------------------------------------------
|
|
92
|
+
// Native Model Factories
|
|
93
|
+
// -----------------------------------------------------------------------------
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Creates a native ContactData model instance.
|
|
97
|
+
* Ensures the Implementation layer remains decoupled from Capacitor types.
|
|
98
|
+
*/
|
|
99
|
+
fun createEmptyContact(
|
|
100
|
+
id: String,
|
|
101
|
+
lookupKey: String?,
|
|
102
|
+
): ContactData =
|
|
103
|
+
ContactData(
|
|
104
|
+
id = id,
|
|
105
|
+
lookupKey = lookupKey,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
// -----------------------------------------------------------------------------
|
|
109
|
+
// Bridge Marshalling (JS -> Native)
|
|
110
|
+
// -----------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Safely extracts a list of strings from a JSONArray based on a specific key.
|
|
114
|
+
* Uses optJSONObject and optString for maximum stability during data parsing.
|
|
115
|
+
*/
|
|
116
|
+
fun extractStringList(
|
|
117
|
+
array: org.json.JSONArray?,
|
|
118
|
+
key: String,
|
|
119
|
+
): List<String> {
|
|
120
|
+
val list = mutableListOf<String>()
|
|
121
|
+
if (array == null) return list
|
|
122
|
+
|
|
123
|
+
for (i in 0 until array.length()) {
|
|
124
|
+
val obj = array.optJSONObject(i)
|
|
125
|
+
obj?.optString(key)?.let { value ->
|
|
126
|
+
if (value.isNotEmpty()) {
|
|
127
|
+
list.add(value)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return list
|
|
132
|
+
}
|
|
133
|
+
}
|
|
File without changes
|