@onekeyfe/react-native-native-list 3.0.117 → 3.0.119
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 +6 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +72 -22
- package/ios/NativeListCell.swift +97 -26
- package/ios/RNCNativeListView.swift +250 -23
- package/lib/module/NativeList.js +12 -8
- package/lib/module/NativeList.web.js +14 -0
- package/lib/module/index.js +1 -1
- package/lib/module/web/NativeListWebEngine.js +112 -18
- package/lib/typescript/src/NativeList.d.ts +2 -0
- package/lib/typescript/src/NativeList.web.d.ts +2 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/models.d.ts +1 -0
- package/package.json +3 -3
- package/src/NativeList.tsx +23 -15
- package/src/NativeList.web.tsx +38 -1
- package/src/index.ts +1 -1
- package/src/models.ts +1 -0
- package/src/web/NativeListWebEngine.ts +161 -28
package/README.md
CHANGED
|
@@ -240,6 +240,12 @@ const avatar = {
|
|
|
240
240
|
} as const;
|
|
241
241
|
```
|
|
242
242
|
|
|
243
|
+
NativeList defaults `loadingStrategy` to `none`, so source-backed images render
|
|
244
|
+
without a loading background or terminal fallback. Set it to `static` or
|
|
245
|
+
`skeleton` to opt into loading UI; a configured `fallbackText` or
|
|
246
|
+
`fallbackIcon` is shown after a terminal load failure only when loading UI is
|
|
247
|
+
enabled.
|
|
248
|
+
|
|
243
249
|
Each cell owns a fixed pool of native `OneKeyImageReusableView` slots. On bind,
|
|
244
250
|
NativeList forwards the URI, headers, content fit, cache policy, autoplay, TOS
|
|
245
251
|
options, overscan, and loading strategy. A stable `rowKey:slot` recycling key
|
|
@@ -506,6 +506,7 @@ internal class NativeListRowView(
|
|
|
506
506
|
private var checkboxUsesSelectorStyle = false
|
|
507
507
|
private var iconSubduedColor = Color.rgb(141, 141, 141)
|
|
508
508
|
private var visualBackdropColor = Color.WHITE
|
|
509
|
+
private var imagePlaceholderColor = "#0000000F"
|
|
509
510
|
private val circleOutlineProvider = object : ViewOutlineProvider() {
|
|
510
511
|
override fun getOutline(view: View, outline: Outline) {
|
|
511
512
|
outline.setOval(0, 0, view.width, view.height)
|
|
@@ -914,6 +915,8 @@ internal class NativeListRowView(
|
|
|
914
915
|
}
|
|
915
916
|
iconSubduedColor = color(theme, "iconSubdued", "#00000072")
|
|
916
917
|
visualBackdropColor = color(theme, "rowBackground", "#FFFFFF")
|
|
918
|
+
imagePlaceholderColor = theme?.optString("strongBackground", "#0000000F")
|
|
919
|
+
?.takeIf(String::isNotEmpty) ?: "#0000000F"
|
|
917
920
|
unreadDot.background = roundedFill(
|
|
918
921
|
parseNativeListColor("#E5484D"),
|
|
919
922
|
4f,
|
|
@@ -2492,11 +2495,12 @@ internal class NativeListRowView(
|
|
|
2492
2495
|
mediaMetadataRow.gravity = Gravity.CENTER_VERTICAL
|
|
2493
2496
|
mediaMetadataRow.addView(subtitle, weighted().apply { marginEnd = dp(8) })
|
|
2494
2497
|
item.json.optJSONObject("networkImage")?.let { networkImage ->
|
|
2495
|
-
|
|
2498
|
+
// OneKey patch: Preserve badge layout without exposing a loading/error tile.
|
|
2499
|
+
// mediaNetworkImage.visibility = VISIBLE
|
|
2496
2500
|
mediaNetworkImage.outlineProvider = circleOutlineProvider
|
|
2497
2501
|
mediaNetworkImage.clipToOutline = true
|
|
2498
2502
|
mediaMetadataRow.addView(mediaNetworkImage, LayoutParams(dp(14), dp(14)))
|
|
2499
|
-
bindImage(networkImage, mediaNetworkImage, item.key, 2, "network")
|
|
2503
|
+
bindImage(networkImage, mediaNetworkImage, item.key, 2, "network", hideUntilLoaded = true)
|
|
2500
2504
|
}
|
|
2501
2505
|
mainColumn.addView(mediaMetadataRow, 0)
|
|
2502
2506
|
mainColumn.addView(titleLine, 1)
|
|
@@ -3184,11 +3188,19 @@ internal class NativeListRowView(
|
|
|
3184
3188
|
val isIcon = kind == "icon"
|
|
3185
3189
|
val cornerIconData = visual.optJSONObject("cornerIcon")
|
|
3186
3190
|
val fallback = visual.optString("fallbackText").take(2)
|
|
3187
|
-
|
|
3191
|
+
val fallbackIconData = visual.optJSONObject("fallbackIcon")
|
|
3192
|
+
val sourceLoadingStrategy = sources.firstOrNull()?.first?.optString("loadingStrategy", "none") ?: "none"
|
|
3193
|
+
val showsSourcePlaceholder = !isIcon && sources.isNotEmpty() && sourceLoadingStrategy != "none"
|
|
3194
|
+
val handlesSourceFallback =
|
|
3195
|
+
!isIcon && sources.isNotEmpty() &&
|
|
3196
|
+
showsSourcePlaceholder &&
|
|
3197
|
+
(visual.has("fallbackText") || fallbackIconData != null)
|
|
3198
|
+
leadingFallback.text = if (handlesSourceFallback) "" else fallback
|
|
3188
3199
|
leadingFallback.setTextColor(parseNativeListColor("#00000072"))
|
|
3189
3200
|
val visualBackground = safeColor(
|
|
3190
3201
|
visual.optString("backgroundColor"),
|
|
3191
|
-
|
|
3202
|
+
// OneKey patch: source-backed visuals default to no placeholder/background.
|
|
3203
|
+
if (!isIcon && sources.isNotEmpty()) Color.TRANSPARENT else parseNativeListColor(imagePlaceholderColor),
|
|
3192
3204
|
)
|
|
3193
3205
|
if (!isIcon && visual.optString("backgroundColor").isNotEmpty()) {
|
|
3194
3206
|
leadingFrame.background = roundedFill(
|
|
@@ -3197,10 +3209,11 @@ internal class NativeListRowView(
|
|
|
3197
3209
|
)
|
|
3198
3210
|
}
|
|
3199
3211
|
leadingFallback.background = roundedFill(
|
|
3200
|
-
visualBackground,
|
|
3212
|
+
if (handlesSourceFallback) parseNativeListColor(imagePlaceholderColor) else visualBackground,
|
|
3201
3213
|
cornerRadiusDp ?: leadingCornerRadius(shape, minOf(sizeDp, heightDp)),
|
|
3202
3214
|
)
|
|
3203
|
-
leadingFallback.visibility =
|
|
3215
|
+
leadingFallback.visibility =
|
|
3216
|
+
if (!isIcon && (sources.isEmpty() || handlesSourceFallback)) VISIBLE else GONE
|
|
3204
3217
|
if (isIcon) {
|
|
3205
3218
|
leadingFrame.background = GradientDrawable().apply {
|
|
3206
3219
|
setColor(visualBackground)
|
|
@@ -3213,6 +3226,14 @@ internal class NativeListRowView(
|
|
|
3213
3226
|
parseNativeListColor("#0000009B"),
|
|
3214
3227
|
)
|
|
3215
3228
|
leadingIcon.visibility = VISIBLE
|
|
3229
|
+
} else if (sources.isEmpty() && fallbackIconData != null) {
|
|
3230
|
+
leadingFallback.visibility = GONE
|
|
3231
|
+
leadingIcon.iconName = fallbackIconData.optString("name")
|
|
3232
|
+
leadingIcon.tintColor = safeColor(
|
|
3233
|
+
fallbackIconData.optString("tintColor"),
|
|
3234
|
+
parseNativeListColor("#0000009B"),
|
|
3235
|
+
)
|
|
3236
|
+
leadingIcon.visibility = VISIBLE
|
|
3216
3237
|
}
|
|
3217
3238
|
val visibleSources = sources.take(leadingImages.size)
|
|
3218
3239
|
val tokenPair = kind == "token" && visibleSources.size > 1
|
|
@@ -3265,7 +3286,10 @@ internal class NativeListRowView(
|
|
|
3265
3286
|
}
|
|
3266
3287
|
visibleSources.forEachIndexed { index, (source, variant) ->
|
|
3267
3288
|
val image = leadingImages[index]
|
|
3268
|
-
|
|
3289
|
+
// OneKey patch: Decorative badges and source-owned fallbacks stay hidden until loaded.
|
|
3290
|
+
// image.visibility = VISIBLE
|
|
3291
|
+
val ownsSourceFallback = index == 0 && handlesSourceFallback
|
|
3292
|
+
image.visibility = if (index > 0 || ownsSourceFallback) INVISIBLE else VISIBLE
|
|
3269
3293
|
image.layoutParams = leadingImageLayout(
|
|
3270
3294
|
index = index,
|
|
3271
3295
|
count = visibleSources.size,
|
|
@@ -3302,21 +3326,32 @@ internal class NativeListRowView(
|
|
|
3302
3326
|
marketLeadingUsesSourceClip = true
|
|
3303
3327
|
image.clipToOutline = false
|
|
3304
3328
|
}
|
|
3305
|
-
val fallbackIcon = if (index == 0)
|
|
3329
|
+
val fallbackIcon = if (index == 0) fallbackIconData else null
|
|
3306
3330
|
val expectedEpoch = bindingEpoch
|
|
3307
3331
|
bindImage(source, image, boundKey ?: "", index, variant,
|
|
3308
|
-
onLoad = if (
|
|
3309
|
-
if (bindingEpoch == expectedEpoch) {
|
|
3332
|
+
onLoad = if (!ownsSourceFallback) null else ({
|
|
3333
|
+
if (bindingEpoch == expectedEpoch) {
|
|
3334
|
+
image.visibility = VISIBLE
|
|
3335
|
+
leadingFallback.visibility = GONE
|
|
3336
|
+
leadingIcon.visibility = GONE
|
|
3337
|
+
}
|
|
3310
3338
|
}),
|
|
3311
|
-
onError = if (
|
|
3339
|
+
onError = if (!ownsSourceFallback) null else ({
|
|
3312
3340
|
if (bindingEpoch == expectedEpoch) {
|
|
3313
3341
|
image.visibility = GONE
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3342
|
+
if (fallbackIcon != null) {
|
|
3343
|
+
leadingFallback.visibility = GONE
|
|
3344
|
+
leadingIcon.iconName = fallbackIcon.optString("name")
|
|
3345
|
+
leadingIcon.tintColor = safeColor(fallbackIcon.optString("tintColor"), parseNativeListColor("#0000009B"))
|
|
3346
|
+
leadingIcon.visibility = VISIBLE
|
|
3347
|
+
} else {
|
|
3348
|
+
leadingIcon.visibility = GONE
|
|
3349
|
+
leadingFallback.text = fallback
|
|
3350
|
+
leadingFallback.visibility = VISIBLE
|
|
3351
|
+
}
|
|
3318
3352
|
}
|
|
3319
3353
|
}),
|
|
3354
|
+
hideUntilLoaded = index > 0 || ownsSourceFallback,
|
|
3320
3355
|
)
|
|
3321
3356
|
}
|
|
3322
3357
|
// OneKey patch: wallet overlays retain source images, provider colors and QR text.
|
|
@@ -3339,7 +3374,7 @@ internal class NativeListRowView(
|
|
|
3339
3374
|
val image = overlay.optJSONObject("image")
|
|
3340
3375
|
val view = when {
|
|
3341
3376
|
image != null -> OneKeyImageReusableView(reactContext).also {
|
|
3342
|
-
bindImage(image, it, boundKey ?: "", 10 + index, "generic")
|
|
3377
|
+
bindImage(image, it, boundKey ?: "", 10 + index, "generic", hideUntilLoaded = true)
|
|
3343
3378
|
selectorImages.add(it)
|
|
3344
3379
|
}
|
|
3345
3380
|
overlay.optString("text").isNotEmpty() -> TextView(context).apply {
|
|
@@ -4086,12 +4121,26 @@ internal class NativeListRowView(
|
|
|
4086
4121
|
variant: String,
|
|
4087
4122
|
onLoad: (() -> Unit)? = null,
|
|
4088
4123
|
onError: (() -> Unit)? = null,
|
|
4124
|
+
hideUntilLoaded: Boolean = false,
|
|
4089
4125
|
retryAttempt: Int = 0,
|
|
4090
4126
|
) {
|
|
4091
4127
|
selectorImageRetries.remove(imageView)?.let(imageView::removeCallbacks)
|
|
4092
4128
|
val expectedEpoch = bindingEpoch
|
|
4093
4129
|
val retryLimit = source.optInt("retryTimes", 0).coerceAtLeast(0)
|
|
4094
4130
|
val uri = source.optString("uri").trim().takeIf(String::isNotEmpty)
|
|
4131
|
+
if (hideUntilLoaded) imageView.visibility = INVISIBLE
|
|
4132
|
+
val handleLoad: (() -> Unit)? = if (hideUntilLoaded) ({
|
|
4133
|
+
if (bindingEpoch == expectedEpoch) {
|
|
4134
|
+
imageView.visibility = VISIBLE
|
|
4135
|
+
onLoad?.invoke()
|
|
4136
|
+
}
|
|
4137
|
+
}) else onLoad
|
|
4138
|
+
val handleError: (() -> Unit)? = if (hideUntilLoaded) ({
|
|
4139
|
+
if (bindingEpoch == expectedEpoch) {
|
|
4140
|
+
imageView.visibility = INVISIBLE
|
|
4141
|
+
onError?.invoke()
|
|
4142
|
+
}
|
|
4143
|
+
}) else onError
|
|
4095
4144
|
imageView.configure(
|
|
4096
4145
|
sourceUri = uri,
|
|
4097
4146
|
sourceHeadersJson = source.optJSONObject("headers")?.toString(),
|
|
@@ -4102,21 +4151,22 @@ internal class NativeListRowView(
|
|
|
4102
4151
|
recyclingKey = if (retryAttempt == 0) "$token:$slot" else "$token:$slot:retry:$retryAttempt",
|
|
4103
4152
|
optimizeTos = retryAttempt == 0 && source.optBoolean("optimizeTos", true),
|
|
4104
4153
|
overscan = source.optDouble("overscan", 1.1),
|
|
4105
|
-
loadingStrategy = source.optString("loadingStrategy", "
|
|
4106
|
-
|
|
4154
|
+
loadingStrategy = source.optString("loadingStrategy", "none"),
|
|
4155
|
+
placeholderColor = imagePlaceholderColor,
|
|
4156
|
+
onLoad = if (retryLimit == 0) handleLoad else ({
|
|
4107
4157
|
if (bindingEpoch == expectedEpoch) {
|
|
4108
4158
|
selectorImageRetries.remove(imageView)?.let(imageView::removeCallbacks)
|
|
4109
|
-
|
|
4159
|
+
handleLoad?.invoke()
|
|
4110
4160
|
}
|
|
4111
4161
|
}),
|
|
4112
|
-
onError = if (retryLimit == 0)
|
|
4162
|
+
onError = if (retryLimit == 0) handleError else ({
|
|
4113
4163
|
if (bindingEpoch == expectedEpoch) {
|
|
4114
|
-
if (retryAttempt >= retryLimit)
|
|
4164
|
+
if (retryAttempt >= retryLimit) handleError?.invoke()
|
|
4115
4165
|
else if (!selectorImageRetries.containsKey(imageView)) {
|
|
4116
4166
|
val retry = Runnable {
|
|
4117
4167
|
if (bindingEpoch == expectedEpoch) {
|
|
4118
4168
|
selectorImageRetries.remove(imageView)
|
|
4119
|
-
bindImage(source, imageView, token, slot, variant, onLoad, onError, retryAttempt + 1)
|
|
4169
|
+
bindImage(source, imageView, token, slot, variant, onLoad, onError, hideUntilLoaded, retryAttempt + 1)
|
|
4120
4170
|
}
|
|
4121
4171
|
}
|
|
4122
4172
|
selectorImageRetries[imageView] = retry
|
package/ios/NativeListCell.swift
CHANGED
|
@@ -1835,7 +1835,13 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1835
1835
|
let label = UILabel()
|
|
1836
1836
|
label.font = nativeListFont(ofSize: 14)
|
|
1837
1837
|
label.lineBreakMode = .byTruncatingTail
|
|
1838
|
-
|
|
1838
|
+
// Match V1: the balance may shrink, but keep the shortened address intact.
|
|
1839
|
+
label.setContentCompressionResistancePriority(
|
|
1840
|
+
item.data.string("presentation") == "accountSelector" && segment.bool("separatorBefore")
|
|
1841
|
+
? .defaultHigh
|
|
1842
|
+
: .defaultLow,
|
|
1843
|
+
for: .horizontal
|
|
1844
|
+
)
|
|
1839
1845
|
label.textColor = dataTextColor(segment.string("tone", default: "secondary"), theme: theme)
|
|
1840
1846
|
setLineHeight(label, text: segment.string("text"), lineHeight: 20)
|
|
1841
1847
|
let runs = segment.dictionaries("textSegments")
|
|
@@ -2591,13 +2597,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2591
2597
|
subtitleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
2592
2598
|
if let networkImage = item.data.dictionary("networkImage") {
|
|
2593
2599
|
mediaMetadataStack.addArrangedSubview(mediaNetworkImage)
|
|
2594
|
-
|
|
2600
|
+
// OneKey patch: Preserve badge layout without exposing a loading/error tile.
|
|
2601
|
+
// mediaNetworkImage.isHidden = false
|
|
2595
2602
|
bindImage(
|
|
2596
2603
|
networkImage,
|
|
2597
2604
|
into: mediaNetworkImage,
|
|
2598
2605
|
token: item.key,
|
|
2599
2606
|
slot: 2,
|
|
2600
|
-
variant: "network"
|
|
2607
|
+
variant: "network",
|
|
2608
|
+
hideUntilLoaded: true
|
|
2601
2609
|
)
|
|
2602
2610
|
}
|
|
2603
2611
|
mainStack.insertArrangedSubview(mediaMetadataStack, at: 0)
|
|
@@ -3367,14 +3375,28 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3367
3375
|
default: kind == "image" || mediaHeight.isActive ? "rounded" : "circle"
|
|
3368
3376
|
)
|
|
3369
3377
|
let cornerIcon = visual.dictionary("cornerIcon")
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3378
|
+
let fallbackText = String(visual.string("fallbackText").prefix(2))
|
|
3379
|
+
let fallbackIconData = visual.dictionary("fallbackIcon")
|
|
3380
|
+
let sourceLoadingStrategy = sources.first?.data.string("loadingStrategy", default: "none") ?? "none"
|
|
3381
|
+
let showsSourcePlaceholder = !isIcon && !sources.isEmpty && sourceLoadingStrategy != "none"
|
|
3382
|
+
let handlesSourceFallback = !isIcon && !sources.isEmpty &&
|
|
3383
|
+
showsSourcePlaceholder &&
|
|
3384
|
+
(visual["fallbackText"] != nil || fallbackIconData != nil)
|
|
3385
|
+
fallbackLabel.text = handlesSourceFallback ? nil : fallbackText
|
|
3386
|
+
let sourceFallbackBackground = currentTheme?["strongBackground"] as? String ?? "#0000000F"
|
|
3387
|
+
// OneKey patch: source-backed visuals default to no placeholder/background.
|
|
3388
|
+
// A non-none image loadingStrategy opts back into the themed placeholder.
|
|
3389
|
+
let visualBackground = visual.string(
|
|
3390
|
+
"backgroundColor",
|
|
3391
|
+
default: !isIcon && !sources.isEmpty ? "#00000000" : sourceFallbackBackground
|
|
3374
3392
|
)
|
|
3393
|
+
let visualBackgroundColor = UIColor(nativeListHex: visualBackground, fallback: .clear)
|
|
3394
|
+
leadingContainer.backgroundColor = handlesSourceFallback
|
|
3395
|
+
? UIColor(nativeListHex: sourceFallbackBackground, fallback: .clear)
|
|
3396
|
+
: visualBackgroundColor
|
|
3375
3397
|
leadingContainer.layer.cornerRadius = leadingCornerRadius(shape: shape)
|
|
3376
3398
|
leadingContainer.clipsToBounds = true
|
|
3377
|
-
fallbackLabel.isHidden = isIcon || !sources.isEmpty
|
|
3399
|
+
fallbackLabel.isHidden = isIcon || (!sources.isEmpty && !handlesSourceFallback)
|
|
3378
3400
|
if isIcon {
|
|
3379
3401
|
leadingContainer.layer.borderWidth = 1 / UIScreen.main.scale
|
|
3380
3402
|
leadingContainer.layer.borderColor = UIColor(
|
|
@@ -3388,6 +3410,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3388
3410
|
)
|
|
3389
3411
|
leadingIconImageView.image = nativeListIcon(named: visual.string("name"))
|
|
3390
3412
|
leadingIconImageView.contentMode = .scaleAspectFit
|
|
3413
|
+
} else if sources.isEmpty, let fallbackIconData {
|
|
3414
|
+
fallbackLabel.isHidden = true
|
|
3415
|
+
leadingIconImageView.isHidden = false
|
|
3416
|
+
leadingIconImageView.tintColor = UIColor(
|
|
3417
|
+
nativeListHex: fallbackIconData.string("tintColor", default: "#646464"),
|
|
3418
|
+
fallback: .darkGray
|
|
3419
|
+
)
|
|
3420
|
+
leadingIconImageView.image = nativeListIcon(named: fallbackIconData.string("name"))
|
|
3421
|
+
leadingIconImageView.contentMode = .scaleAspectFit
|
|
3391
3422
|
}
|
|
3392
3423
|
let visibleSources = Array(sources.prefix(leadingImages.count))
|
|
3393
3424
|
let tokenPair = kind == "token" && visibleSources.count > 1
|
|
@@ -3422,7 +3453,10 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3422
3453
|
}
|
|
3423
3454
|
for (index, source) in visibleSources.enumerated() {
|
|
3424
3455
|
let imageView = leadingImages[index]
|
|
3425
|
-
|
|
3456
|
+
// OneKey patch: Decorative badges and source-owned fallbacks stay hidden until loaded.
|
|
3457
|
+
// imageView.isHidden = false
|
|
3458
|
+
let ownsSourceFallback = index == 0 && handlesSourceFallback
|
|
3459
|
+
imageView.isHidden = index > 0 || ownsSourceFallback
|
|
3426
3460
|
imageView.clipsToBounds = true
|
|
3427
3461
|
leadingSlotConstraints.append(contentsOf: leadingConstraints(
|
|
3428
3462
|
imageView,
|
|
@@ -3436,22 +3470,31 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3436
3470
|
imageView.layer.cornerRadius = 0
|
|
3437
3471
|
imageView.clipsToBounds = false
|
|
3438
3472
|
}
|
|
3439
|
-
let fallbackIcon = index == 0 ?
|
|
3473
|
+
let fallbackIcon = index == 0 ? fallbackIconData : nil
|
|
3440
3474
|
let expectedEpoch = bindingEpoch
|
|
3441
3475
|
bindImage(source.data, into: imageView, token: key, slot: index, variant: source.variant,
|
|
3442
|
-
onLoad:
|
|
3476
|
+
onLoad: !ownsSourceFallback ? nil : { [weak self, weak imageView] in
|
|
3443
3477
|
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
3444
3478
|
imageView?.isHidden = false
|
|
3479
|
+
self.fallbackLabel.isHidden = true
|
|
3445
3480
|
self.leadingIconImageView.isHidden = true
|
|
3481
|
+
self.leadingContainer.backgroundColor = visualBackgroundColor
|
|
3446
3482
|
},
|
|
3447
|
-
onError:
|
|
3448
|
-
guard let self, self.bindingEpoch == expectedEpoch
|
|
3483
|
+
onError: !ownsSourceFallback ? nil : { [weak self, weak imageView] in
|
|
3484
|
+
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
3449
3485
|
imageView?.isHidden = true
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3486
|
+
if let fallbackIcon {
|
|
3487
|
+
self.fallbackLabel.isHidden = true
|
|
3488
|
+
self.leadingIconImageView.image = nativeListIcon(named: fallbackIcon.string("name"))
|
|
3489
|
+
self.leadingIconImageView.tintColor = UIColor(nativeListHex: fallbackIcon.string("tintColor", default: "#646464"), fallback: .darkGray)
|
|
3490
|
+
self.leadingIconImageView.isHidden = false
|
|
3491
|
+
} else {
|
|
3492
|
+
self.leadingIconImageView.isHidden = true
|
|
3493
|
+
self.fallbackLabel.text = fallbackText
|
|
3494
|
+
self.fallbackLabel.isHidden = false
|
|
3495
|
+
}
|
|
3496
|
+
},
|
|
3497
|
+
hideUntilLoaded: index > 0 || ownsSourceFallback)
|
|
3455
3498
|
}
|
|
3456
3499
|
// OneKey patch: source-derived wallet decorations may occupy both corners.
|
|
3457
3500
|
let overlays = visual.dictionaries("overlays")
|
|
@@ -3468,7 +3511,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3468
3511
|
let width = CGFloat(overlay.double("width", default: isWalletText ? Double(naturalTextWidth) : Double(size)))
|
|
3469
3512
|
let frame = UIView()
|
|
3470
3513
|
frame.translatesAutoresizingMaskIntoConstraints = false
|
|
3471
|
-
|
|
3514
|
+
// OneKey patch: The row theme, not a light-only white, owns badge backing.
|
|
3515
|
+
// frame.backgroundColor = UIColor(nativeListHex: overlay.string("backgroundColor", default: "#FFFFFF"), fallback: .clear)
|
|
3516
|
+
frame.backgroundColor = UIColor(
|
|
3517
|
+
nativeListHex: overlay.string(
|
|
3518
|
+
"backgroundColor",
|
|
3519
|
+
default: currentTheme?["rowBackground"] as? String ?? "#00000000"
|
|
3520
|
+
),
|
|
3521
|
+
fallback: .clear
|
|
3522
|
+
)
|
|
3472
3523
|
frame.layer.cornerRadius = min(width, height) / 2
|
|
3473
3524
|
frame.clipsToBounds = true
|
|
3474
3525
|
leadingContainer.addSubview(frame)
|
|
@@ -3483,7 +3534,14 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3483
3534
|
let content: UIView
|
|
3484
3535
|
if let image = overlay.dictionary("image") {
|
|
3485
3536
|
let imageView = OneKeyImageReusableView(frame: .zero)
|
|
3486
|
-
bindImage(
|
|
3537
|
+
bindImage(
|
|
3538
|
+
image,
|
|
3539
|
+
into: imageView,
|
|
3540
|
+
token: key,
|
|
3541
|
+
slot: 10 + index,
|
|
3542
|
+
variant: "generic",
|
|
3543
|
+
hideUntilLoaded: true
|
|
3544
|
+
)
|
|
3487
3545
|
selectorImages.append(imageView)
|
|
3488
3546
|
content = imageView
|
|
3489
3547
|
} else if !overlay.string("text").isEmpty {
|
|
@@ -3943,6 +4001,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3943
4001
|
variant: String,
|
|
3944
4002
|
onLoad: (() -> Void)? = nil,
|
|
3945
4003
|
onError: (() -> Void)? = nil,
|
|
4004
|
+
hideUntilLoaded: Bool = false,
|
|
3946
4005
|
retryAttempt: Int = 0
|
|
3947
4006
|
) {
|
|
3948
4007
|
let imageID = ObjectIdentifier(imageView)
|
|
@@ -3957,6 +4016,17 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3957
4016
|
} else {
|
|
3958
4017
|
headersJson = nil
|
|
3959
4018
|
}
|
|
4019
|
+
if hideUntilLoaded { imageView.isHidden = true }
|
|
4020
|
+
let handleLoad: (() -> Void)? = hideUntilLoaded ? { [weak self, weak imageView] in
|
|
4021
|
+
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
4022
|
+
imageView?.isHidden = false
|
|
4023
|
+
onLoad?()
|
|
4024
|
+
} : onLoad
|
|
4025
|
+
let handleError: (() -> Void)? = hideUntilLoaded ? { [weak self, weak imageView] in
|
|
4026
|
+
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
4027
|
+
imageView?.isHidden = true
|
|
4028
|
+
onError?()
|
|
4029
|
+
} : onError
|
|
3960
4030
|
imageView.configure(
|
|
3961
4031
|
sourceUri: source.string("uri").trimmingCharacters(in: .whitespacesAndNewlines),
|
|
3962
4032
|
sourceHeadersJson: headersJson,
|
|
@@ -3967,20 +4037,21 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3967
4037
|
recyclingKey: retryAttempt == 0 ? "\(token):\(slot)" : "\(token):\(slot):retry:\(retryAttempt)",
|
|
3968
4038
|
optimizeTos: retryAttempt == 0 && (source["optimizeTos"] == nil || source.bool("optimizeTos")),
|
|
3969
4039
|
overscan: source["overscan"] == nil ? 1.1 : source.double("overscan"),
|
|
3970
|
-
loadingStrategy: source.string("loadingStrategy", default: "
|
|
3971
|
-
|
|
4040
|
+
loadingStrategy: source.string("loadingStrategy", default: "none"),
|
|
4041
|
+
placeholderColor: currentTheme?["strongBackground"] as? String ?? "#0000000F",
|
|
4042
|
+
onLoad: retryLimit == 0 ? handleLoad : { [weak self] in
|
|
3972
4043
|
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
3973
4044
|
self.selectorImageRetries.removeValue(forKey: imageID)?.cancel()
|
|
3974
|
-
|
|
4045
|
+
handleLoad?()
|
|
3975
4046
|
},
|
|
3976
|
-
onError: retryLimit == 0 ?
|
|
4047
|
+
onError: retryLimit == 0 ? handleError : { [weak self, weak imageView] in
|
|
3977
4048
|
guard let self, self.bindingEpoch == expectedEpoch, let imageView else { return }
|
|
3978
|
-
guard retryAttempt < retryLimit else {
|
|
4049
|
+
guard retryAttempt < retryLimit else { handleError?(); return }
|
|
3979
4050
|
guard self.selectorImageRetries[imageID] == nil else { return }
|
|
3980
4051
|
let retry = DispatchWorkItem { [weak self, weak imageView] in
|
|
3981
4052
|
guard let self, self.bindingEpoch == expectedEpoch, let imageView else { return }
|
|
3982
4053
|
self.selectorImageRetries.removeValue(forKey: imageID)
|
|
3983
|
-
self.bindImage(source, into: imageView, token: token, slot: slot, variant: variant, onLoad: onLoad, onError: onError, retryAttempt: retryAttempt + 1)
|
|
4054
|
+
self.bindImage(source, into: imageView, token: token, slot: slot, variant: variant, onLoad: onLoad, onError: onError, hideUntilLoaded: hideUntilLoaded, retryAttempt: retryAttempt + 1)
|
|
3984
4055
|
}
|
|
3985
4056
|
self.selectorImageRetries[imageID] = retry
|
|
3986
4057
|
DispatchQueue.main.asyncAfter(deadline: .now() + Double(Int.random(in: 0...2)), execute: retry)
|