@circle-fin/w3s-pw-react-native-sdk 1.1.7 → 2.0.1

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.
Files changed (114) hide show
  1. package/BARE_REACT_NATIVE_GUIDE.md +397 -0
  2. package/LICENSE +1 -1
  3. package/README.md +203 -90
  4. package/android/build.gradle +43 -126
  5. package/android/src/main/AndroidManifest.xml +12 -16
  6. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/ProgrammablewalletRnSdkModule.kt +414 -244
  7. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/PromiseCallback.kt +76 -74
  8. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/RecordsHelper.kt +602 -0
  9. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/models/Records.kt +43 -0
  10. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/pwcustom/RnImageSetter.kt +55 -33
  11. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/pwcustom/RnLayoutProvider.kt +81 -79
  12. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/pwcustom/RnViewSetterProvider.kt +93 -63
  13. package/app.json +8 -0
  14. package/build/ProgrammablewalletRnSdkModule.d.ts +44 -0
  15. package/build/ProgrammablewalletRnSdkModule.d.ts.map +1 -0
  16. package/build/ProgrammablewalletRnSdkModule.js +21 -0
  17. package/build/ProgrammablewalletRnSdkModule.js.map +1 -0
  18. package/build/WalletSdk.d.ts +20 -0
  19. package/build/WalletSdk.d.ts.map +1 -0
  20. package/build/WalletSdk.js +303 -0
  21. package/build/WalletSdk.js.map +1 -0
  22. package/build/bridgeSafe.d.ts +50 -0
  23. package/build/bridgeSafe.d.ts.map +1 -0
  24. package/build/bridgeSafe.js +136 -0
  25. package/build/bridgeSafe.js.map +1 -0
  26. package/build/index.d.ts +21 -0
  27. package/build/index.d.ts.map +1 -0
  28. package/build/index.js +21 -0
  29. package/build/index.js.map +1 -0
  30. package/{lib/typescript/src → build}/types.d.ts +72 -43
  31. package/build/types.d.ts.map +1 -0
  32. package/build/types.js +331 -0
  33. package/build/types.js.map +1 -0
  34. package/build/utils/securityQuestionUtils.d.ts +43 -0
  35. package/build/utils/securityQuestionUtils.d.ts.map +1 -0
  36. package/build/utils/securityQuestionUtils.js +109 -0
  37. package/build/utils/securityQuestionUtils.js.map +1 -0
  38. package/expo-module.config.json +11 -0
  39. package/ios/Array+Extension.swift +17 -15
  40. package/ios/BridgeHelper.swift +71 -92
  41. package/ios/{RnWalletSdk+CustomizeAdapter.swift → CustomizeAdapter.swift} +19 -29
  42. package/ios/ProgrammablewalletRnSdk.podspec +30 -0
  43. package/ios/ProgrammablewalletRnSdkModule.swift +384 -0
  44. package/ios/TextConfig.swift +17 -15
  45. package/ios/TextKey.swift +17 -15
  46. package/ios/UIApplication+Extension.swift +26 -17
  47. package/ios/UIColor+Extension.swift +34 -28
  48. package/ios/UITextField+Extension.swift +31 -0
  49. package/ios/UIView+Extension.swift +24 -17
  50. package/package.json +71 -116
  51. package/plugins/apple-signin-entitlements.js +16 -0
  52. package/plugins/infoplist-config.js +77 -0
  53. package/plugins/infoplist-config.md +72 -0
  54. package/plugins/podfile-modifier.js +84 -0
  55. package/plugins/podfile-modifier.md +33 -0
  56. package/plugins/withCopyFiles.js +148 -0
  57. package/plugins/withCopyFiles.md +81 -0
  58. package/src/ProgrammablewalletRnSdkModule.ts +68 -38
  59. package/src/WalletSdk.ts +297 -159
  60. package/src/bridgeSafe.ts +156 -0
  61. package/src/index.ts +21 -0
  62. package/src/types.ts +133 -110
  63. package/src/utils/securityQuestionUtils.ts +121 -0
  64. package/COPYRIGHT +0 -10
  65. package/android/gradle.properties +0 -21
  66. package/android/src/main/AndroidManifestNew.xml +0 -22
  67. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/BridgeHelper.kt +0 -399
  68. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/ProgrammablewalletRnSdkPackage.kt +0 -49
  69. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/PromiseCallback2.kt +0 -65
  70. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/PromiseLogoutCallback.kt +0 -47
  71. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/PromiseSocialCallback.kt +0 -53
  72. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/TestHelper.kt +0 -37
  73. package/android/src/main/java/com/circlefin/programmablewalletrnsdk/annotation/ExcludeFromGeneratedCCReport.kt +0 -33
  74. package/android/src/newarch/ProgrammablewalletRnSdkSpec.kt +0 -29
  75. package/android/src/oldarch/ProgrammablewalletRnSdkSpec.kt +0 -76
  76. package/circlefin-w3s-pw-react-native-sdk.podspec +0 -41
  77. package/ios/EventEmitter.swift +0 -49
  78. package/ios/ProgrammablewalletRnSdk.h +0 -29
  79. package/ios/ProgrammablewalletRnSdk.mm +0 -162
  80. package/ios/ReactNativeEventEmitter.m +0 -27
  81. package/ios/ReactNativeEventEmitter.swift +0 -37
  82. package/ios/RnWalletSdk.swift +0 -390
  83. package/ios/programmablewallet-rn-sdk-Bridging-Header.h +0 -22
  84. package/lib/commonjs/NativeProgrammablewalletRnSdk.js +0 -24
  85. package/lib/commonjs/NativeProgrammablewalletRnSdk.js.map +0 -1
  86. package/lib/commonjs/ProgrammablewalletRnSdkModule.js +0 -38
  87. package/lib/commonjs/ProgrammablewalletRnSdkModule.js.map +0 -1
  88. package/lib/commonjs/WalletSdk.js +0 -211
  89. package/lib/commonjs/WalletSdk.js.map +0 -1
  90. package/lib/commonjs/index.js +0 -74
  91. package/lib/commonjs/index.js.map +0 -1
  92. package/lib/commonjs/types.js +0 -342
  93. package/lib/commonjs/types.js.map +0 -1
  94. package/lib/module/NativeProgrammablewalletRnSdk.js +0 -19
  95. package/lib/module/NativeProgrammablewalletRnSdk.js.map +0 -1
  96. package/lib/module/ProgrammablewalletRnSdkModule.js +0 -31
  97. package/lib/module/ProgrammablewalletRnSdkModule.js.map +0 -1
  98. package/lib/module/WalletSdk.js +0 -203
  99. package/lib/module/WalletSdk.js.map +0 -1
  100. package/lib/module/index.js +0 -18
  101. package/lib/module/index.js.map +0 -1
  102. package/lib/module/types.js +0 -334
  103. package/lib/module/types.js.map +0 -1
  104. package/lib/typescript/src/NativeProgrammablewalletRnSdk.d.ts +0 -28
  105. package/lib/typescript/src/NativeProgrammablewalletRnSdk.d.ts.map +0 -1
  106. package/lib/typescript/src/ProgrammablewalletRnSdkModule.d.ts +0 -3
  107. package/lib/typescript/src/ProgrammablewalletRnSdkModule.d.ts.map +0 -1
  108. package/lib/typescript/src/WalletSdk.d.ts +0 -3
  109. package/lib/typescript/src/WalletSdk.d.ts.map +0 -1
  110. package/lib/typescript/src/index.d.ts +0 -4
  111. package/lib/typescript/src/index.d.ts.map +0 -1
  112. package/lib/typescript/src/types.d.ts.map +0 -1
  113. package/src/NativeProgrammablewalletRnSdk.ts +0 -77
  114. package/src/index.tsx +0 -29
