@octopus-community/react-native 1.0.7 → 1.9.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.
- package/OctopusReactNativeSdk.podspec +1 -1
- package/README.md +40 -35
- package/android/build.gradle +2 -0
- package/android/gradle.properties +2 -2
- package/android/src/main/AndroidManifest.xml +2 -1
- package/android/src/main/AndroidManifestNew.xml +2 -1
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusActivity.kt +56 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusContent.kt +396 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusEventEmitter.kt +22 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusEventSerializer.kt +339 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusReactModule.kt +326 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/{OctopusReactNativeSdkPackage.kt → OctopusReactPackage.kt} +3 -3
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSDKInitializer.kt +53 -9
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSSOAuthenticator.kt +5 -15
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIConfiguration.kt +6 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIConfigurationManager.kt +12 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIController.kt +17 -2
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIViewManager.kt +63 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/ProfileFieldMapper.kt +2 -2
- package/ios/OctopusEventManager.swift +27 -0
- package/ios/OctopusEventSerializer.swift +271 -0
- package/ios/OctopusReactNativeSdk.mm +26 -1
- package/ios/OctopusReactNativeSdk.swift +225 -3
- package/ios/OctopusSDKInitializer.swift +32 -0
- package/ios/OctopusSSOAuthenticator.swift +1 -5
- package/ios/OctopusUIConfiguration.swift +6 -0
- package/ios/OctopusUIManager.swift +134 -10
- package/ios/OctopusUIViewManager.m +7 -0
- package/ios/OctopusUIViewManager.swift +37 -0
- package/lib/module/OctopusUIView.js +39 -0
- package/lib/module/OctopusUIView.js.map +1 -0
- package/lib/module/addHasAccessToCommunityListener.js +33 -0
- package/lib/module/addHasAccessToCommunityListener.js.map +1 -0
- package/lib/module/addNavigateToUrlListener.js +41 -0
- package/lib/module/addNavigateToUrlListener.js.map +1 -0
- package/lib/module/addNotSeenNotificationsCountListener.js +30 -0
- package/lib/module/addNotSeenNotificationsCountListener.js.map +1 -0
- package/lib/module/addSDKEventListener.js +48 -0
- package/lib/module/addSDKEventListener.js.map +1 -0
- package/lib/module/connectUser.js +24 -3
- package/lib/module/connectUser.js.map +1 -1
- package/lib/module/index.js +12 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/initialize.js +13 -12
- package/lib/module/initialize.js.map +1 -1
- package/lib/module/openUI.js +23 -2
- package/lib/module/openUI.js.map +1 -1
- package/lib/module/overrideCommunityAccess.js +36 -0
- package/lib/module/overrideCommunityAccess.js.map +1 -0
- package/lib/module/overrideDefaultLocale.js +75 -0
- package/lib/module/overrideDefaultLocale.js.map +1 -0
- package/lib/module/trackCommunityAccess.js +33 -0
- package/lib/module/trackCommunityAccess.js.map +1 -0
- package/lib/module/trackCustomEvent.js +36 -0
- package/lib/module/trackCustomEvent.js.map +1 -0
- package/lib/module/types/sdkEvents.js +2 -0
- package/lib/module/types/sdkEvents.js.map +1 -0
- package/lib/module/types/urlOpeningStrategy.js +23 -0
- package/lib/module/types/urlOpeningStrategy.js.map +1 -0
- package/lib/module/updateNotSeenNotificationsCount.js +33 -0
- package/lib/module/updateNotSeenNotificationsCount.js.map +1 -0
- package/lib/typescript/src/OctopusUIView.d.ts +32 -0
- package/lib/typescript/src/OctopusUIView.d.ts.map +1 -0
- package/lib/typescript/src/addHasAccessToCommunityListener.d.ts +27 -0
- package/lib/typescript/src/addHasAccessToCommunityListener.d.ts.map +1 -0
- package/lib/typescript/src/addNavigateToUrlListener.d.ts +31 -0
- package/lib/typescript/src/addNavigateToUrlListener.d.ts.map +1 -0
- package/lib/typescript/src/addNotSeenNotificationsCountListener.d.ts +24 -0
- package/lib/typescript/src/addNotSeenNotificationsCountListener.d.ts.map +1 -0
- package/lib/typescript/src/addSDKEventListener.d.ts +43 -0
- package/lib/typescript/src/addSDKEventListener.d.ts.map +1 -0
- package/lib/typescript/src/connectUser.d.ts +24 -8
- package/lib/typescript/src/connectUser.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +13 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/initialize.d.ts +22 -12
- package/lib/typescript/src/initialize.d.ts.map +1 -1
- package/lib/typescript/src/openUI.d.ts +28 -1
- package/lib/typescript/src/openUI.d.ts.map +1 -1
- package/lib/typescript/src/overrideCommunityAccess.d.ts +30 -0
- package/lib/typescript/src/overrideCommunityAccess.d.ts.map +1 -0
- package/lib/typescript/src/overrideDefaultLocale.d.ts +37 -0
- package/lib/typescript/src/overrideDefaultLocale.d.ts.map +1 -0
- package/lib/typescript/src/trackCommunityAccess.d.ts +27 -0
- package/lib/typescript/src/trackCommunityAccess.d.ts.map +1 -0
- package/lib/typescript/src/trackCustomEvent.d.ts +30 -0
- package/lib/typescript/src/trackCustomEvent.d.ts.map +1 -0
- package/lib/typescript/src/types/sdkEvents.d.ts +222 -0
- package/lib/typescript/src/types/sdkEvents.d.ts.map +1 -0
- package/lib/typescript/src/types/urlOpeningStrategy.d.ts +20 -0
- package/lib/typescript/src/types/urlOpeningStrategy.d.ts.map +1 -0
- package/lib/typescript/src/updateNotSeenNotificationsCount.d.ts +27 -0
- package/lib/typescript/src/updateNotSeenNotificationsCount.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/OctopusUIView.tsx +57 -0
- package/src/addHasAccessToCommunityListener.ts +38 -0
- package/src/addNavigateToUrlListener.ts +54 -0
- package/src/addNotSeenNotificationsCountListener.ts +35 -0
- package/src/addSDKEventListener.ts +49 -0
- package/src/connectUser.ts +24 -8
- package/src/index.ts +13 -0
- package/src/initialize.ts +23 -12
- package/src/openUI.ts +32 -2
- package/src/overrideCommunityAccess.ts +33 -0
- package/src/overrideDefaultLocale.ts +88 -0
- package/src/trackCommunityAccess.ts +30 -0
- package/src/trackCustomEvent.ts +36 -0
- package/src/types/sdkEvents.ts +315 -0
- package/src/types/urlOpeningStrategy.ts +20 -0
- package/src/updateNotSeenNotificationsCount.ts +30 -0
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusReactNativeSdkModule.kt +0 -155
- package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIActivity.kt +0 -422
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
package com.octopuscommunity.octopusreactnativesdk
|
|
2
|
-
|
|
3
|
-
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
-
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
-
import com.facebook.react.bridge.ReactMethod
|
|
6
|
-
import com.facebook.react.bridge.Promise
|
|
7
|
-
import com.facebook.react.bridge.ReadableMap
|
|
8
|
-
|
|
9
|
-
class OctopusReactNativeSdkModule(reactContext: ReactApplicationContext) :
|
|
10
|
-
ReactContextBaseJavaModule(reactContext) {
|
|
11
|
-
|
|
12
|
-
private val sdkInitializer = OctopusSDKInitializer()
|
|
13
|
-
private val eventEmitter = OctopusEventEmitter(reactContext)
|
|
14
|
-
private val uiController = OctopusUIController(reactContext)
|
|
15
|
-
private val ssoAuthenticator = OctopusSSOAuthenticator(reactContext, eventEmitter)
|
|
16
|
-
|
|
17
|
-
override fun getName(): String = NAME
|
|
18
|
-
|
|
19
|
-
@ReactMethod
|
|
20
|
-
fun initialize(options: ReadableMap, promise: Promise) {
|
|
21
|
-
sdkInitializer.initialize(reactApplicationContext, options, promise)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
@ReactMethod
|
|
25
|
-
fun openUI(promise: Promise) {
|
|
26
|
-
uiController.openUI(promise)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
@ReactMethod
|
|
30
|
-
fun closeUI(promise: Promise) {
|
|
31
|
-
uiController.closeUI(promise)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
@ReactMethod
|
|
35
|
-
fun connectUser(params: ReadableMap, promise: Promise) {
|
|
36
|
-
ssoAuthenticator.connectUser(params, promise)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
@ReactMethod
|
|
40
|
-
fun disconnectUser(promise: Promise) {
|
|
41
|
-
ssoAuthenticator.disconnectUser(promise)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
@ReactMethod
|
|
45
|
-
fun completeUserTokenRequest(requestId: String, token: String, promise: Promise) {
|
|
46
|
-
ssoAuthenticator.completeTokenRequest(requestId, token)
|
|
47
|
-
promise.resolve(null)
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
@ReactMethod
|
|
51
|
-
fun cancelUserTokenRequest(requestId: String, promise: Promise) {
|
|
52
|
-
ssoAuthenticator.cancelTokenRequest(requestId)
|
|
53
|
-
promise.resolve(null)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@ReactMethod
|
|
57
|
-
fun addListener(eventName: String) {
|
|
58
|
-
eventEmitter.addListener(eventName)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
@ReactMethod
|
|
62
|
-
fun removeListeners(count: Int) {
|
|
63
|
-
eventEmitter.removeListeners(count)
|
|
64
|
-
}
|
|
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
|
-
|
|
148
|
-
companion object {
|
|
149
|
-
const val NAME = "OctopusReactNativeSdk"
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
init {
|
|
153
|
-
OctopusEventEmitter.instance = eventEmitter
|
|
154
|
-
}
|
|
155
|
-
}
|
package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIActivity.kt
DELETED
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
package com.octopuscommunity.octopusreactnativesdk
|
|
2
|
-
|
|
3
|
-
import android.content.BroadcastReceiver
|
|
4
|
-
import android.content.Context
|
|
5
|
-
import android.content.Intent
|
|
6
|
-
import android.content.IntentFilter
|
|
7
|
-
import android.content.res.Configuration
|
|
8
|
-
import android.graphics.BitmapFactory
|
|
9
|
-
import android.os.Build
|
|
10
|
-
import android.os.Bundle
|
|
11
|
-
import android.util.Log
|
|
12
|
-
import androidx.activity.ComponentActivity
|
|
13
|
-
import androidx.activity.compose.setContent
|
|
14
|
-
import androidx.compose.foundation.layout.Box
|
|
15
|
-
import androidx.compose.foundation.layout.fillMaxSize
|
|
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
|
|
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
|
|
28
|
-
import androidx.navigation.compose.NavHost
|
|
29
|
-
import androidx.navigation.compose.composable
|
|
30
|
-
import androidx.navigation.compose.rememberNavController
|
|
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
|
|
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
|
|
47
|
-
import java.util.concurrent.ConcurrentHashMap
|
|
48
|
-
|
|
49
|
-
// Cache for loaded images to avoid reloading on recomposition
|
|
50
|
-
private val imageCache = ConcurrentHashMap<String, Painter?>()
|
|
51
|
-
// Cache for drawable resources list to avoid repeated reflection
|
|
52
|
-
private var cachedDrawableResources: List<String>? = null
|
|
53
|
-
|
|
54
|
-
class OctopusUIActivity : ComponentActivity() {
|
|
55
|
-
private val closeUIReceiver = object : BroadcastReceiver() {
|
|
56
|
-
override fun onReceive(context: Context?, intent: Intent?) {
|
|
57
|
-
finish()
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
override fun onCreate(savedInstanceState: Bundle?) {
|
|
62
|
-
super.onCreate(savedInstanceState)
|
|
63
|
-
registerCloseUIReceiver()
|
|
64
|
-
setContent {
|
|
65
|
-
OctopusUI(onBack = { finish() })
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
override fun onDestroy() {
|
|
70
|
-
super.onDestroy()
|
|
71
|
-
unregisterReceiver(closeUIReceiver)
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private fun registerCloseUIReceiver() {
|
|
75
|
-
val intentFilter = IntentFilter(OctopusUIController.CLOSE_UI_ACTION)
|
|
76
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
77
|
-
registerReceiver(closeUIReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
|
|
78
|
-
} else {
|
|
79
|
-
@Suppress("UnspecifiedRegisterReceiverFlag")
|
|
80
|
-
registerReceiver(closeUIReceiver, intentFilter)
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@Composable
|
|
86
|
-
private fun OctopusUI(onBack: () -> Unit) {
|
|
87
|
-
val context = LocalContext.current
|
|
88
|
-
|
|
89
|
-
// Get theme config once when UI is created - no need for polling
|
|
90
|
-
val themeConfig = OctopusThemeManager.getThemeConfig()
|
|
91
|
-
|
|
92
|
-
// Function to detect if system is in dark mode
|
|
93
|
-
fun isSystemInDarkTheme(): Boolean {
|
|
94
|
-
return (context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Determine base color scheme based on theme config or system theme
|
|
98
|
-
val baseColorScheme = if (themeConfig?.colorScheme == "dark") {
|
|
99
|
-
octopusDarkColorScheme()
|
|
100
|
-
} else if (themeConfig?.colorScheme == "light") {
|
|
101
|
-
octopusLightColorScheme()
|
|
102
|
-
} else if (isSystemInDarkTheme()) {
|
|
103
|
-
octopusDarkColorScheme()
|
|
104
|
-
} else {
|
|
105
|
-
octopusLightColorScheme()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Apply custom colors if theme config is provided
|
|
109
|
-
val finalColorScheme = if (themeConfig != null) {
|
|
110
|
-
baseColorScheme.copy(
|
|
111
|
-
primary = themeConfig.primaryColor?.let {
|
|
112
|
-
Color(android.graphics.Color.parseColor(it))
|
|
113
|
-
} ?: baseColorScheme.primary,
|
|
114
|
-
primaryLow = themeConfig.primaryLowContrastColor?.let {
|
|
115
|
-
Color(android.graphics.Color.parseColor(it))
|
|
116
|
-
} ?: baseColorScheme.primaryLow,
|
|
117
|
-
primaryHigh = themeConfig.primaryHighContrastColor?.let {
|
|
118
|
-
Color(android.graphics.Color.parseColor(it))
|
|
119
|
-
} ?: baseColorScheme.primaryHigh,
|
|
120
|
-
onPrimary = themeConfig.onPrimaryColor?.let {
|
|
121
|
-
Color(android.graphics.Color.parseColor(it))
|
|
122
|
-
} ?: baseColorScheme.onPrimary
|
|
123
|
-
)
|
|
124
|
-
} else {
|
|
125
|
-
baseColorScheme
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Handle logo loading using built-in Android capabilities
|
|
129
|
-
var logoPainter by remember { mutableStateOf<Painter?>(null) }
|
|
130
|
-
|
|
131
|
-
LaunchedEffect(themeConfig) {
|
|
132
|
-
themeConfig?.logoSource?.let { logoSource ->
|
|
133
|
-
val uri = logoSource.getString("uri")
|
|
134
|
-
if (uri != null) {
|
|
135
|
-
logoPainter = loadImageFromUri(uri, context)
|
|
136
|
-
} else {
|
|
137
|
-
logoPainter = null
|
|
138
|
-
}
|
|
139
|
-
} ?: run {
|
|
140
|
-
// No theme config or no logo source
|
|
141
|
-
logoPainter = null
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Create drawables based on theme config
|
|
146
|
-
val drawables = if (logoPainter != null) {
|
|
147
|
-
OctopusDrawablesDefaults.drawables(logo = logoPainter)
|
|
148
|
-
} else {
|
|
149
|
-
OctopusDrawablesDefaults.drawables()
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Create typography based on theme config
|
|
153
|
-
val typography = createCustomTypography(themeConfig)
|
|
154
|
-
|
|
155
|
-
// Apply theme with custom colors, logo, and typography
|
|
156
|
-
OctopusTheme(
|
|
157
|
-
colorScheme = finalColorScheme,
|
|
158
|
-
drawables = drawables,
|
|
159
|
-
typography = typography
|
|
160
|
-
) {
|
|
161
|
-
OctopusUIContent(onBack = onBack)
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private fun createCustomTypography(themeConfig: OctopusThemeConfig?): OctopusTypography {
|
|
166
|
-
val defaultTypography = OctopusTypographyDefaults.typography()
|
|
167
|
-
|
|
168
|
-
if (themeConfig?.fonts == null) {
|
|
169
|
-
return defaultTypography
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
val fontsConfig = themeConfig.fonts!!
|
|
173
|
-
val textStyles = fontsConfig.textStyles
|
|
174
|
-
|
|
175
|
-
// Create custom typography based on new unified font configuration
|
|
176
|
-
if (textStyles != null && textStyles.isNotEmpty()) {
|
|
177
|
-
return OctopusTypographyDefaults.typography(
|
|
178
|
-
title1 = createTextStyle(textStyles["title1"], defaultTypography.title1),
|
|
179
|
-
title2 = createTextStyle(textStyles["title2"], defaultTypography.title2),
|
|
180
|
-
body1 = createTextStyle(textStyles["body1"], defaultTypography.body1),
|
|
181
|
-
body2 = createTextStyle(textStyles["body2"], defaultTypography.body2),
|
|
182
|
-
caption1 = createTextStyle(textStyles["caption1"], defaultTypography.caption1),
|
|
183
|
-
caption2 = createTextStyle(textStyles["caption2"], defaultTypography.caption2)
|
|
184
|
-
)
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return defaultTypography
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
private fun createTextStyle(textStyleConfig: OctopusTextStyleConfig?, defaultStyle: TextStyle): TextStyle {
|
|
192
|
-
if (textStyleConfig == null) {
|
|
193
|
-
return defaultStyle
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
val fontFamily = when (textStyleConfig.fontType) {
|
|
197
|
-
"serif" -> FontFamily.Serif
|
|
198
|
-
"monospace" -> FontFamily.Monospace
|
|
199
|
-
"default" -> FontFamily.Default
|
|
200
|
-
else -> FontFamily.Default
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
val fontSize = textStyleConfig.fontSize?.let {
|
|
204
|
-
// Use points directly as sp (1 point ≈ 1 sp on Android)
|
|
205
|
-
it.sp
|
|
206
|
-
} ?: defaultStyle.fontSize
|
|
207
|
-
|
|
208
|
-
return defaultStyle.copy(
|
|
209
|
-
fontFamily = fontFamily,
|
|
210
|
-
fontSize = fontSize
|
|
211
|
-
)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
@Composable
|
|
216
|
-
private fun OctopusUIContent(onBack: () -> Unit) {
|
|
217
|
-
Box(modifier = Modifier.fillMaxSize()) {
|
|
218
|
-
val navController = rememberNavController()
|
|
219
|
-
|
|
220
|
-
NavHost(
|
|
221
|
-
navController = navController,
|
|
222
|
-
startDestination = "OctopusHome"
|
|
223
|
-
) {
|
|
224
|
-
composable(route = "OctopusHome") {
|
|
225
|
-
OctopusNavigationHandler(
|
|
226
|
-
navigateToLogin = {
|
|
227
|
-
OctopusEventEmitter.instance?.emitLoginRequired()
|
|
228
|
-
},
|
|
229
|
-
navigateToProfileEdit = { fieldToEdit ->
|
|
230
|
-
OctopusEventEmitter.instance?.emitEditUser(fieldToEdit)
|
|
231
|
-
},
|
|
232
|
-
navigateToClientObject = {
|
|
233
|
-
// TODO : Bridge Post react
|
|
234
|
-
}
|
|
235
|
-
) {
|
|
236
|
-
OctopusHomeScreen(
|
|
237
|
-
navController = navController,
|
|
238
|
-
backIcon = true,
|
|
239
|
-
onBack = onBack
|
|
240
|
-
)
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
octopusComposables(
|
|
244
|
-
navController = navController,
|
|
245
|
-
onNavigateToLogin = {
|
|
246
|
-
OctopusEventEmitter.instance?.emitLoginRequired()
|
|
247
|
-
},
|
|
248
|
-
onNavigateToProfileEdit = { fieldToEdit ->
|
|
249
|
-
OctopusEventEmitter.instance?.emitEditUser(fieldToEdit)
|
|
250
|
-
}
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private suspend fun loadImageFromUri(uri: String, context: Context): Painter? {
|
|
257
|
-
// Check cache first
|
|
258
|
-
imageCache[uri]?.let { return it }
|
|
259
|
-
|
|
260
|
-
return withContext(Dispatchers.IO) {
|
|
261
|
-
try {
|
|
262
|
-
val result = when {
|
|
263
|
-
// Network URLs - load directly
|
|
264
|
-
uri.startsWith("http://", ignoreCase = true) ||
|
|
265
|
-
uri.startsWith("https://", ignoreCase = true) -> {
|
|
266
|
-
loadFromNetwork(uri)
|
|
267
|
-
}
|
|
268
|
-
// Everything else - try as React Native asset
|
|
269
|
-
else -> {
|
|
270
|
-
loadReactNativeAsset(uri, context)
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
// Cache result (including null to avoid retrying failed loads)
|
|
275
|
-
imageCache[uri] = result
|
|
276
|
-
result
|
|
277
|
-
} catch (e: Exception) {
|
|
278
|
-
Log.w("OctopusUIActivity", "Failed to load image from URI: $uri", e)
|
|
279
|
-
imageCache[uri] = null
|
|
280
|
-
null
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private fun loadFromNetwork(url: String): Painter? {
|
|
286
|
-
return try {
|
|
287
|
-
URL(url).openStream().use { inputStream ->
|
|
288
|
-
val bitmap = BitmapFactory.decodeStream(inputStream)
|
|
289
|
-
bitmap?.let { BitmapPainter(it.asImageBitmap()) }
|
|
290
|
-
}
|
|
291
|
-
} catch (e: Exception) {
|
|
292
|
-
Log.w("OctopusUIActivity", "Failed to load image from network: $url", e)
|
|
293
|
-
null
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
private fun loadReactNativeAsset(assetName: String, context: Context): Painter? {
|
|
298
|
-
return try {
|
|
299
|
-
// First try: Direct drawable resource lookup (most common case)
|
|
300
|
-
val drawableResult = loadFromDrawableResources(assetName, context)
|
|
301
|
-
if (drawableResult != null) {
|
|
302
|
-
return drawableResult
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
// Second try: Assets folder with strategic path checking
|
|
306
|
-
loadFromAssetsFolder(assetName, context)
|
|
307
|
-
} catch (e: Exception) {
|
|
308
|
-
Log.w("OctopusUIActivity", "Failed to load asset: $assetName", e)
|
|
309
|
-
null
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
private fun loadFromAssetsFolder(assetName: String, context: Context): Painter? {
|
|
314
|
-
// Check if assetName already has extension
|
|
315
|
-
val hasExtension = assetName.contains(".")
|
|
316
|
-
val extensions = if (hasExtension) listOf("") else listOf("png", "jpg", "jpeg", "webp")
|
|
317
|
-
|
|
318
|
-
val folders = listOf(
|
|
319
|
-
"",
|
|
320
|
-
"drawable-mdpi",
|
|
321
|
-
"drawable-hdpi",
|
|
322
|
-
"drawable-xhdpi",
|
|
323
|
-
"drawable-xxhdpi",
|
|
324
|
-
"drawable-xxxhdpi",
|
|
325
|
-
"drawable"
|
|
326
|
-
)
|
|
327
|
-
|
|
328
|
-
for (folder in folders) {
|
|
329
|
-
for (ext in extensions) {
|
|
330
|
-
val filename = if (ext.isEmpty()) assetName else "$assetName.$ext"
|
|
331
|
-
val path = if (folder.isEmpty()) filename else "$folder/$filename"
|
|
332
|
-
|
|
333
|
-
try {
|
|
334
|
-
context.assets.open(path).use { inputStream ->
|
|
335
|
-
val bitmap = BitmapFactory.decodeStream(inputStream)
|
|
336
|
-
if (bitmap != null) {
|
|
337
|
-
return BitmapPainter(bitmap.asImageBitmap())
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
} catch (e: Exception) {
|
|
341
|
-
// Continue to next path
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return null
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
private fun loadFromDrawableResources(assetName: String, context: Context): Painter? {
|
|
350
|
-
return try {
|
|
351
|
-
// Try exact match first
|
|
352
|
-
val resourceId = context.resources.getIdentifier(
|
|
353
|
-
assetName, "drawable", context.packageName
|
|
354
|
-
)
|
|
355
|
-
if (resourceId != 0) {
|
|
356
|
-
return loadBitmapFromResource(context, resourceId)
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Fallback: fuzzy search (only if exact match fails)
|
|
360
|
-
val drawableResources = getAllDrawableResources(context)
|
|
361
|
-
for (resourceName in drawableResources) {
|
|
362
|
-
if (isResourceNameMatch(resourceName, assetName)) {
|
|
363
|
-
val resId = context.resources.getIdentifier(
|
|
364
|
-
resourceName, "drawable", context.packageName
|
|
365
|
-
)
|
|
366
|
-
if (resId != 0) {
|
|
367
|
-
return loadBitmapFromResource(context, resId)
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
null
|
|
373
|
-
} catch (e: Exception) {
|
|
374
|
-
null
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
private fun loadBitmapFromResource(context: Context, resourceId: Int): Painter? {
|
|
379
|
-
return try {
|
|
380
|
-
val bitmap = BitmapFactory.decodeResource(context.resources, resourceId)
|
|
381
|
-
bitmap?.let { BitmapPainter(it.asImageBitmap()) }
|
|
382
|
-
} catch (e: Exception) {
|
|
383
|
-
null
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
private fun getAllDrawableResources(context: Context): List<String> {
|
|
388
|
-
// Return cached result if available
|
|
389
|
-
cachedDrawableResources?.let { return it }
|
|
390
|
-
|
|
391
|
-
return try {
|
|
392
|
-
val packageName = context.packageName
|
|
393
|
-
val drawableResources = mutableListOf<String>()
|
|
394
|
-
|
|
395
|
-
// Get all drawable resources by scanning the R.drawable class
|
|
396
|
-
val drawableClass = Class.forName("$packageName.R\$drawable")
|
|
397
|
-
val fields = drawableClass.declaredFields
|
|
398
|
-
|
|
399
|
-
for (field in fields) {
|
|
400
|
-
if (field.type == Int::class.javaPrimitiveType) {
|
|
401
|
-
drawableResources.add(field.name)
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Cache for future use
|
|
406
|
-
cachedDrawableResources = drawableResources
|
|
407
|
-
drawableResources
|
|
408
|
-
} catch (e: Exception) {
|
|
409
|
-
Log.w("OctopusUIActivity", "Failed to get drawable resources", e)
|
|
410
|
-
emptyList()
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
private fun isResourceNameMatch(resourceName: String, assetName: String): Boolean {
|
|
416
|
-
val normalizedResourceName = resourceName.lowercase()
|
|
417
|
-
val normalizedAssetName = assetName.lowercase()
|
|
418
|
-
|
|
419
|
-
// Only check if one contains the other - removed dangerous generic patterns
|
|
420
|
-
return normalizedResourceName.contains(normalizedAssetName) ||
|
|
421
|
-
normalizedAssetName.contains(normalizedResourceName)
|
|
422
|
-
}
|