@octopus-community/react-native 1.0.0 → 1.0.3

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 (27) hide show
  1. package/README.md +308 -3
  2. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusReactNativeSdkModule.kt +82 -0
  3. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSDKInitializer.kt +149 -1
  4. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusThemeConfig.kt +22 -0
  5. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusThemeManager.kt +11 -0
  6. package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIActivity.kt +234 -38
  7. package/ios/OctopusColorUtility.swift +44 -0
  8. package/ios/OctopusReactNativeSdk.mm +8 -0
  9. package/ios/OctopusReactNativeSdk.swift +52 -28
  10. package/ios/OctopusSDKInitializer.swift +136 -6
  11. package/ios/OctopusUIManager.swift +138 -7
  12. package/lib/module/initialize.js +54 -1
  13. package/lib/module/initialize.js.map +1 -1
  14. package/lib/module/internals/colorSchemeManager.js +105 -0
  15. package/lib/module/internals/colorSchemeManager.js.map +1 -0
  16. package/lib/module/internals/fontParser.js +48 -0
  17. package/lib/module/internals/fontParser.js.map +1 -0
  18. package/lib/typescript/src/initialize.d.ts +92 -0
  19. package/lib/typescript/src/initialize.d.ts.map +1 -1
  20. package/lib/typescript/src/internals/colorSchemeManager.d.ts +39 -0
  21. package/lib/typescript/src/internals/colorSchemeManager.d.ts.map +1 -0
  22. package/lib/typescript/src/internals/fontParser.d.ts +18 -0
  23. package/lib/typescript/src/internals/fontParser.d.ts.map +1 -0
  24. package/package.json +1 -1
  25. package/src/initialize.ts +131 -1
  26. package/src/internals/colorSchemeManager.ts +113 -0
  27. package/src/internals/fontParser.ts +64 -0
package/README.md CHANGED
@@ -49,7 +49,18 @@ Due to a bug in XCode 15, you might need to set `ENABLE_USER_SCRIPT_SANDBOXING`
49
49
 
50
50
  ### Expo
51
51
 
52
- An expo plugin will be available shortly.
52
+ Configure `use_frameworks` (static or dynamic) with `expo-build-properties`:
53
+
54
+ ```json
55
+ [
56
+ "expo-build-properties",
57
+ {
58
+ "ios": {
59
+ "useFrameworks": "static"
60
+ }
61
+ }
62
+ ]
63
+ ```
53
64
 
54
65
  ## Usage
55
66
 
@@ -89,12 +100,23 @@ import {
89
100
  closeUI,
90
101
  } from '@octopus-community/react-native';
91
102
 
92
- // Initialize with SSO mode
103
+ // Initialize with SSO mode and custom theme
93
104
  await initialize({
94
105
  apiKey: 'YOUR_OCTOPUS_API_KEY',
95
106
  connectionMode: {
96
107
  type: 'sso',
97
108
  appManagedFields: ['username', 'profilePicture', 'biography']
109
+ },
110
+ theme: {
111
+ colors: {
112
+ primary: '#FF6B35', // Your brand's primary color
113
+ primaryLowContrast: '#FF8C69', // Lighter variation
114
+ primaryHighContrast: '#CC4A1A', // Darker variation
115
+ onPrimary: '#FFFFFF', // Text color on primary background
116
+ },
117
+ logo: {
118
+ image: Image.resolveAssetSource(require('./assets/logo.png')), // Your custom logo
119
+ },
98
120
  }
99
121
  });
100
122
 
