@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
@@ -4,68 +4,264 @@ import android.content.BroadcastReceiver
4
4
  import android.content.Context
5
5
  import android.content.Intent
6
6
  import android.content.IntentFilter
7
+ import android.content.res.Configuration
8
+ import android.graphics.BitmapFactory
7
9
  import android.os.Build
8
10
  import android.os.Bundle
11
+ import android.util.Log
9
12
  import androidx.activity.ComponentActivity
10
13
  import androidx.activity.compose.setContent
11
14
  import androidx.compose.foundation.layout.Box
12
15
  import androidx.compose.foundation.layout.fillMaxSize
13
16
  import androidx.compose.runtime.Composable
17
+ import androidx.compose.runtime.LaunchedEffect
18
+ import androidx.compose.runtime.getValue
19
+ import androidx.compose.runtime.mutableStateOf
20
+ import androidx.compose.runtime.remember
21
+ import androidx.compose.runtime.setValue
14
22
  import androidx.compose.ui.Modifier
23
+ import androidx.compose.ui.graphics.Color
24
+ import androidx.compose.ui.graphics.asImageBitmap
25
+ import androidx.compose.ui.graphics.painter.BitmapPainter
26
+ import androidx.compose.ui.graphics.painter.Painter
27
+ import androidx.compose.ui.platform.LocalContext
15
28
  import androidx.navigation.compose.NavHost
29
+ import androidx.navigation.compose.composable
16
30
  import androidx.navigation.compose.rememberNavController
17
- import com.octopuscommunity.sdk.ui.OctopusDestination
31
+ import com.octopuscommunity.sdk.ui.OctopusDrawablesDefaults
32
+ import com.octopuscommunity.sdk.ui.OctopusTheme
33
+ import com.octopuscommunity.sdk.ui.OctopusTypographyDefaults
34
+ import com.octopuscommunity.sdk.ui.OctopusTypography
35
+ import com.octopuscommunity.sdk.ui.components.OctopusNavigationHandler
36
+ import androidx.compose.ui.text.TextStyle
37
+ import androidx.compose.ui.text.font.FontFamily
38
+ import androidx.compose.ui.text.font.FontWeight
39
+ import androidx.compose.ui.unit.sp
40
+ import com.octopuscommunity.sdk.ui.home.OctopusHomeScreen
18
41
  import com.octopuscommunity.sdk.ui.octopusComposables
42
+ import com.octopuscommunity.sdk.ui.octopusDarkColorScheme
43
+ import com.octopuscommunity.sdk.ui.octopusLightColorScheme
44
+ import kotlinx.coroutines.Dispatchers
45
+ import kotlinx.coroutines.withContext
46
+ import java.net.URL
19
47
 
