@octopus-community/react-native 1.0.2 → 1.0.4

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/README.md CHANGED
@@ -168,7 +168,9 @@ Future versions of this React Native module will let you show the UI in your Rea
168
168
 
169
169
  ### Theme Customization
170
170
 
171
- You can customize the Octopus UI to match your app's branding by providing a theme configuration during initialization:
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
172
174
 
173
175
  ```ts
174
176
  import { Image } from 'react-native';
@@ -184,33 +186,276 @@ await initialize({
184
186
  onPrimary: '#FFFFFF', // Text color on primary background
185
187
  },
186
188
  logo: {
187
- // Use Image.resolveAssetSource() for bundled image assets
188
189
  image: Image.resolveAssetSource(require('./assets/logo.png')),
189
190
  },
190
191
  }
191
192
  });
192
193
  ```
193
194
 
194
- **Supported customizations:**
195
- - **Colors**: Primary, low contrast, high contrast, and text colors (iOS & Android)
196
- - **Logo**: Custom logo using bundled images (iOS & Android)
197
- - **Color Format**: Hex strings with or without # prefix (e.g., `#FF6B35` or `FF6B35`)
198
- - **Supported Hex Formats**: 3-digit (`#F63`), 6-digit (`#FF6633`), 8-digit (`#FF6633FF`)
199
- - **Zero Dependencies**: Implementation uses only built-in native APIs
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
200
361
 
201
- **Logo Options:**
202
- - **Bundled Images**: Use `Image.resolveAssetSource(require('./assets/logo.png'))` for local assets
203
- - **Platform Support**: Both iOS and Android support local image resources
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
204
424
 
205
425
  **Note**: All theme properties are optional. If not provided, the default Octopus theme will be used.
206
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
+
207
429
  ### API docs
208
430
 
209
431
  For details about the Typescript API, head to the [API docs](./docs/api/README.md).
210
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
+
211
439
  ### Example app
212
440
 
213
- 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.
214
459
 
215
460
  ## Troubleshooting
216
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
  }
@@ -36,47 +36,123 @@ class OctopusSDKInitializer {
36
36
  }
37
37
  }
38
38
 
39
- private fun parseThemeConfig(options: ReadableMap): OctopusThemeConfig? {
40
- val themeMap = options.getMap("theme") ?: return null
39
+ fun parseThemeConfig(options: ReadableMap): OctopusThemeConfig? {
41
40
 
42
- val colorsMap = themeMap.getMap("colors")
43
- val logoMap = themeMap.getMap("logo")
41
+ val themeMap = options.getMap("theme")
42
+ val colorScheme = options.getString("colorScheme") // Get colorScheme from React Native
44
43
 
45
44
  var primaryColor: String? = null
46
45
  var primaryLowContrastColor: String? = null
47
46
  var primaryHighContrastColor: String? = null
48
47
  var onPrimaryColor: String? = null
49
48
  var logoSource: ReadableMap? = null
49
+ var fontsConfig: OctopusFontsConfig? = null
50
50
 
51
- colorsMap?.let { colors ->
52
- primaryColor = parseColor(colors.getString("primary"))
53
- primaryLowContrastColor = parseColor(colors.getString("primaryLowContrast"))
54
- primaryHighContrastColor = parseColor(colors.getString("primaryHighContrast"))
55
- onPrimaryColor = parseColor(colors.getString("onPrimary"))
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
+ }
56
92
  }
57
93
 
58
- // Handle logo - only support Image.resolveAssetSource() approach
59
- logoMap?.let { logo ->
60
- val imageSource = logo.getMap("image")
61
- if (imageSource != null) {
62
- logoSource = imageSource
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
+ }
63
102
  }
64
103
  }
65
104
 
66
105
  // Only create theme config if we have at least one customization
67
- if (primaryColor != null || logoSource != null) {
106
+ if (primaryColor != null || logoSource != null || colorScheme != null || fontsConfig != null) {
68
107
  return OctopusThemeConfig(
69
108
  primaryColor = primaryColor,
70
109
  primaryLowContrastColor = primaryLowContrastColor,
71
110
  primaryHighContrastColor = primaryHighContrastColor,
72
111
  onPrimaryColor = onPrimaryColor,
73
- logoSource = logoSource
112
+ logoSource = logoSource,
113
+ colorScheme = colorScheme,
114
+ fonts = fontsConfig
74
115
  )
75
116
  }
76
117
 
77
118
  return null
78
119
  }
79
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
+
80
156
  private fun extractResourceNameFromUri(uri: String): String? {
81
157
  // Extract resource name from React Native image URI
82
158
  // Examples: "logo.png" -> "logo", "images/logo.png" -> "logo"
@@ -108,7 +184,7 @@ class OctopusSDKInitializer {
108
184
  "sso" -> {
109
185
  ConnectionMode.SSO(
110
186
  appManagedFields = ProfileFieldMapper.fromReactNativeArray(
111
- connectionModeMap.getArray("appManagedFields")
187
+ if (connectionModeMap.hasKey("appManagedFields")) connectionModeMap.getArray("appManagedFields") else null
112
188
  )
113
189
  )
114
190
  }
@@ -2,10 +2,21 @@ package com.octopuscommunity.octopusreactnativesdk
2
2
 
3
3
  import com.facebook.react.bridge.ReadableMap
4
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
+
5
14
  data class OctopusThemeConfig(
6
15
  val primaryColor: String?,
7
16
  val primaryLowContrastColor: String?,
8
17
  val primaryHighContrastColor: String?,
9
18
  val onPrimaryColor: String?,
10
- val logoSource: ReadableMap?
19
+ val logoSource: ReadableMap?,
20
+ val colorScheme: String?, // "light" or "dark"
21
+ val fonts: OctopusFontsConfig?
11
22
  )