@@ -144,13 +166,296 @@ Show the Octopus home screen with the [`openUI()`](./docs/api/functions/openUI.m
144
166
 
145
167
  Future versions of this React Native module will let you show the UI in your React components. Please reach out if you need this prioritized.
146
168
 
169
+ ### Theme Customization
170
+
171
+ The Octopus SDK provides comprehensive theming capabilities to match your app's branding. You can customize colors, fonts, and logo, with full support for both light and dark modes.
172
+
173
+ #### Basic Theme Setup
174
+
175
+ ```ts
176
+ import { Image } from 'react-native';
177
+
178
+ await initialize({
179
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
180
+ connectionMode: { type: 'octopus' },
181
+ theme: {
182
+ colors: {
183
+ primary: '#FF6B35', // Main brand color
184
+ primaryLowContrast: '#FF8C69', // Lighter variation of primary
185
+ primaryHighContrast: '#CC4A1A', // Darker variation for high contrast
186
+ onPrimary: '#FFFFFF', // Text color on primary background
187
+ },
188
+ logo: {
189
+ image: Image.resolveAssetSource(require('./assets/logo.png')),
190
+ },
191
+ }
192
+ });
193
+ ```
194
+
195
+ #### Dark/Light Mode Management
196
+
197
+ The SDK automatically handles system appearance changes, but you can also force specific modes:
198
+
199
+ ##### System Mode (Default)
200
+ ```ts
201
+ // The SDK automatically follows the system appearance
202
+ // No additional configuration needed
203
+ await initialize({
204
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
205
+ connectionMode: { type: 'octopus' },
206
+ theme: { /* your theme */ }
207
+ });
208
+ ```
209
+
210
+ ##### Forced Light Mode
211
+ ```ts
212
+ import { Appearance } from 'react-native';
213
+
214
+ // Force light mode for your entire app
215
+ Appearance.setColorScheme('light');
216
+
217
+ await initialize({
218
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
219
+ connectionMode: { type: 'octopus' },
220
+ theme: { /* your theme */ }
221
+ });
222
+ ```
223
+
224
+ ##### Forced Dark Mode
225
+ ```ts
226
+ import { Appearance } from 'react-native';
227
+
228
+ // Force dark mode for your entire app
229
+ Appearance.setColorScheme('dark');
230
+
231
+ await initialize({
232
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
233
+ connectionMode: { type: 'octopus' },
234
+ theme: { /* your theme */ }
235
+ });
236
+ ```
237
+
238
+ #### Dual-Mode Color Themes
239
+
240
+ For enhanced theming, you can provide separate color sets for light and dark modes:
241
+
242
+ ```ts
243
+ await initialize({
244
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
245
+ connectionMode: { type: 'octopus' },
246
+ theme: {
247
+ colors: {
248
+ light: {
249
+ primary: '#3B82F6', // Blue for light mode
250
+ primaryLowContrast: '#60A5FA',
251
+ primaryHighContrast: '#1D4ED8',
252
+ onPrimary: '#FFFFFF',
253
+ },
254
+ dark: {
255
+ primary: '#60A5FA', // Lighter blue for dark mode
256
+ primaryLowContrast: '#93C5FD',
257
+ primaryHighContrast: '#3B82F6',
258
+ onPrimary: '#000000',
259
+ },
260
+ },
261
+ }
262
+ });
263
+ ```
264
+
265
+ #### Font Customization
266
+
267
+ Customize typography with text styles and font sizes:
268
+
269
+ ```ts
270
+ await initialize({
271
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
272
+ connectionMode: { type: 'octopus' },
273
+ theme: {
274
+ fonts: {
275
+ textStyles: {
276
+ title1: {
277
+ fontType: 'serif', // serif, monospace, or default
278
+ fontSize: { size: 28 }
279
+ },
280
+ title2: {
281
+ fontType: 'serif',
282
+ fontSize: { size: 22 }
283
+ },
284
+ body1: {
285
+ fontType: 'default', // Uses system default
286
+ fontSize: { size: 16 }
287
+ },
288
+ body2: {
289
+ fontSize: { size: 14 } // Only size, uses default font type
290
+ },
291
+ caption1: {
292
+ fontType: 'monospace',
293
+ fontSize: { size: 12 }
294
+ },
295
+ caption2: {
296
+ fontSize: { size: 10 }
297
+ },
298
+ }
299
+ }
300
+ }
301
+ });
302
+ ```
303
+
304
+ #### Theme Application
305
+
306
+ Themes are applied when the Octopus UI is opened. The SDK automatically detects the current system appearance (light/dark mode) and applies the appropriate theme configuration.
307
+
308
+ #### Complete Theme Example
309
+
310
+ Here's a comprehensive example showing all theming options:
311
+
312
+ ```ts
313
+ import { Image, Appearance } from 'react-native';
314
+ import { initialize } from '@octopus-community/react-native';
315
+
316
+ // Force dark mode (optional)
317
+ Appearance.setColorScheme('dark');
318
+
319
+ await initialize({
320
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
321
+ connectionMode: {
322
+ type: 'sso',
323
+ appManagedFields: ['username', 'profilePicture']
324
+ },
325
+ theme: {
326
+ // Dual-mode colors
327
+ colors: {
328
+ light: {
329
+ primary: '#8B5CF6',
330
+ primaryLowContrast: '#A78BFA',
331
+ primaryHighContrast: '#7C3AED',
332
+ onPrimary: '#FFFFFF',
333
+ },
334
+ dark: {
335
+ primary: '#A78BFA',
336
+ primaryLowContrast: '#C4B5FD',
337
+ primaryHighContrast: '#8B5CF6',
338
+ onPrimary: '#000000',
339
+ },
340
+ },
341
+ // Custom fonts
342
+ fonts: {
343
+ textStyles: {
344
+ title1: { fontType: 'serif', fontSize: { size: 28 } },
345
+ title2: { fontType: 'serif', fontSize: { size: 22 } },
346
+ body1: { fontSize: { size: 16 } },
347
+ body2: { fontSize: { size: 14 } },
348
+ caption1: { fontType: 'monospace', fontSize: { size: 12 } },
349
+ caption2: { fontSize: { size: 10 } },
350
+ }
351
+ },
352
+ // Custom logo
353
+ logo: {
354
+ image: Image.resolveAssetSource(require('./assets/logo.png')),
355
+ },
356
+ }
357
+ });
358
+ ```
359
+
360
+ #### Dynamic Theme Switching
361
+
362
+ To change themes in your app, you need to re-initialize the SDK with the new theme configuration:
363
+
364
+ ```ts
365
+ // Example: Switch between different theme sets
366
+ const switchToGreenTheme = async () => {
367
+ await initialize({
368
+ apiKey: 'YOUR_OCTOPUS_API_KEY',
369
+ connectionMode: { type: 'sso', appManagedFields: ['username'] },
370
+ theme: {
371
+ colors: {
372
+ light: {
373
+ primary: '#10B981',
374
+ primaryLowContrast: '#34D399',
375
+ primaryHighContrast: '#059669',
376
+ onPrimary: '#FFFFFF',
377
+ },
378
+ dark: {
379
+ primary: '#34D399',
380
+ primaryLowContrast: '#6EE7B7',
381
+ primaryHighContrast: '#10B981',
382
+ onPrimary: '#000000',
383
+ },
384
+ }
385
+ }
386
+ });
387
+ };
388
+ ```
389
+
390
+ #### Theme Configuration Reference
391
+
392
+ **Color Properties:**
393
+ - `primary`: Main brand color (hex format: `#FF6B35` or `FF6B35`)
394
+ - `primaryLowContrast`: Lighter variation for subtle elements
395
+ - `primaryHighContrast`: Darker variation for high contrast needs
396
+ - `onPrimary`: Text color displayed over primary background
397
+
398
+ **Font Types:**
399
+ - `default`: System default font
400
+ - `serif`: Serif font family
401
+ - `monospace`: Monospace font family
402
+
403
+ **Text Styles:**
404
+ - `title1`: Large titles (default: 28pt)
405
+ - `title2`: Medium titles (default: 22pt)
406
+ - `body1`: Primary body text (default: 16pt)
407
+ - `body2`: Secondary body text (default: 14pt)
408
+ - `caption1`: Small captions (default: 12pt)
409
+ - `caption2`: Extra small captions (default: 10pt)
410
+
411
+
412
+
413
+ **Supported Formats:**
414
+ - **Colors**: 3-digit (`#F63`), 6-digit (`#FF6633`), 8-digit (`#FF6633FF`) hex codes
415
+ - **Images**: Use `Image.resolveAssetSource(require('./path/to/image.png'))` for bundled assets
416
+ - **Fonts**: Choose between `serif`, `monospace`, or `default` font types
417
+
418
+ **Platform Behavior:**
419
+ - **iOS**: Uses adaptive colors that automatically respond to system appearance changes
420
+ - **Android**: Theme is applied when the UI opens and reflects the current system appearance
421
+ - **Theme Changes**: Require re-initializing the SDK with new theme configuration
422
+ - **System Mode**: Automatically follows device light/dark mode settings
423
+ - **Forced Mode**: Use `Appearance.setColorScheme('light'|'dark')` to override system settings
424
+
425
+ **Note**: All theme properties are optional. If not provided, the default Octopus theme will be used.
426
+
427
+ For more detailed theming information and advanced customization options, see the [Octopus Community iOS SDK theming documentation](https://doc.octopuscommunity.com/SDK/sso/ios#modify-the-theme).
428
+
147
429
  ### API docs
148
430
 
149
431
  For details about the Typescript API, head to the [API docs](./docs/api/README.md).
150
432
 
433
+ #### Key Theming Functions
434
+
435
+ - **`initialize(params)`**: Initialize the SDK with theme configuration
436
+ - **`Appearance.setColorScheme(mode)`**: Force light/dark mode (React Native API)
437
+ - **`openUI()`**: Open the Octopus UI with the current theme applied
438
+
151
439
  ### Example app
152
440
 
153
- Take a look at our [example app](./example/src/App.tsx).
441
+ Take a look at our [example app](./example/src/App.tsx) which demonstrates:
442
+
443
+ - **Complete theming system** with multiple color themes and font customizations
444
+ - **Dark/Light mode management** with system detection and forced modes
445
+ - **Dual-mode color themes** with separate light and dark color sets
446
+ - **Font customization** with different font types (serif, monospace, default) and sizes
447
+ - **Theme switching** by re-initializing the SDK with new configurations
448
+ - **Logo customization** with bundled image assets
449
+ - **Interactive theme controls** to test different configurations
450
+
451
+ The example app includes a comprehensive theme selector interface that lets you:
452
+ - Switch between system, light, and dark modes
453
+ - Choose from multiple pre-defined color themes (Theme 1, 2, 3, or None)
454
+ - Customize font types (default, serif, monospace) and sizes (small, default, large)
455
+ - Toggle custom logo display
456
+ - See theme changes applied when the UI reopens
457
+
458
+ This is the perfect starting point to understand how to implement theming in your own React Native app.
154
459
 
155
460
  ## Troubleshooting
156
461
 
@@ -63,6 +63,88 @@ class OctopusReactNativeSdkModule(reactContext: ReactApplicationContext) :
63
63
  eventEmitter.removeListeners(count)
64
64
  }
65
65
 
66
+ @ReactMethod
67
+ fun updateColorScheme(colorScheme: String?, promise: Promise) {
68
+ // Update the theme config with the new color scheme
69
+ val currentThemeConfig = OctopusThemeManager.getThemeConfig()
70
+ if (currentThemeConfig != null) {
71
+ val updatedThemeConfig = currentThemeConfig.copy(colorScheme = colorScheme)
72
+ OctopusThemeManager.setThemeConfig(updatedThemeConfig)
73
+ }
74
+ promise.resolve(null)
75
+ }
76
+
77
+ @ReactMethod
78
+ fun updateTheme(themeOptions: ReadableMap, promise: Promise) {
79
+ try {
80
+ // Parse theme using the standard initializer
81
+ val themeConfig = sdkInitializer.parseThemeConfig(themeOptions)
82
+ OctopusThemeManager.setThemeConfig(themeConfig)
83
+ promise.resolve(null)
84
+ } catch (e: Exception) {
85
+ promise.reject("UPDATE_THEME_ERROR", "Failed to update theme", e)
86
+ }
87
+ }
88
+
89
+ private fun parseColor(colorString: String?): String? {
90
+ if (colorString == null) return null
91
+
92
+ return try {
93
+ // Validate that the color string is a valid hex color
94
+ android.graphics.Color.parseColor(colorString)
95
+ // Return the original string if parsing succeeds
96
+ colorString
97
+ } catch (e: IllegalArgumentException) {
98
+ // Invalid color format - return null to skip this color
99
+ null
100
+ }
101
+ }
102
+
103
+ private fun parseFontsConfig(fontsMap: ReadableMap): OctopusFontsConfig? {
104
+ // Use pre-processed configuration from TypeScript layer
105
+ val parsedConfig = fontsMap.getMap("parsedConfig")
106
+ if (parsedConfig != null) {
107
+ return parsePreProcessedFontsConfig(parsedConfig)
108
+ }
109
+
110
+ return null
111
+ }
112
+
113
+ private fun parsePreProcessedFontsConfig(parsedConfig: ReadableMap): OctopusFontsConfig? {
114
+ val textStylesMap = parsedConfig.getMap("textStyles")
115
+ val textStyles = mutableMapOf<String, OctopusTextStyleConfig>()
116
+
117
+ // Parse pre-processed font configuration from TypeScript layer
118
+ textStylesMap?.let { textStylesMap ->
119
+ val textStyleKeys = arrayOf("title1", "title2", "body1", "body2", "caption1", "caption2")
120
+
121
+ textStyleKeys.forEach { key ->
122
+ val textStyleMap = textStylesMap.getMap(key)
123
+ textStyleMap?.let { style ->
124
+ val fontType = style.getString("fontType")
125
+ val fontSize = if (style.hasKey("fontSize")) style.getDouble("fontSize") else Double.NaN
126
+
127
+ if (fontType != null || (!fontSize.isNaN() && fontSize > 0)) {
128
+ textStyles[key] = OctopusTextStyleConfig(
129
+ fontType = fontType,
130
+ fontSize = if (fontSize.isNaN() || fontSize <= 0) null else fontSize
131
+ )
132
+ }
133
+ }
134
+ }
135
+ }
136
+
137
+ // Only create fonts config if we have text styles
138
+ if (textStyles.isNotEmpty()) {
139
+ return OctopusFontsConfig(
140
+ textStyles = textStyles
141
+ )
142
+ }
143
+
144
+ return null
145
+ }
146
+
147
+
66
148
  companion object {
67
149
  const val NAME = "OctopusReactNativeSdk"
68
150
  }
@@ -5,6 +5,7 @@ import com.facebook.react.bridge.ReactApplicationContext
5
5
  import com.facebook.react.bridge.ReadableMap
6
6
  import com.octopuscommunity.sdk.OctopusSDK
7
7
  import com.octopuscommunity.sdk.domain.model.ConnectionMode
8
+ import android.graphics.Color
8
9
 
9
10
  class OctopusSDKInitializer {
10
11
 
@@ -17,6 +18,11 @@ class OctopusSDKInitializer {
17
18
 
18
19
  try {
19
20
  val connectionMode = parseConnectionMode(options)
21
+
22
+ // Store theme configuration for later use in UI
23
+ val themeConfig = parseThemeConfig(options)
24
+ OctopusThemeManager.setThemeConfig(themeConfig)
25
+
20
26
  OctopusSDK.initialize(
21
27
  context = context,
22
28
  apiKey = apiKey,
@@ -29,6 +35,148 @@ class OctopusSDKInitializer {
29
35
  promise.reject("INITIALIZE_ERROR", "Failed to initialize Octopus SDK", e)
30
36
  }
31
37
  }
38
+
39
+ fun parseThemeConfig(options: ReadableMap): OctopusThemeConfig? {
40
+
41
+ val themeMap = options.getMap("theme")
42
+ val colorScheme = options.getString("colorScheme") // Get colorScheme from React Native
43
+
44
+ var primaryColor: String? = null
45
+ var primaryLowContrastColor: String? = null
46
+ var primaryHighContrastColor: String? = null
47
+ var onPrimaryColor: String? = null
48
+ var logoSource: ReadableMap? = null
49
+ var fontsConfig: OctopusFontsConfig? = null
50
+
51
+ // Parse colors from theme if available
52
+ themeMap?.let { theme ->
53
+ val colorsMap = theme.getMap("colors")
54
+ colorsMap?.let { colors ->
55
+ // Check if this is a dual-mode theme (has light and dark properties)
56
+ val lightColors = colors.getMap("light")
57
+ val darkColors = colors.getMap("dark")
58
+
59
+ if (lightColors != null && darkColors != null) {
60
+ // Dual-mode theme - select colors based on current color scheme
61
+ val selectedColors = if (colorScheme == "dark") darkColors else lightColors
62
+ primaryColor = parseColor(if (selectedColors.hasKey("primary")) selectedColors.getString("primary") else null)
63
+ primaryLowContrastColor = parseColor(if (selectedColors.hasKey("primaryLowContrast")) selectedColors.getString("primaryLowContrast") else null)
64
+ primaryHighContrastColor = parseColor(if (selectedColors.hasKey("primaryHighContrast")) selectedColors.getString("primaryHighContrast") else null)
65
+ onPrimaryColor = parseColor(if (selectedColors.hasKey("onPrimary")) selectedColors.getString("onPrimary") else null)
66
+ } else {
67
+ // Single-mode theme (backward compatibility)
68
+ primaryColor = parseColor(if (colors.hasKey("primary")) colors.getString("primary") else null)
69
+ primaryLowContrastColor = parseColor(if (colors.hasKey("primaryLowContrast")) colors.getString("primaryLowContrast") else null)
70
+ primaryHighContrastColor = parseColor(if (colors.hasKey("primaryHighContrast")) colors.getString("primaryHighContrast") else null)
71
+ onPrimaryColor = parseColor(if (colors.hasKey("onPrimary")) colors.getString("onPrimary") else null)
72
+ }
73
+ }
74
+
75
+ // Handle logo from theme
76
+ val logoMap = theme.getMap("logo")
77
+ logoMap?.let { logo ->
78
+ val imageSource = logo.getMap("image")
79
+ if (imageSource != null) {
80
+ logoSource = imageSource
81
+ }
82
+ }
83
+
84
+ // Handle fonts from theme - use pre-processed configuration from TypeScript layer
85
+ val fontsMap = theme.getMap("fonts")
86
+ fontsMap?.let { fonts ->
87
+ val parsedConfig = fonts.getMap("parsedConfig")
88
+ if (parsedConfig != null) {
89
+ fontsConfig = parsePreProcessedFontsConfig(parsedConfig)
90
+ }
91
+ }
92
+ }
93
+
94
+ // Handle logo at root level (for backward compatibility)
95
+ if (logoSource == null) {
96
+ val rootLogoMap = options.getMap("logo")
97
+ rootLogoMap?.let { logo ->
98
+ val imageSource = logo.getMap("image")
99
+ if (imageSource != null) {
100
+ logoSource = imageSource
101
+ }
102
+ }
103
+ }
104
+
105
+ // Only create theme config if we have at least one customization
106
+ if (primaryColor != null || logoSource != null || colorScheme != null || fontsConfig != null) {
107
+ return OctopusThemeConfig(
108
+ primaryColor = primaryColor,
109
+ primaryLowContrastColor = primaryLowContrastColor,
110
+ primaryHighContrastColor = primaryHighContrastColor,
111
+ onPrimaryColor = onPrimaryColor,
112
+ logoSource = logoSource,
113
+ colorScheme = colorScheme,
114
+ fonts = fontsConfig
115
+ )
116
+ }
117
+
118
+ return null
119
+ }
120
+
121
+ private fun parsePreProcessedFontsConfig(parsedConfig: ReadableMap): OctopusFontsConfig? {
122
+ val textStylesMap = parsedConfig.getMap("textStyles")
123
+ val textStyles = mutableMapOf<String, OctopusTextStyleConfig>()
124
+
125
+ // Parse pre-processed font configuration from TypeScript layer
126
+ textStylesMap?.let { textStylesMap ->
127
+ val textStyleKeys = arrayOf("title1", "title2", "body1", "body2", "caption1", "caption2")
128
+
129
+ textStyleKeys.forEach { key ->
130
+ val textStyleMap = textStylesMap.getMap(key)
131
+ textStyleMap?.let { style ->
132
+ val fontType = style.getString("fontType")
133
+ val fontSize = if (style.hasKey("fontSize")) style.getDouble("fontSize") else Double.NaN
134
+
135
+ if (fontType != null || (!fontSize.isNaN() && fontSize > 0)) {
136
+ textStyles[key] = OctopusTextStyleConfig(
137
+ fontType = fontType,
138
+ fontSize = if (fontSize.isNaN() || fontSize <= 0) null else fontSize
139
+ )
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ // Only create fonts config if we have text styles
146
+ if (textStyles.isNotEmpty()) {
147
+ return OctopusFontsConfig(
148
+ textStyles = textStyles
149
+ )
150
+ }
151
+
152
+ return null
153
+ }
154
+
155
+
156
+ private fun extractResourceNameFromUri(uri: String): String? {
157
+ // Extract resource name from React Native image URI
158
+ // Examples: "logo.png" -> "logo", "images/logo.png" -> "logo"
159
+ return try {
160
+ val fileName = uri.substringAfterLast("/")
161
+ fileName.substringBeforeLast(".")
162
+ } catch (e: Exception) {
163
+ null
164
+ }
165
+ }
166
+
167
+ private fun parseColor(colorString: String?): String? {
168
+ if (colorString == null) return null
169
+
170
+ return try {
171
+ // Validate that the color string is a valid hex color
172
+ Color.parseColor(colorString)
173
+ // Return the original string if parsing succeeds
174
+ colorString
175
+ } catch (e: IllegalArgumentException) {
176
+ // Invalid color format - return null to skip this color
177
+ null
178
+ }
179
+ }
32
180
 
33
181
  private fun parseConnectionMode(options: ReadableMap): ConnectionMode {
34
182
  val connectionModeMap = options.getMap("connectionMode")
@@ -36,7 +184,7 @@ class OctopusSDKInitializer {
36
184
  "sso" -> {
37
185
  ConnectionMode.SSO(
38
186
  appManagedFields = ProfileFieldMapper.fromReactNativeArray(
39
- connectionModeMap.getArray("appManagedFields")
187
+ if (connectionModeMap.hasKey("appManagedFields")) connectionModeMap.getArray("appManagedFields") else null
40
188
  )
41
189
  )
42
190
  }
@@ -0,0 +1,22 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ import com.facebook.react.bridge.ReadableMap
4
+
5
+ data class OctopusTextStyleConfig(
6
+ val fontType: String?, // "serif", "monospace", or "default"
7
+ val fontSize: Double? // Font size in points
8
+ )
9
+
10
+ data class OctopusFontsConfig(
11
+ val textStyles: Map<String, OctopusTextStyleConfig>?
12
+ )
13
+
14
+ data class OctopusThemeConfig(
15
+ val primaryColor: String?,
16
+ val primaryLowContrastColor: String?,
17
+ val primaryHighContrastColor: String?,
18
+ val onPrimaryColor: String?,
19
+ val logoSource: ReadableMap?,
20
+ val colorScheme: String?, // "light" or "dark"
21
+ val fonts: OctopusFontsConfig?
22
+ )
@@ -0,0 +1,11 @@
1
+ package com.octopuscommunity.octopusreactnativesdk
2
+
3
+ object OctopusThemeManager {
4
+ private var themeConfig: OctopusThemeConfig? = null
5
+
6
+ fun setThemeConfig(config: OctopusThemeConfig?) {
7
+ themeConfig = config
8
+ }
9
+
10
+ fun getThemeConfig(): OctopusThemeConfig? = themeConfig
11
+ }