20
48
  class OctopusUIActivity : ComponentActivity() {
21
- private val closeUIReceiver = object : BroadcastReceiver() {
22
- override fun onReceive(context: Context?, intent: Intent?) {
23
- finish()
49
+ private val closeUIReceiver = object : BroadcastReceiver() {
50
+ override fun onReceive(context: Context?, intent: Intent?) {
51
+ finish()
52
+ }
24
53
  }
25
- }
26
54
 
27
- override fun onCreate(savedInstanceState: Bundle?) {
28
- super.onCreate(savedInstanceState)
29
- registerCloseUIReceiver()
30
- setContent {
31
- OctopusUI()
55
+ override fun onCreate(savedInstanceState: Bundle?) {
56
+ super.onCreate(savedInstanceState)
57
+ registerCloseUIReceiver()
58
+ setContent {
59
+ OctopusUI(onBack = { finish() })
60
+ }
32
61
  }
33
- }
34
62
 
35
- override fun onDestroy() {
36
- super.onDestroy()
37
- unregisterReceiver(closeUIReceiver)
38
- }
63
+ override fun onDestroy() {
64
+ super.onDestroy()
65
+ unregisterReceiver(closeUIReceiver)
66
+ }
39
67
 
40
- private fun registerCloseUIReceiver() {
41
- val intentFilter = IntentFilter(OctopusUIController.CLOSE_UI_ACTION)
42
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
43
- registerReceiver(closeUIReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
44
- } else {
45
- @Suppress("UnspecifiedRegisterReceiverFlag")
46
- registerReceiver(closeUIReceiver, intentFilter)
68
+ private fun registerCloseUIReceiver() {
69
+ val intentFilter = IntentFilter(OctopusUIController.CLOSE_UI_ACTION)
70
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
71
+ registerReceiver(closeUIReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
72
+ } else {
73
+ @Suppress("UnspecifiedRegisterReceiverFlag")
74
+ registerReceiver(closeUIReceiver, intentFilter)
75
+ }
47
76
  }
48
- }
49
77
  }
50
78
 
51
79
  @Composable
52
- private fun OctopusUI() {
53
- Box(modifier = Modifier.fillMaxSize()) {
54
- val navController = rememberNavController()
80
+ private fun OctopusUI(onBack: () -> Unit) {
81
+ val context = LocalContext.current
82
+
83
+ // Get theme config once when UI is created - no need for polling
84
+ val themeConfig = OctopusThemeManager.getThemeConfig()
85
+
86
+ // Function to detect if system is in dark mode
87
+ fun isSystemInDarkTheme(): Boolean {
88
+ return (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
89
+ }
90
+
91
+ // Determine base color scheme based on theme config or system theme
92
+ val baseColorScheme = if (themeConfig?.colorScheme == "dark") {
93
+ octopusDarkColorScheme()
94
+ } else if (themeConfig?.colorScheme == "light") {
95
+ octopusLightColorScheme()
96
+ } else if (isSystemInDarkTheme()) {
97
+ octopusDarkColorScheme()
98
+ } else {
99
+ octopusLightColorScheme()
100
+ }
101
+
102
+ // Apply custom colors if theme config is provided
103
+ val finalColorScheme = if (themeConfig != null) {
104
+ baseColorScheme.copy(
105
+ primary = themeConfig.primaryColor?.let {
106
+ Color(android.graphics.Color.parseColor(it))
107
+ } ?: baseColorScheme.primary,
108
+ primaryLow = themeConfig.primaryLowContrastColor?.let {
109
+ Color(android.graphics.Color.parseColor(it))
110
+ } ?: baseColorScheme.primaryLow,
111
+ primaryHigh = themeConfig.primaryHighContrastColor?.let {
112
+ Color(android.graphics.Color.parseColor(it))
113
+ } ?: baseColorScheme.primaryHigh,
114
+ onPrimary = themeConfig.onPrimaryColor?.let {
115
+ Color(android.graphics.Color.parseColor(it))
116
+ } ?: baseColorScheme.onPrimary
117
+ )
118
+ } else {
119
+ baseColorScheme
120
+ }
121
+
122
+ // Handle logo loading using built-in Android capabilities
123
+ var logoPainter by remember { mutableStateOf<Painter?>(null) }
124
+
125
+ LaunchedEffect(themeConfig) {
126
+ themeConfig?.logoSource?.let { logoSource ->
127
+ val uri = logoSource.getString("uri")
128
+ if (uri != null) {
129
+ logoPainter = loadImageFromUri(uri)
130
+ } else {
131
+ logoPainter = null
132
+ }
133
+ } ?: run {
134
+ // No theme config or no logo source
135
+ logoPainter = null
136
+ }
137
+ }
138
+
139
+ // Create drawables based on theme config
140
+ val drawables = if (logoPainter != null) {
141
+ OctopusDrawablesDefaults.drawables(logo = logoPainter)
142
+ } else {
143
+ OctopusDrawablesDefaults.drawables()
144
+ }
55
145
 
56
- NavHost(
57
- navController = navController,
58
- startDestination = OctopusDestination.Home
146
+ // Create typography based on theme config
147
+ val typography = createCustomTypography(themeConfig)
148
+
149
+ // Apply theme with custom colors, logo, and typography
150
+ OctopusTheme(
151
+ colorScheme = finalColorScheme,
152
+ drawables = drawables,
153
+ typography = typography
59
154
  ) {
60
- octopusComposables(
61
- navController = navController,
62
- onNavigateToLogin = {
63
- OctopusEventEmitter.instance?.emitLoginRequired()
64
- },
65
- onNavigateToProfileEdit = { fieldToEdit ->
66
- OctopusEventEmitter.instance?.emitEditUser(fieldToEdit)
155
+ OctopusUIContent(onBack = onBack)
156
+ }
157
+ }
158
+
159
+ private fun createCustomTypography(themeConfig: OctopusThemeConfig?): OctopusTypography {
160
+ val defaultTypography = OctopusTypographyDefaults.typography()
161
+
162
+ if (themeConfig?.fonts == null) {
163
+ return defaultTypography
164
+ }
165
+
166
+ val fontsConfig = themeConfig.fonts!!
167
+ val textStyles = fontsConfig.textStyles
168
+
169
+ // Create custom typography based on new unified font configuration
170
+ if (textStyles != null && textStyles.isNotEmpty()) {
171
+ return OctopusTypographyDefaults.typography(
172
+ title1 = createTextStyle(textStyles["title1"], defaultTypography.title1),
173
+ title2 = createTextStyle(textStyles["title2"], defaultTypography.title2),
174
+ body1 = createTextStyle(textStyles["body1"], defaultTypography.body1),
175
+ body2 = createTextStyle(textStyles["body2"], defaultTypography.body2),
176
+ caption1 = createTextStyle(textStyles["caption1"], defaultTypography.caption1),
177
+ caption2 = createTextStyle(textStyles["caption2"], defaultTypography.caption2)
178
+ )
179
+ }
180
+
181
+
182
+ return defaultTypography
183
+ }
184
+
185
+ private fun createTextStyle(textStyleConfig: OctopusTextStyleConfig?, defaultStyle: TextStyle): TextStyle {
186
+ if (textStyleConfig == null) {
187
+ return defaultStyle
188
+ }
189
+
190
+ val fontFamily = when (textStyleConfig.fontType) {
191
+ "serif" -> FontFamily.Serif
192
+ "monospace" -> FontFamily.Monospace
193
+ "default" -> FontFamily.Default
194
+ else -> FontFamily.Default
195
+ }
196
+
197
+ val fontSize = textStyleConfig.fontSize?.let {
198
+ // Use points directly as sp (1 point ≈ 1 sp on Android)
199
+ it.sp
200
+ } ?: defaultStyle.fontSize
201
+
202
+ return defaultStyle.copy(
203
+ fontFamily = fontFamily,
204
+ fontSize = fontSize
205
+ )
206
+ }
207
+
208
+
209
+ @Composable
210
+ private fun OctopusUIContent(onBack: () -> Unit) {
211
+ Box(modifier = Modifier.fillMaxSize()) {
212
+ val navController = rememberNavController()
213
+
214
+ NavHost(
215
+ navController = navController,
216
+ startDestination = "OctopusHome"
217
+ ) {
218
+ composable(route = "OctopusHome") {
219
+ OctopusNavigationHandler(
220
+ navigateToLogin = {
221
+ OctopusEventEmitter.instance?.emitLoginRequired()
222
+ },
223
+ navigateToProfileEdit = { fieldToEdit ->
224
+ OctopusEventEmitter.instance?.emitEditUser(fieldToEdit)
225
+ },
226
+ navigateToClientObject = {
227
+ // TODO : Bridge Post react
228
+ }
229
+ ) {
230
+ OctopusHomeScreen(
231
+ navController = navController,
232
+ backIcon = true,
233
+ onBack = onBack
234
+ )
235
+ }
236
+ }
237
+ octopusComposables(
238
+ navController = navController,
239
+ onNavigateToLogin = {
240
+ OctopusEventEmitter.instance?.emitLoginRequired()
241
+ },
242
+ onNavigateToProfileEdit = { fieldToEdit ->
243
+ OctopusEventEmitter.instance?.emitEditUser(fieldToEdit)
244
+ }
245
+ )
67
246
  }
68
- )
69
247
  }
70
- }
71
248
  }
249
+
250
+ private suspend fun loadImageFromUri(uri: String): Painter? {
251
+ return withContext(Dispatchers.IO) {
252
+ try {
253
+ val url = URL(uri)
254
+ val inputStream = url.openStream()
255
+ val bitmap = BitmapFactory.decodeStream(inputStream)
256
+ inputStream.close()
257
+
258
+ if (bitmap != null) {
259
+ BitmapPainter(bitmap.asImageBitmap())
260
+ } else {
261
+ null
262
+ }
263
+ } catch (e: Exception) {
264
+ null
265
+ }
266
+ }
267
+ }
@@ -0,0 +1,44 @@
1
+ import UIKit
2
+
3
+ /// A utility class for creating UIColor instances from hex strings.
4
+ /// This avoids conflicts with other modules that might also extend UIColor.
5
+ internal class OctopusColorUtility {
6
+
7
+ /// Creates a UIColor from a hex string.
8
+ /// - Parameter hex: A hex color string (e.g., "#FF0000", "FF0000", "#FFF", etc.)
9
+ /// - Returns: A UIColor instance, or nil if the hex string is invalid
10
+ static func color(fromHex hex: String) -> UIColor? {
11
+ // Remove any whitespace and convert to lowercase
12
+ let hex = hex.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
13
+
14
+ // Remove # prefix if present
15
+ let cleanHex = hex.hasPrefix("#") ? String(hex.dropFirst()) : hex
16
+
17
+ // Validate hex string contains only valid characters
18
+ guard cleanHex.range(of: "^[0-9a-f]+$", options: .regularExpression) != nil else {
19
+ return nil
20
+ }
21
+
22
+ var int: UInt64 = 0
23
+ Scanner(string: cleanHex).scanHexInt64(&int)
24
+
25
+ let a, r, g, b: UInt64
26
+ switch cleanHex.count {
27
+ case 3: // RGB (12-bit)
28
+ (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
29
+ case 6: // RGB (24-bit)
30
+ (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
31
+ case 8: // ARGB (32-bit)
32
+ (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
33
+ default:
34
+ return nil
35
+ }
36
+
37
+ return UIColor(
38
+ red: CGFloat(r) / 255,
39
+ green: CGFloat(g) / 255,
40
+ blue: CGFloat(b) / 255,
41
+ alpha: CGFloat(a) / 255
42
+ )
43
+ }
44
+ }
@@ -29,6 +29,14 @@ RCT_EXTERN_METHOD(cancelUserTokenRequest:(NSString *)requestId
29
29
  RCT_EXTERN_METHOD(disconnectUser:(RCTPromiseResolveBlock)resolve
30
30
  withRejecter:(RCTPromiseRejectBlock)reject)
31
31
 
32
+ RCT_EXTERN_METHOD(updateColorScheme:(NSString *)colorScheme
33
+ withResolver:(RCTPromiseResolveBlock)resolve
34
+ withRejecter:(RCTPromiseRejectBlock)reject)
35
+
36
+ RCT_EXTERN_METHOD(updateTheme:(NSDictionary *)themeOptions
37
+ withResolver:(RCTPromiseResolveBlock)resolve
38
+ withRejecter:(RCTPromiseRejectBlock)reject)
39
+
32
40
  + (BOOL)requiresMainQueueSetup
33
41
  {
34
42
  return NO;
@@ -5,37 +5,43 @@ import UIKit
5
5
 
6
6
  @objc(OctopusReactNativeSdk)
7
7
  class OctopusReactNativeSdk: RCTEventEmitter {
8
-
8
+
9
9
  // MARK: - Properties
10
-
10
+
11
11
  private var octopusSDK: OctopusSDK?
12
12
  private lazy var uiManager = OctopusUIManager()
13
13
  private lazy var eventManager = OctopusEventManager(eventEmitter: self)
14
14
  private let sdkInitializer = OctopusSDKInitializer()
15
15
  private var ssoAuthenticator: OctopusSSOAuthenticator?
16
-
16
+ private var theme: OctopusTheme?
17
+ private var logoSource: [String: Any]?
18
+ private var fontConfiguration: [String: Any]?
19
+
17
20
  // MARK: - Initialization
18
-
21
+
19
22
  @objc(initialize:withResolver:withRejecter:)
20
23
  func initialize(options: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
21
24
  do {
22
25
  self.octopusSDK = try sdkInitializer.initialize(options: options, eventManager: eventManager)
23
26
  self.ssoAuthenticator = OctopusSSOAuthenticator(octopusSDK: self.octopusSDK!, eventManager: eventManager)
27
+ self.theme = sdkInitializer.parseTheme(from: options)
28
+ self.logoSource = sdkInitializer.getLogoSource(from: options)
29
+ self.fontConfiguration = sdkInitializer.getFontConfiguration(from: options)
24
30
  resolve(nil)
25
31
  } catch {
26
32
  reject("INITIALIZE_ERROR", "Failed to initialize Octopus SDK: \(error.localizedDescription)", error)
27
33
  }
28
34
  }
29
-
35
+
30
36
  // MARK: - User authentication
31
-
37
+
32
38
  @objc(connectUser:withResolver:withRejecter:)
33
39
  func connectUser(params: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
34
40
  guard let authenticator = ssoAuthenticator else {
35
41
  reject("CONNECT_USER_ERROR", "SDK not initialized. Call initialize() first.", nil)
36
42
  return
37
43
  }
38
-
44
+
39
45
  Task {
40
46
  do {
41
47
  try await authenticator.connectUser(params: params)
@@ -44,66 +50,66 @@ class OctopusReactNativeSdk: RCTEventEmitter {
44
50
  reject("CONNECT_USER_ERROR", "Failed to connect user: \(error.localizedDescription)", error)
45
51
  }
46
52
  }
47
-
53
+
48
54
  }
49
-
55
+
50
56
  @objc(completeUserTokenRequest:withToken:withResolver:withRejecter:)
51
57
  func completeUserTokenRequest(requestId: String, token: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
52
58
  guard let authenticator = ssoAuthenticator else {
53
59
  reject("PROVIDE_TOKEN_ERROR", "SDK not initialized", nil)
54
60
  return
55
61
  }
56
-
62
+
57
63
  authenticator.completeTokenRequest(requestId: requestId, token: token)
58
64
  resolve(nil)
59
65
  }
60
-
66
+
61
67
  @objc(cancelUserTokenRequest:withResolver:withRejecter:)
62
68
  func cancelUserTokenRequest(requestId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
63
69
  guard let authenticator = ssoAuthenticator else {
64
70
  reject("PROVIDE_TOKEN_ERROR", "SDK not initialized", nil)
65
71
  return
66
72
  }
67
-
73
+
68
74
  authenticator.cancelTokenRequest(requestId: requestId)
69
75
  resolve(nil)
70
76
  }
71
-
77
+
72
78
  @objc(disconnectUser:withRejecter:)
73
79
  func disconnectUser(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
74
80
  guard let authenticator = ssoAuthenticator else {
75
81
  reject("DISCONNECT_USER_ERROR", "SDK not initialized. Call initialize() first.", nil)
76
82
  return
77
83
  }
78
-
84
+
79
85
  do {
80
86
  try authenticator.disconnectUser()
81
87
  resolve(nil)
82
88
  } catch {
83
89
  reject("DISCONNECT_USER_ERROR", "Failed to disconnect user: \(error.localizedDescription)", error)
84
90
  }
85
-
91
+
86
92
  }
87
-
93
+
88
94
  // MARK: - UI management
89
-
95
+
90
96
  @objc(openUI:withRejecter:)
91
97
  func openUI(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
92
98
  guard let octopus = octopusSDK else {
93
99
  reject("OPEN_UI_ERROR", "SDK not initialized. Call initialize() first.", nil)
94
100
  return
95
101
  }
96
-
102
+
97
103
  DispatchQueue.main.async {
98
104
  do {
99
- try self.uiManager.openUI(octopus: octopus)
105
+ try self.uiManager.openUI(octopus: octopus, theme: self.theme, logoSource: self.logoSource, fontConfiguration: self.fontConfiguration)
100
106
  resolve(nil)
101
107
  } catch {
102
108
  reject("OPEN_UI_ERROR", error.localizedDescription, error)
103
109
  }
104
110
  }
105
111
  }
106
-
112
+
107
113
  @objc(closeUI:withRejecter:)
108
114
  func closeUI(resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
109
115
  DispatchQueue.main.async {
@@ -115,33 +121,51 @@ class OctopusReactNativeSdk: RCTEventEmitter {
115
121
  }
116
122
  }
117
123
  }
118
-
124
+
125
+ @objc(updateColorScheme:withResolver:withRejecter:)
126
+ func updateColorScheme(colorScheme: String?, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
127
+ // iOS uses adaptive colors that automatically respond to system appearance changes
128
+ // No manual updates needed - the theme is applied when UI opens
129
+ resolve(nil)
130
+ }
131
+
132
+ @objc(updateTheme:withResolver:withRejecter:)
133
+ func updateTheme(themeOptions: [String: Any], resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
134
+ do {
135
+ self.theme = sdkInitializer.parseTheme(from: themeOptions)
136
+ resolve(nil)
137
+ } catch {
138
+ reject("UPDATE_THEME_ERROR", "Failed to update theme: \(error.localizedDescription)", error)
139
+ }
140
+ }
141
+
142
+
119
143
  // MARK: - Lifecycle management
120
-
144
+
121
145
  deinit {
122
146
  cleanup()
123
147
  }
124
-
148
+
125
149
  private func cleanup() {
126
150
  uiManager.cleanup()
127
151
  octopusSDK = nil
128
152
  }
129
-
153
+
130
154
  @objc override func invalidate() {
131
155
  cleanup()
132
156
  super.invalidate()
133
157
  }
134
-
158
+
135
159
  // MARK: - RCTEventEmitter overrides
136
-
160
+
137
161
  @objc override func supportedEvents() -> [String]! {
138
162
  return eventManager.supportedEvents()
139
163
  }
140
-
164
+
141
165
  @objc override func startObserving() {
142
166
  eventManager.startObserving()
143
167
  }
144
-
168
+
145
169
  @objc override func stopObserving() {
146
170
  eventManager.stopObserving()
147
171
  }