@octopus-community/react-native 1.0.5 → 1.0.6
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/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusSSOAuthenticator.kt
CHANGED
|
@@ -107,10 +107,12 @@ class OctopusSSOAuthenticator(private val eventEmitter: OctopusEventEmitter) {
|
|
|
107
107
|
} else null
|
|
108
108
|
|
|
109
109
|
val profilePicture = profileParams.getString("profilePicture")?.let { pictureUrl ->
|
|
110
|
-
if (pictureUrl.startsWith("http://") || pictureUrl.startsWith("https://")) {
|
|
110
|
+
if (pictureUrl.isNotBlank() && (pictureUrl.startsWith("http://") || pictureUrl.startsWith("https://"))) {
|
|
111
111
|
Image.Remote(pictureUrl)
|
|
112
|
-
} else {
|
|
112
|
+
} else if (pictureUrl.isNotBlank()) {
|
|
113
113
|
Image.Local(pictureUrl)
|
|
114
|
+
} else {
|
|
115
|
+
null
|
|
114
116
|
}
|
|
115
117
|
}
|
|
116
118
|
|
package/android/src/main/java/com/octopuscommunity/octopusreactnativesdk/OctopusUIActivity.kt
CHANGED
|
@@ -44,6 +44,12 @@ import com.octopuscommunity.sdk.ui.octopusLightColorScheme
|
|
|
44
44
|
import kotlinx.coroutines.Dispatchers
|
|
45
45
|
import kotlinx.coroutines.withContext
|
|
46
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
|
|
47
53
|
|
|
48
54
|
class OctopusUIActivity : ComponentActivity() {
|
|
49
55
|
private val closeUIReceiver = object : BroadcastReceiver() {
|
|
@@ -79,7 +85,7 @@ class OctopusUIActivity : ComponentActivity() {
|
|
|
79
85
|
@Composable
|
|
80
86
|
private fun OctopusUI(onBack: () -> Unit) {
|
|
81
87
|
val context = LocalContext.current
|
|
82
|
-
|
|
88
|
+
|
|
83
89
|
// Get theme config once when UI is created - no need for polling
|
|
84
90
|
val themeConfig = OctopusThemeManager.getThemeConfig()
|
|
85
91
|
|
|
@@ -122,19 +128,19 @@ private fun OctopusUI(onBack: () -> Unit) {
|
|
|
122
128
|
// Handle logo loading using built-in Android capabilities
|
|
123
129
|
var logoPainter by remember { mutableStateOf<Painter?>(null) }
|
|
124
130
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
|
131
141
|
logoPainter = null
|
|
132
142
|
}
|
|
133
|
-
} ?: run {
|
|
134
|
-
// No theme config or no logo source
|
|
135
|
-
logoPainter = null
|
|
136
143
|
}
|
|
137
|
-
}
|
|
138
144
|
|
|
139
145
|
// Create drawables based on theme config
|
|
140
146
|
val drawables = if (logoPainter != null) {
|
|
@@ -158,14 +164,14 @@ private fun OctopusUI(onBack: () -> Unit) {
|
|
|
158
164
|
|
|
159
165
|
private fun createCustomTypography(themeConfig: OctopusThemeConfig?): OctopusTypography {
|
|
160
166
|
val defaultTypography = OctopusTypographyDefaults.typography()
|
|
161
|
-
|
|
167
|
+
|
|
162
168
|
if (themeConfig?.fonts == null) {
|
|
163
169
|
return defaultTypography
|
|
164
170
|
}
|
|
165
|
-
|
|
171
|
+
|
|
166
172
|
val fontsConfig = themeConfig.fonts!!
|
|
167
173
|
val textStyles = fontsConfig.textStyles
|
|
168
|
-
|
|
174
|
+
|
|
169
175
|
// Create custom typography based on new unified font configuration
|
|
170
176
|
if (textStyles != null && textStyles.isNotEmpty()) {
|
|
171
177
|
return OctopusTypographyDefaults.typography(
|
|
@@ -177,8 +183,8 @@ private fun createCustomTypography(themeConfig: OctopusThemeConfig?): OctopusTyp
|
|
|
177
183
|
caption2 = createTextStyle(textStyles["caption2"], defaultTypography.caption2)
|
|
178
184
|
)
|
|
179
185
|
}
|
|
180
|
-
|
|
181
|
-
|
|
186
|
+
|
|
187
|
+
|
|
182
188
|
return defaultTypography
|
|
183
189
|
}
|
|
184
190
|
|
|
@@ -186,19 +192,19 @@ private fun createTextStyle(textStyleConfig: OctopusTextStyleConfig?, defaultSty
|
|
|
186
192
|
if (textStyleConfig == null) {
|
|
187
193
|
return defaultStyle
|
|
188
194
|
}
|
|
189
|
-
|
|
195
|
+
|
|
190
196
|
val fontFamily = when (textStyleConfig.fontType) {
|
|
191
197
|
"serif" -> FontFamily.Serif
|
|
192
198
|
"monospace" -> FontFamily.Monospace
|
|
193
199
|
"default" -> FontFamily.Default
|
|
194
200
|
else -> FontFamily.Default
|
|
195
201
|
}
|
|
196
|
-
|
|
197
|
-
val fontSize = textStyleConfig.fontSize?.let {
|
|
202
|
+
|
|
203
|
+
val fontSize = textStyleConfig.fontSize?.let {
|
|
198
204
|
// Use points directly as sp (1 point ≈ 1 sp on Android)
|
|
199
205
|
it.sp
|
|
200
206
|
} ?: defaultStyle.fontSize
|
|
201
|
-
|
|
207
|
+
|
|
202
208
|
return defaultStyle.copy(
|
|
203
209
|
fontFamily = fontFamily,
|
|
204
210
|
fontSize = fontSize
|
|
@@ -247,21 +253,170 @@ private fun OctopusUIContent(onBack: () -> Unit) {
|
|
|
247
253
|
}
|
|
248
254
|
}
|
|
249
255
|
|
|
250
|
-
private suspend fun loadImageFromUri(uri: String): Painter? {
|
|
256
|
+
private suspend fun loadImageFromUri(uri: String, context: Context): Painter? {
|
|
257
|
+
// Check cache first
|
|
258
|
+
imageCache[uri]?.let { return it }
|
|
259
|
+
|
|
251
260
|
return withContext(Dispatchers.IO) {
|
|
252
261
|
try {
|
|
253
|
-
val
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
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
|
+
}
|
|
262
272
|
}
|
|
273
|
+
|
|
274
|
+
// Cache result (including null to avoid retrying failed loads)
|
|
275
|
+
imageCache[uri] = result
|
|
276
|
+
result
|
|
263
277
|
} catch (e: Exception) {
|
|
278
|
+
Log.w("OctopusUIActivity", "Failed to load image from URI: $uri", e)
|
|
279
|
+
imageCache[uri] = null
|
|
264
280
|
null
|
|
265
281
|
}
|
|
266
282
|
}
|
|
267
|
-
}
|
|
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
|
+
}
|
|
@@ -87,7 +87,7 @@ class OctopusSSOAuthenticator {
|
|
|
87
87
|
let ageInformation: ClientUser.AgeInformation? = legalAgeReached != nil ? (legalAgeReached! ? .legalAgeReached : .underaged) : nil
|
|
88
88
|
|
|
89
89
|
var profilePictureData: Data? = nil
|
|
90
|
-
if let pictureUrl = profileParams["profilePicture"] as? String {
|
|
90
|
+
if let pictureUrl = profileParams["profilePicture"] as? String, !pictureUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
|
91
91
|
profilePictureData = try await loadImageData(from: pictureUrl)
|
|
92
92
|
}
|
|
93
93
|
|