@@ -0,0 +1,602 @@
1
+ /**
2
+ * Copyright 2025 Circle Internet Group, Inc. All rights reserved.
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+ package com.circlefin.programmablewalletrnsdk
19
+
20
+ import android.content.Context
21
+ import android.graphics.Color
22
+ import android.graphics.Typeface
23
+ import android.text.TextUtils
24
+ import android.util.Log
25
+ import circle.programmablewallet.sdk.api.ApiError
26
+ import circle.programmablewallet.sdk.api.ExecuteWarning
27
+ import circle.programmablewallet.sdk.presentation.SecurityQuestion
28
+ import circle.programmablewallet.sdk.presentation.SettingsManagement
29
+ import circle.programmablewallet.sdk.presentation.TextConfig
30
+ import circle.programmablewallet.sdk.presentation.IconTextConfig
31
+ import com.circlefin.programmablewalletrnsdk.pwcustom.RnLayoutProvider
32
+ import circle.programmablewallet.sdk.presentation.Resource
33
+ import com.circlefin.programmablewalletrnsdk.pwcustom.RnImageSetter
34
+ import circle.programmablewallet.sdk.result.ExecuteResult
35
+ import circle.programmablewallet.sdk.result.LoginResult
36
+ import com.circlefin.programmablewalletrnsdk.models.ConfigurationRecord
37
+ import com.circlefin.programmablewalletrnsdk.models.SettingsManagementRecord
38
+
39
+ object RecordsHelper {
40
+
41
+ private const val INPUT_TYPE_TEXT = "text"
42
+ private const val INPUT_TYPE_DATE_PICKER = "datePicker"
43
+
44
+ private val typefaceMap = mutableMapOf<String, Typeface>()
45
+ fun convertJsSecurityQuestionsToNative(jsSecurityQuestions: Array<Any>): Array<SecurityQuestion?> {
46
+ return jsSecurityQuestions.mapIndexed { _, jsQuestion ->
47
+ try {
48
+ val (title, inputTypeValue) = extractSecurityQuestionData(jsQuestion)
49
+
50
+ val inputType = when (inputTypeValue) {
51
+ INPUT_TYPE_TEXT -> SecurityQuestion.InputType.text
52
+ INPUT_TYPE_DATE_PICKER -> SecurityQuestion.InputType.datePicker
53
+ else -> {
54
+ SecurityQuestion.InputType.text
55
+ }
56
+ }
57
+
58
+ val securityQuestion = SecurityQuestion(title, inputType)
59
+ securityQuestion
60
+ } catch (e: Exception) {
61
+ SecurityQuestion("", SecurityQuestion.InputType.text)
62
+ }
63
+ }.toTypedArray()
64
+ }
65
+
66
+ private fun extractSecurityQuestionData(jsQuestion: Any): Pair<String, String?> {
67
+ return when (jsQuestion) {
68
+ is Map<*, *> -> {
69
+ val title = (jsQuestion["title"] as? String) ?: ""
70
+ val inputType = jsQuestion["inputType"] as? String
71
+ Pair(title, inputType)
72
+ }
73
+
74
+ is Array<*> -> {
75
+ if (jsQuestion.size >= 2) {
76
+ val title = (jsQuestion[0] as? String) ?: ""
77
+ val inputType = jsQuestion[1] as? String
78
+ Pair(title, inputType)
79
+ } else {
80
+ Pair("", null)
81
+ }
82
+ }
83
+
84
+ else -> {
85
+ val title = tryExtractField(jsQuestion, "title") ?: ""
86
+ val inputType = tryExtractField(jsQuestion, "inputType")
87
+ Pair(title, inputType)
88
+ }
89
+ }
90
+ }
91
+
92
+ private fun tryExtractField(obj: Any, fieldName: String): String? {
93
+ return try {
94
+ /** Try direct field access */
95
+ val field = obj.javaClass.getDeclaredField(fieldName)
96
+ field.isAccessible = true
97
+ field.get(obj) as? String
98
+ } catch (e: Exception) {
99
+ try {
100
+ /** Try getter method */
101
+ val capitalizedName = fieldName.first().uppercaseChar() + fieldName.substring(1)
102
+ val getter = obj.javaClass.getMethod("get$capitalizedName")
103
+ getter.invoke(obj) as? String
104
+ } catch (e2: Exception) {
105
+ try {
106
+ /** Try public field */
107
+ val field = obj.javaClass.getField(fieldName)
108
+ field.get(obj) as? String
109
+ } catch (e3: Exception) {
110
+ null
111
+ }
112
+ }
113
+ }
114
+ }
115
+
116
+ private fun toNativeSettingsManagement(record: SettingsManagementRecord): SettingsManagement {
117
+ return SettingsManagement(record.enableBiometricsPin)
118
+ }
119
+
120
+ fun prepareConfigurationData(record: ConfigurationRecord): Triple<String?, String?, SettingsManagement?> {
121
+ val endpoint = record.endpoint?.run {
122
+ if (this.endsWith("/")) this else "$this/"
123
+ }
124
+ val appId = record.appId
125
+ val settings = record.settingsManagement?.let { settingsRecord ->
126
+ toNativeSettingsManagement(settingsRecord)
127
+ }
128
+
129
+ return Triple(endpoint, appId, settings)
130
+ }
131
+
132
+ fun convertToDismissOnCallbackMap(mapData: Map<String, Any>): Map<Int, Boolean> {
133
+ val result = mapData.mapNotNull { (key, value) ->
134
+ try {
135
+ val intKey = key.toInt()
136
+ val boolValue = when (value) {
137
+ is Boolean -> value
138
+ is String -> value.toBoolean()
139
+ is Number -> value.toInt() != 0
140
+ else -> false
141
+ }
142
+ intKey to boolValue
143
+ } catch (e: NumberFormatException) {
144
+ null
145
+ }
146
+ }.toMap()
147
+ return result
148
+ }
149
+
150
+ fun filterValidErrorCodeKeys(mapData: Map<String, Any>): Map<String, Any> {
151
+ return mapData.filter { (key, _) ->
152
+ // Check if key is a valid integer string or ErrorCode name
153
+ try {
154
+ key.toInt()
155
+ true
156
+ } catch (e: NumberFormatException) {
157
+ errorCodeStringToIntMap.containsKey(key)
158
+ }
159
+ }
160
+ }
161
+
162
+ fun convertToErrorStringMap(mapData: Map<String, Any>): Map<Int, String> {
163
+ val result = mapData.map { (key, value) ->
164
+ // Convert key to Int (keys are pre-validated by filterValidErrorCodeKeys)
165
+ val intKey: Int = try {
166
+ key.toInt()
167
+ } catch (e: NumberFormatException) {
168
+ // If not an integer string, map from error code string
169
+ errorCodeStringToIntMap[key] ?: throw IllegalArgumentException("Invalid ErrorCode key: $key")
170
+ }
171
+
172
+ val stringValue = when (value) {
173
+ is String -> value
174
+ else -> value.toString()
175
+ }
176
+ intKey to stringValue
177
+ }.toMap()
178
+ return result
179
+ }
180
+
181
+ private val errorCodeStringToIntMap = mapOf(
182
+ "unknown" to -1,
183
+ "success" to 0,
184
+ "apiParameterMissing" to 1,
185
+ "apiParameterInvalid" to 2,
186
+ "forbidden" to 3,
187
+ "unauthorized" to 4,
188
+ "retry" to 9,
189
+ "customerSuspended" to 10,
190
+ "pending" to 11,
191
+ "invalidSession" to 12,
192
+ "invalidPartnerId" to 13,
193
+ "invalidMessage" to 14,
194
+ "invalidPhone" to 15,
195
+ "walletIdNotFound" to 156001,
196
+ "tokenIdNotFound" to 156002,
197
+ "transactionIdNotFound" to 156003,
198
+ "walletSetIdNotFound" to 156004,
199
+ "notEnoughFounds" to 155201,
200
+ "notEnoughBalance" to 155202,
201
+ "exceedWithdrawLimit" to 155203,
202
+ "minimumFundsRequired" to 155204,
203
+ "invalidTransactionFee" to 155205,
204
+ "rejectedOnAmlScreening" to 155206,
205
+ "tagRequired" to 155207,
206
+ "gasLimitTooLow" to 155208,
207
+ "transactionDataNotEncodedProperly" to 155209,
208
+ "fullNodeReturnedError" to 155210,
209
+ "walletSetupRequired" to 155211,
210
+ "lowerThenMinimumAccountBalance" to 155212,
211
+ "rejectedByBlockchain" to 155213,
212
+ "droppedAsPartOfReorg" to 155214,
213
+ "operationNotSupport" to 155215,
214
+ "amountBelowMinimum" to 155216,
215
+ "wrongNftTokenIdNumber" to 155217,
216
+ "invalidDestinationAddress" to 155218,
217
+ "tokenWalletChainMismatch" to 155219,
218
+ "wrongAmountsNumber" to 155220,
219
+ "userAlreadyExisted" to 155101,
220
+ "userNotFound" to 155102,
221
+ "userTokenNotFound" to 155103,
222
+ "userTokenExpired" to 155104,
223
+ "invalidUserToken" to 155105,
224
+ "userWasInitialized" to 155106,
225
+ "userHasSetPin" to 155107,
226
+ "userHasSetSecurityQuestion" to 155108,
227
+ "userWasDisabled" to 155109,
228
+ "userDoesNotSetPinYet" to 155110,
229
+ "userDoesNotSetSecurityQuestionYet" to 155111,
230
+ "incorrectUserPin" to 155112,
231
+ "incorrectDeviceId" to 155113,
232
+ "incorrectAppId" to 155114,
233
+ "incorrectSecurityAnswers" to 155115,
234
+ "invalidChallengeId" to 155116,
235
+ "invalidApproveContent" to 155117,
236
+ "invalidEncryptionKey" to 155118,
237
+ "userPinLocked" to 155119,
238
+ "securityAnswersLocked" to 155120,
239
+ "walletIsFrozen" to 155501,
240
+ "maxWalletLimitReached" to 155502,
241
+ "walletSetIdMutuallyExclusive" to 155503,
242
+ "metadataUnmatched" to 155504,
243
+ "userCanceled" to 155701,
244
+ "launchUiFailed" to 155702,
245
+ "pinCodeNotMatched" to 155703,
246
+ "insecurePinCode" to 155704,
247
+ "hintsMatchAnswers" to 155705,
248
+ "networkError" to 155706,
249
+ "biometricsSettingNotEnabled" to 155708,
250
+ "deviceNotSupportBiometrics" to 155709,
251
+ "biometricsKeyPermanentlyInvalidated" to 155710,
252
+ "biometricsUserSkip" to 155711,
253
+ "biometricsUserDisableForPin" to 155712,
254
+ "biometricsUserLockout" to 155713,
255
+ "biometricsUserLockoutPermanent" to 155714,
256
+ "biometricsUserNotAllowPermission" to 155715,
257
+ "biometricsInternalError" to 155716,
258
+ "userSecretMissing" to 155717,
259
+ "invalidUserTokenFormat" to 155718,
260
+ "userTokenMismatch" to 155719,
261
+ "socialLoginFailed" to 155720,
262
+ "loginInfoMissing" to 155721
263
+ )
264
+
265
+ fun convertToImageMap(context: Context, mapData: Map<String, Any>): Map<String, String> {
266
+ val result = mapData.mapNotNull { (key, value) ->
267
+ try {
268
+ val imageUrl = when (value) {
269
+ is String -> value
270
+ else -> value.toString()
271
+ }
272
+
273
+ // Map TypeScript ImageKey enum values to their actual enum names
274
+ // Some ImageKey values use different names than expected
275
+ val mappedKey = when (key) {
276
+ "back" -> "back" // This maps to ToolbarIcon.back
277
+ "close" -> "close" // This maps to ToolbarIcon.close
278
+ else -> key // Most keys should match Resource.Icon enum names directly
279
+ }
280
+
281
+ mappedKey to imageUrl
282
+ } catch (e: Exception) {
283
+ Log.e("RecordsHelper", "Failed to process image map entry for key: '$key', value: '$value'", e)
284
+ null
285
+ }
286
+ }.toMap()
287
+ return result
288
+ }
289
+
290
+ private fun convertExecuteResultToMap(result: ExecuteResult): Map<String, Any?> {
291
+ return mapOf(
292
+ "resultType" to result.resultType?.name,
293
+ "status" to result.status?.name,
294
+ "data" to result.data?.let { data ->
295
+ mapOf(
296
+ "signature" to data.signature,
297
+ "signedTransaction" to data.signedTransaction,
298
+ "txHash" to data.txHash
299
+ )
300
+ }
301
+ )
302
+ }
303
+
304
+ fun convertApiErrorToMap(error: ApiError): Map<String, Any?> {
305
+ // Check if there's a custom error message set via setErrorStringMap
306
+ val customMessage = RnLayoutProvider.getErrorString(error.code)
307
+ val messageToUse = customMessage ?: error.message
308
+
309
+ return mapOf(
310
+ "code" to error.code.value,
311
+ "message" to messageToUse
312
+ )
313
+ }
314
+
315
+ fun convertExecuteWarningToMap(warning: ExecuteWarning): Map<String, Any?> {
316
+ return mapOf(
317
+ "warningType" to warning.warningType,
318
+ "warningString" to warning.warningString
319
+ )
320
+ }
321
+
322
+ fun convertResultToMap(result: Any?): Map<String, Any?> {
323
+ return when (result) {
324
+ null -> emptyMap()
325
+ is ExecuteResult -> convertExecuteResultToMap(result)
326
+ is LoginResult -> convertLoginResultToMap(result)
327
+ is Map<*, *> -> {
328
+ @Suppress("UNCHECKED_CAST")
329
+ result as Map<String, Any?>
330
+ }
331
+
332
+ else -> {
333
+ try {
334
+ mapOf("data" to result.toString())
335
+ } catch (e: Exception) {
336
+ mapOf("data" to result.toString())
337
+ }
338
+ }
339
+ }
340
+ }
341
+
342
+ private fun convertLoginResultToMap(loginResult: LoginResult): Map<String, Any?> {
343
+ val resultMap = mutableMapOf<String, Any?>()
344
+
345
+ loginResult.userToken?.let { resultMap["userToken"] = it }
346
+ loginResult.encryptionKey?.let { resultMap["encryptionKey"] = it }
347
+ loginResult.refreshToken?.let { resultMap["refreshToken"] = it }
348
+
349
+ loginResult.oauthInfo?.let { oauthInfo ->
350
+ val oauthMap = mutableMapOf<String, Any?>()
351
+ oauthInfo.provider?.let { oauthMap["provider"] = it }
352
+ oauthInfo.scope?.let { oauthMap["scope"] = it }
353
+ oauthInfo.socialUserUUID?.let { oauthMap["socialUserUUID"] = it }
354
+
355
+ oauthInfo.socialUserInfo?.let { userInfo ->
356
+ val userInfoMap = mutableMapOf<String, Any?>()
357
+ userInfo.name?.let { userInfoMap["name"] = it }
358
+ userInfo.email?.let { userInfoMap["email"] = it }
359
+ userInfo.phone?.let { userInfoMap["phone"] = it }
360
+ if (userInfoMap.isNotEmpty()) {
361
+ oauthMap["socialUserInfo"] = userInfoMap
362
+ }
363
+ }
364
+
365
+ if (oauthMap.isNotEmpty()) {
366
+ resultMap["oauthInfo"] = oauthMap
367
+ }
368
+ }
369
+
370
+ return resultMap
371
+ }
372
+
373
+ fun convertToTextConfigsMap(context: Context, mapData: Map<String, Any>): Map<String, Array<TextConfig?>> {
374
+ val resultMap = mutableMapOf<String, Array<TextConfig?>>()
375
+
376
+ for ((key, value) in mapData) {
377
+ when (value) {
378
+ is List<*> -> {
379
+ val textConfigs = mutableListOf<TextConfig?>()
380
+ for (item in value) {
381
+ val textConfig = convertToTextConfig(context, item)
382
+ textConfigs.add(textConfig)
383
+ }
384
+ resultMap[key] = textConfigs.toTypedArray()
385
+ }
386
+ is Array<*> -> {
387
+ val textConfigs = mutableListOf<TextConfig?>()
388
+ for (item in value) {
389
+ val textConfig = convertToTextConfig(context, item)
390
+ textConfigs.add(textConfig)
391
+ }
392
+ resultMap[key] = textConfigs.toTypedArray()
393
+ }
394
+ else -> {
395
+ // If it's not a collection, treat it as a single TextConfig
396
+ val textConfig = convertToTextConfig(context, value)
397
+ resultMap[key] = arrayOf(textConfig)
398
+ }
399
+ }
400
+ }
401
+
402
+ return resultMap
403
+ }
404
+
405
+ fun convertToTextConfigMap(context: Context, mapData: Map<String, Any>): Map<String, TextConfig> {
406
+ val resultMap = mutableMapOf<String, TextConfig>()
407
+
408
+ for ((key, value) in mapData) {
409
+ val textConfig = convertToTextConfig(context, value)
410
+ textConfig?.let {
411
+ resultMap[key] = it
412
+ }
413
+ }
414
+
415
+ return resultMap
416
+ }
417
+
418
+ fun convertToIconTextConfigsMap(context: Context, mapData: Map<String, Any>): Map<Resource.IconTextsKey, Array<IconTextConfig?>> {
419
+ val resultMap = mutableMapOf<Resource.IconTextsKey, Array<IconTextConfig?>>()
420
+
421
+ for ((key, value) in mapData) {
422
+ try {
423
+ val enumKey = Resource.IconTextsKey.valueOf(key)
424
+
425
+ when (value) {
426
+ is List<*> -> {
427
+ val iconTextConfigs = mutableListOf<IconTextConfig?>()
428
+ for (item in value) {
429
+ val iconTextConfig = createIconTextConfig(context, item)
430
+ iconTextConfigs.add(iconTextConfig)
431
+ }
432
+ resultMap[enumKey] = iconTextConfigs.toTypedArray()
433
+ }
434
+ is Array<*> -> {
435
+ val iconTextConfigs = mutableListOf<IconTextConfig?>()
436
+ for (item in value) {
437
+ val iconTextConfig = createIconTextConfig(context, item)
438
+ iconTextConfigs.add(iconTextConfig)
439
+ }
440
+ resultMap[enumKey] = iconTextConfigs.toTypedArray()
441
+ }
442
+ else -> {
443
+ val iconTextConfig = createIconTextConfig(context, value)
444
+ resultMap[enumKey] = arrayOf(iconTextConfig)
445
+ }
446
+ }
447
+ } catch (e: Exception) {
448
+ continue
449
+ }
450
+ }
451
+
452
+ return resultMap
453
+ }
454
+
455
+ /**
456
+ * Creates an IconTextConfig using the map data.
457
+ *
458
+ * @param context The Android context.
459
+ * @param data The input data, expected to be a map with the following structure:
460
+ * - "image": A string representing the image URL (optional).
461
+ * - "textConfig": An object that can be converted to a TextConfig instance (optional).
462
+ * If the input is not a map or does not contain these keys, the function will attempt
463
+ * to create a TextConfig from the data directly.
464
+ * @return An IconTextConfig instance if the input data is valid, or null otherwise.
465
+ */
466
+ private fun createIconTextConfig(context: Context, data: Any?): IconTextConfig? {
467
+ if (data == null) return null
468
+
469
+ return when (data) {
470
+ is Map<*, *> -> {
471
+ @Suppress("UNCHECKED_CAST")
472
+ val dataMap = data as Map<String, Any?>
473
+
474
+ val imageUrl = dataMap["image"] as? String
475
+
476
+ val textConfigData = dataMap["textConfig"]
477
+ val textConfig = if (textConfigData != null) {
478
+ convertToTextConfig(context, textConfigData)
479
+ } else {
480
+ null
481
+ }
482
+
483
+ try {
484
+ val setter = if (imageUrl != null) {
485
+ RnImageSetter(imageUrl)
486
+ } else {
487
+ null
488
+ }
489
+
490
+ IconTextConfig(setter, textConfig)
491
+ } catch (e: Exception) {
492
+ null
493
+ }
494
+ }
495
+ else -> {
496
+ try {
497
+ val textConfig = convertToTextConfig(context, data)
498
+ IconTextConfig(null, textConfig)
499
+ } catch (e: Exception) {
500
+ null
501
+ }
502
+ }
503
+ }
504
+ }
505
+
506
+ private fun convertToTextConfig(context: Context, data: Any?): TextConfig? {
507
+ if (data == null) return null
508
+
509
+ return when (data) {
510
+ is Map<*, *> -> {
511
+ @Suppress("UNCHECKED_CAST")
512
+ val dataMap = data as Map<String, Any?>
513
+
514
+ val text = dataMap["text"] as? String
515
+ val gradientColors = convertToGradientColors(dataMap["gradientColors"])
516
+ val font = dataMap["font"] as? String
517
+ val textColor = dataMap["textColor"] as? String
518
+
519
+ val textConfig = TextConfig(text, gradientColors, getTypeface(context, font))
520
+ textConfig.textColor = getColor(textColor)
521
+ textConfig
522
+ }
523
+ is String -> {
524
+ TextConfig(data, null, null)
525
+ }
526
+ else -> {
527
+ try {
528
+ val stringValue = data.toString()
529
+ if (stringValue.isNotEmpty() && stringValue != "null") {
530
+ TextConfig(stringValue, null, null)
531
+ } else {
532
+ null
533
+ }
534
+ } catch (e: Exception) {
535
+ null
536
+ }
537
+ }
538
+ }
539
+ }
540
+
541
+ private fun convertToGradientColors(colorsData: Any?): IntArray? {
542
+ if (colorsData == null) return null
543
+
544
+ return when (colorsData) {
545
+ is List<*> -> {
546
+ colorsData.mapNotNull { colorStr ->
547
+ try {
548
+ getColor(colorStr as? String)
549
+ } catch (e: Exception) {
550
+ null
551
+ }
552
+ }.toIntArray()
553
+ }
554
+ is Array<*> -> {
555
+ colorsData.mapNotNull { colorStr ->
556
+ try {
557
+ getColor(colorStr as? String)
558
+ } catch (e: Exception) {
559
+ null
560
+ }
561
+ }.toIntArray()
562
+ }
563
+ else -> null
564
+ }
565
+ }
566
+
567
+ private fun getColor(colorStr: String?): Int {
568
+ return try {
569
+ if (TextUtils.isEmpty(colorStr)) {
570
+ 0
571
+ } else Color.parseColor(colorStr)
572
+ } catch (e: Exception) {
573
+ 0
574
+ }
575
+ }
576
+
577
+ private fun getTypeface(context: Context, fontFamilyName: String?): Typeface? {
578
+ if (TextUtils.isEmpty(fontFamilyName)) {
579
+ return null
580
+ }
581
+
582
+ if (typefaceMap[fontFamilyName] != null) {
583
+ return typefaceMap[fontFamilyName]
584
+ }
585
+
586
+ val fileExtensions = arrayOf(".ttf", ".otf")
587
+ val fontsAssetPath = "fonts/"
588
+
589
+ for (fileExtension in fileExtensions) {
590
+ try {
591
+ val fileName = fontsAssetPath + fontFamilyName!! + fileExtension
592
+ val typeface = Typeface.createFromAsset(context.assets, fileName)
593
+ typefaceMap[fontFamilyName] = typeface
594
+ return typeface
595
+ } catch (e: Exception) {
596
+ // If the typeface asset does not exist, try another extension.
597
+ continue
598
+ }
599
+ }
600
+ return null
601
+ }
602
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Copyright 2025 Circle Internet Group, Inc. All rights reserved.
3
+ *
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+ package com.circlefin.programmablewalletrnsdk.models
19
+
20
+ import expo.modules.kotlin.records.Field
21
+ import expo.modules.kotlin.records.Record
22
+
23
+ /**
24
+ * Record for settings management configuration
25
+ */
26
+ class SettingsManagementRecord : Record {
27
+ @Field
28
+ val enableBiometricsPin: Boolean = false
29
+ }
30
+
31
+ /**
32
+ * Record for wallet SDK configuration
33
+ */
34
+ class ConfigurationRecord : Record {
35
+ @Field
36
+ val endpoint: String? = null
37
+
38
+ @Field
39
+ val appId: String? = null
40
+
41
+ @Field
42
+ val settingsManagement: SettingsManagementRecord? = null
43
+ }