@onekeyfe/react-native-native-list 3.0.118 → 3.0.120
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 +6 -4
- package/ios/NativeListCell.swift +14 -8
- package/ios/RNCNativeListView.swift +247 -19
- package/lib/module/web/NativeListWebEngine.js +33 -9
- package/lib/typescript/src/models.d.ts +1 -0
- package/package.json +3 -3
- package/src/models.ts +1 -0
- package/src/web/NativeListWebEngine.ts +44 -10
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
|
|
@@ -3189,16 +3189,18 @@ internal class NativeListRowView(
|
|
|
3189
3189
|
val cornerIconData = visual.optJSONObject("cornerIcon")
|
|
3190
3190
|
val fallback = visual.optString("fallbackText").take(2)
|
|
3191
3191
|
val fallbackIconData = visual.optJSONObject("fallbackIcon")
|
|
3192
|
+
val sourceLoadingStrategy = sources.firstOrNull()?.first?.optString("loadingStrategy", "none") ?: "none"
|
|
3193
|
+
val showsSourcePlaceholder = !isIcon && sources.isNotEmpty() && sourceLoadingStrategy != "none"
|
|
3192
3194
|
val handlesSourceFallback =
|
|
3193
3195
|
!isIcon && sources.isNotEmpty() &&
|
|
3196
|
+
showsSourcePlaceholder &&
|
|
3194
3197
|
(visual.has("fallbackText") || fallbackIconData != null)
|
|
3195
3198
|
leadingFallback.text = if (handlesSourceFallback) "" else fallback
|
|
3196
3199
|
leadingFallback.setTextColor(parseNativeListColor("#00000072"))
|
|
3197
3200
|
val visualBackground = safeColor(
|
|
3198
3201
|
visual.optString("backgroundColor"),
|
|
3199
|
-
// OneKey patch:
|
|
3200
|
-
|
|
3201
|
-
parseNativeListColor(imagePlaceholderColor),
|
|
3202
|
+
// OneKey patch: source-backed visuals default to no placeholder/background.
|
|
3203
|
+
if (!isIcon && sources.isNotEmpty()) Color.TRANSPARENT else parseNativeListColor(imagePlaceholderColor),
|
|
3202
3204
|
)
|
|
3203
3205
|
if (!isIcon && visual.optString("backgroundColor").isNotEmpty()) {
|
|
3204
3206
|
leadingFrame.background = roundedFill(
|
|
@@ -4149,7 +4151,7 @@ internal class NativeListRowView(
|
|
|
4149
4151
|
recyclingKey = if (retryAttempt == 0) "$token:$slot" else "$token:$slot:retry:$retryAttempt",
|
|
4150
4152
|
optimizeTos = retryAttempt == 0 && source.optBoolean("optimizeTos", true),
|
|
4151
4153
|
overscan = source.optDouble("overscan", 1.1),
|
|
4152
|
-
loadingStrategy = source.optString("loadingStrategy", "
|
|
4154
|
+
loadingStrategy = source.optString("loadingStrategy", "none"),
|
|
4153
4155
|
placeholderColor = imagePlaceholderColor,
|
|
4154
4156
|
onLoad = if (retryLimit == 0) handleLoad else ({
|
|
4155
4157
|
if (bindingEpoch == expectedEpoch) {
|
package/ios/NativeListCell.swift
CHANGED
|
@@ -3377,18 +3377,23 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3377
3377
|
let cornerIcon = visual.dictionary("cornerIcon")
|
|
3378
3378
|
let fallbackText = String(visual.string("fallbackText").prefix(2))
|
|
3379
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"
|
|
3380
3382
|
let handlesSourceFallback = !isIcon && !sources.isEmpty &&
|
|
3383
|
+
showsSourcePlaceholder &&
|
|
3381
3384
|
(visual["fallbackText"] != nil || fallbackIconData != nil)
|
|
3382
3385
|
fallbackLabel.text = handlesSourceFallback ? nil : fallbackText
|
|
3383
3386
|
let sourceFallbackBackground = currentTheme?["strongBackground"] as? String ?? "#0000000F"
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
: visual.string("backgroundColor", default: sourceFallbackBackground),
|
|
3390
|
-
fallback: .gray
|
|
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
|
|
3391
3392
|
)
|
|
3393
|
+
let visualBackgroundColor = UIColor(nativeListHex: visualBackground, fallback: .clear)
|
|
3394
|
+
leadingContainer.backgroundColor = handlesSourceFallback
|
|
3395
|
+
? UIColor(nativeListHex: sourceFallbackBackground, fallback: .clear)
|
|
3396
|
+
: visualBackgroundColor
|
|
3392
3397
|
leadingContainer.layer.cornerRadius = leadingCornerRadius(shape: shape)
|
|
3393
3398
|
leadingContainer.clipsToBounds = true
|
|
3394
3399
|
fallbackLabel.isHidden = isIcon || (!sources.isEmpty && !handlesSourceFallback)
|
|
@@ -3473,6 +3478,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3473
3478
|
imageView?.isHidden = false
|
|
3474
3479
|
self.fallbackLabel.isHidden = true
|
|
3475
3480
|
self.leadingIconImageView.isHidden = true
|
|
3481
|
+
self.leadingContainer.backgroundColor = visualBackgroundColor
|
|
3476
3482
|
},
|
|
3477
3483
|
onError: !ownsSourceFallback ? nil : { [weak self, weak imageView] in
|
|
3478
3484
|
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
@@ -4031,7 +4037,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
4031
4037
|
recyclingKey: retryAttempt == 0 ? "\(token):\(slot)" : "\(token):\(slot):retry:\(retryAttempt)",
|
|
4032
4038
|
optimizeTos: retryAttempt == 0 && (source["optimizeTos"] == nil || source.bool("optimizeTos")),
|
|
4033
4039
|
overscan: source["overscan"] == nil ? 1.1 : source.double("overscan"),
|
|
4034
|
-
loadingStrategy: source.string("loadingStrategy", default: "
|
|
4040
|
+
loadingStrategy: source.string("loadingStrategy", default: "none"),
|
|
4035
4041
|
placeholderColor: currentTheme?["strongBackground"] as? String ?? "#0000000F",
|
|
4036
4042
|
onLoad: retryLimit == 0 ? handleLoad : { [weak self] in
|
|
4037
4043
|
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
@@ -94,7 +94,10 @@ final class NativeListView: UIView {
|
|
|
94
94
|
private var interactiveReorderCompactKey: String?
|
|
95
95
|
private var interactiveReorderUsesAtomicTargeting = false
|
|
96
96
|
private var interactiveReorderTargetIndex: Int?
|
|
97
|
+
private var interactiveReorderTargetKey: String?
|
|
98
|
+
private var interactiveReorderLockedCrossAxisPosition: CGFloat?
|
|
97
99
|
private var interactiveReorderTransformedCells: [NativeListCell] = []
|
|
100
|
+
private var deferredReorderReconfigureKeys = Set<String>()
|
|
98
101
|
private let interactiveReorderPlaceholder = UIView()
|
|
99
102
|
private var interactiveReorderAnimator: UIViewPropertyAnimator?
|
|
100
103
|
private let reorderStartFeedback = UIImpactFeedbackGenerator(style: .medium)
|
|
@@ -255,6 +258,13 @@ final class NativeListView: UIView {
|
|
|
255
258
|
refreshVisibleSelection()
|
|
256
259
|
return
|
|
257
260
|
}
|
|
261
|
+
if let current = config, interactiveReorderSource != nil {
|
|
262
|
+
if canDeferSnapshotDuringInteractiveReorder(from: current, to: next) {
|
|
263
|
+
deferSnapshotDuringInteractiveReorder(from: current, to: next)
|
|
264
|
+
return
|
|
265
|
+
}
|
|
266
|
+
cancelInteractiveReorderForStructuralUpdate()
|
|
267
|
+
}
|
|
258
268
|
let oldItems = itemsByKey
|
|
259
269
|
let themeChanged = !dictionariesEqual(config?.theme, next.theme)
|
|
260
270
|
if config?.generation != next.generation { endReachedGeneration = nil }
|
|
@@ -269,11 +279,13 @@ final class NativeListView: UIView {
|
|
|
269
279
|
snapshot.appendSections([0])
|
|
270
280
|
let keys = next.items.map(\.key)
|
|
271
281
|
snapshot.appendItems(keys, toSection: 0)
|
|
272
|
-
|
|
282
|
+
var changedKeys = Set(keys.filter { key in
|
|
273
283
|
guard let old = oldItems[key], let new = itemsByKey[key] else { return false }
|
|
274
284
|
return themeChanged || old.revision != new.revision || old.content != new.content
|
|
275
|
-
}
|
|
276
|
-
|
|
285
|
+
})
|
|
286
|
+
changedKeys.formUnion(deferredReorderReconfigureKeys)
|
|
287
|
+
deferredReorderReconfigureKeys.removeAll()
|
|
288
|
+
snapshot.reconfigureItems(changedKeys.filter { itemsByKey[$0] != nil })
|
|
277
289
|
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
|
278
290
|
guard let self else { return }
|
|
279
291
|
self.collectionView.layoutIfNeeded()
|
|
@@ -300,9 +312,10 @@ final class NativeListView: UIView {
|
|
|
300
312
|
pending.append((index, changes))
|
|
301
313
|
}
|
|
302
314
|
|
|
303
|
-
var changedKeys
|
|
315
|
+
var changedKeys = Set<String>()
|
|
304
316
|
var marketQuoteKeys = Set<String>()
|
|
305
317
|
var sizeChanged = false
|
|
318
|
+
var reorderStructureChanged = false
|
|
306
319
|
let marketQuoteFields: Set<String> = [
|
|
307
320
|
"revision", "price", "priceSegments", "change", "accessibilityLabel",
|
|
308
321
|
]
|
|
@@ -313,22 +326,37 @@ final class NativeListView: UIView {
|
|
|
313
326
|
if key != "key" && key != "type" { merged[key] = value }
|
|
314
327
|
}
|
|
315
328
|
guard let item = try? NativeListItem(data: merged) else { return }
|
|
316
|
-
if rowHeight(
|
|
329
|
+
if rowHeight(previous, usingCompactReorderHeight: false) != rowHeight(item, usingCompactReorderHeight: false) ||
|
|
330
|
+
current.orientation == "horizontal" && previous.type == "rail" && railWidth(previous) != railWidth(item) ||
|
|
331
|
+
current.sectionIndexEnabled && previous.type == "sectionHeader" &&
|
|
332
|
+
previous.data.string("indexTitle").isEmpty != item.data.string("indexTitle").isEmpty {
|
|
333
|
+
sizeChanged = true
|
|
334
|
+
}
|
|
335
|
+
if previous.sectionKey != item.sectionKey ||
|
|
336
|
+
previous.isReorderable != item.isReorderable ||
|
|
337
|
+
!hasSameNestedReorderStructure(previous, item) ||
|
|
338
|
+
previous.type == "identity" &&
|
|
339
|
+
previous.data.string("presentation") != item.data.string("presentation") {
|
|
340
|
+
reorderStructureChanged = true
|
|
341
|
+
}
|
|
317
342
|
current.items[index] = item
|
|
318
343
|
if previous.type == "market", Set(changes.keys).isSubset(of: marketQuoteFields) {
|
|
319
344
|
marketQuoteKeys.insert(item.key)
|
|
320
345
|
} else {
|
|
321
|
-
changedKeys.
|
|
346
|
+
changedKeys.insert(item.key)
|
|
322
347
|
}
|
|
323
348
|
if let selected = changes["selected"] as? Bool {
|
|
324
349
|
if selected { current.selectedKeys.insert(item.key) } else { current.selectedKeys.remove(item.key) }
|
|
325
350
|
}
|
|
326
351
|
}
|
|
327
352
|
if !changedKeys.isEmpty { invalidateActionAnchor(reason: "snapshot") }
|
|
353
|
+
if interactiveReorderSource != nil, sizeChanged || reorderStructureChanged {
|
|
354
|
+
cancelInteractiveReorderForStructuralUpdate()
|
|
355
|
+
}
|
|
328
356
|
config = current
|
|
329
357
|
itemsByKey = Dictionary(uniqueKeysWithValues: current.items.map { ($0.key, $0) })
|
|
330
358
|
for indexPath in collectionView.indexPathsForVisibleItems {
|
|
331
|
-
guard let item =
|
|
359
|
+
guard let item = item(at: indexPath),
|
|
332
360
|
marketQuoteKeys.contains(item.key),
|
|
333
361
|
let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else { continue }
|
|
334
362
|
cell.updateMarketQuote(item, theme: current.theme)
|
|
@@ -339,6 +367,13 @@ final class NativeListView: UIView {
|
|
|
339
367
|
checkEndReached()
|
|
340
368
|
return
|
|
341
369
|
}
|
|
370
|
+
if interactiveReorderSource != nil {
|
|
371
|
+
deferredReorderReconfigureKeys.formUnion(changedKeys)
|
|
372
|
+
configureFooter(current)
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
changedKeys.formUnion(deferredReorderReconfigureKeys)
|
|
376
|
+
deferredReorderReconfigureKeys.removeAll()
|
|
342
377
|
var snapshot = dataSource.snapshot()
|
|
343
378
|
snapshot.reconfigureItems(changedKeys.filter { snapshot.indexOfItem($0) != nil })
|
|
344
379
|
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
|
@@ -567,6 +602,9 @@ final class NativeListView: UIView {
|
|
|
567
602
|
let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else {
|
|
568
603
|
interactiveReorderSource = nil
|
|
569
604
|
interactiveReorderCell = nil
|
|
605
|
+
interactiveReorderTargetIndex = nil
|
|
606
|
+
interactiveReorderTargetKey = nil
|
|
607
|
+
interactiveReorderLockedCrossAxisPosition = nil
|
|
570
608
|
return
|
|
571
609
|
}
|
|
572
610
|
// OneKey patch: a hidden wallet child starts dragging its whole logical group.
|
|
@@ -581,6 +619,11 @@ final class NativeListView: UIView {
|
|
|
581
619
|
item.data.string("presentation") == "walletSidebar" &&
|
|
582
620
|
current.items.contains { $0.type == "walletGroup" }
|
|
583
621
|
interactiveReorderTargetIndex = indexPath.item
|
|
622
|
+
interactiveReorderTargetKey = item.key
|
|
623
|
+
let sourceCenter = flowLayout.layoutAttributesForItem(at: indexPath)?.center ?? cell.center
|
|
624
|
+
interactiveReorderLockedCrossAxisPosition = current.orientation == "horizontal"
|
|
625
|
+
? sourceCenter.y
|
|
626
|
+
: sourceCenter.x
|
|
584
627
|
if item.type == "walletGroup" {
|
|
585
628
|
interactiveReorderCompactKey = item.key
|
|
586
629
|
cell.setWalletGroupReorderCompact(true)
|
|
@@ -596,6 +639,8 @@ final class NativeListView: UIView {
|
|
|
596
639
|
interactiveReorderCell = nil
|
|
597
640
|
interactiveReorderUsesAtomicTargeting = false
|
|
598
641
|
interactiveReorderTargetIndex = nil
|
|
642
|
+
interactiveReorderTargetKey = nil
|
|
643
|
+
interactiveReorderLockedCrossAxisPosition = nil
|
|
599
644
|
interactiveReorderFeedbackIndex = nil
|
|
600
645
|
flowLayout.invalidateLayout()
|
|
601
646
|
collectionView.layoutIfNeeded()
|
|
@@ -608,13 +653,20 @@ final class NativeListView: UIView {
|
|
|
608
653
|
showInteractiveReorderPlaceholder(at: indexPath, item: item, config: current)
|
|
609
654
|
case .changed:
|
|
610
655
|
guard interactiveReorderSource != nil else { return }
|
|
611
|
-
|
|
656
|
+
var point = gesture.location(in: collectionView)
|
|
657
|
+
if let lockedPosition = interactiveReorderLockedCrossAxisPosition {
|
|
658
|
+
if config?.orientation == "horizontal" {
|
|
659
|
+
point.y = lockedPosition
|
|
660
|
+
} else {
|
|
661
|
+
point.x = lockedPosition
|
|
662
|
+
}
|
|
663
|
+
}
|
|
612
664
|
collectionView.updateInteractiveMovementTargetPosition(point)
|
|
613
665
|
if let indexPath = nearestReorderIndexPath(to: point),
|
|
614
666
|
let item = item(at: indexPath) {
|
|
615
667
|
let targetChanged = interactiveReorderFeedbackIndex != indexPath.item
|
|
616
668
|
if interactiveReorderUsesAtomicTargeting {
|
|
617
|
-
updateAtomicReorderTarget(to: indexPath.item, animated: targetChanged)
|
|
669
|
+
updateAtomicReorderTarget(to: indexPath.item, key: item.key, animated: targetChanged)
|
|
618
670
|
}
|
|
619
671
|
if targetChanged {
|
|
620
672
|
interactiveReorderFeedbackIndex = indexPath.item
|
|
@@ -628,6 +680,7 @@ final class NativeListView: UIView {
|
|
|
628
680
|
interactiveReorderCell?.setPressed(false)
|
|
629
681
|
finishInteractiveReorder(cancelled: false)
|
|
630
682
|
case .cancelled, .failed:
|
|
683
|
+
guard interactiveReorderSource != nil else { return }
|
|
631
684
|
interactiveReorderCell?.setPressed(false)
|
|
632
685
|
finishInteractiveReorder(cancelled: true)
|
|
633
686
|
default:
|
|
@@ -636,10 +689,17 @@ final class NativeListView: UIView {
|
|
|
636
689
|
}
|
|
637
690
|
|
|
638
691
|
private func nearestReorderIndexPath(to point: CGPoint) -> IndexPath? {
|
|
639
|
-
|
|
692
|
+
let isHorizontal = config?.orientation == "horizontal"
|
|
693
|
+
return collectionView.indexPathsForVisibleItems.min { lhs, rhs in
|
|
640
694
|
let lhsFrame = flowLayout.layoutAttributesForItem(at: lhs)?.frame ?? .zero
|
|
641
695
|
let rhsFrame = flowLayout.layoutAttributesForItem(at: rhs)?.frame ?? .zero
|
|
642
|
-
|
|
696
|
+
let lhsDistance = isHorizontal
|
|
697
|
+
? abs(lhsFrame.midX - point.x)
|
|
698
|
+
: abs(lhsFrame.midY - point.y)
|
|
699
|
+
let rhsDistance = isHorizontal
|
|
700
|
+
? abs(rhsFrame.midX - point.x)
|
|
701
|
+
: abs(rhsFrame.midY - point.y)
|
|
702
|
+
return lhsDistance < rhsDistance
|
|
643
703
|
}
|
|
644
704
|
}
|
|
645
705
|
|
|
@@ -685,6 +745,7 @@ final class NativeListView: UIView {
|
|
|
685
745
|
}
|
|
686
746
|
|
|
687
747
|
private func finishInteractiveReorder(cancelled: Bool) {
|
|
748
|
+
interactiveReorderLockedCrossAxisPosition = nil
|
|
688
749
|
if interactiveReorderUsesAtomicTargeting {
|
|
689
750
|
finishAtomicInteractiveReorder(cancelled: cancelled)
|
|
690
751
|
return
|
|
@@ -715,12 +776,58 @@ final class NativeListView: UIView {
|
|
|
715
776
|
} else {
|
|
716
777
|
self.interactiveReorderSource = nil
|
|
717
778
|
self.interactiveReorderCell = nil
|
|
779
|
+
self.interactiveReorderTargetIndex = nil
|
|
780
|
+
self.interactiveReorderTargetKey = nil
|
|
781
|
+
self.scheduleDeferredReorderRefresh()
|
|
718
782
|
}
|
|
719
783
|
}
|
|
720
784
|
interactiveReorderAnimator = animator
|
|
721
785
|
animator.startAnimation()
|
|
722
786
|
}
|
|
723
787
|
|
|
788
|
+
private func cancelInteractiveReorderForStructuralUpdate() {
|
|
789
|
+
guard interactiveReorderSource != nil else { return }
|
|
790
|
+
interactiveReorderAnimator?.stopAnimation(true)
|
|
791
|
+
interactiveReorderAnimator = nil
|
|
792
|
+
collectionView.cancelInteractiveMovement()
|
|
793
|
+
interactiveReorderCell?.setPressed(false)
|
|
794
|
+
if interactiveReorderCompactKey != nil {
|
|
795
|
+
interactiveReorderCell?.setWalletGroupReorderCompact(false)
|
|
796
|
+
interactiveReorderCompactKey = nil
|
|
797
|
+
flowLayout.invalidateLayout()
|
|
798
|
+
}
|
|
799
|
+
resetAtomicReorderTransforms()
|
|
800
|
+
interactiveReorderSource = nil
|
|
801
|
+
interactiveReorderCell = nil
|
|
802
|
+
interactiveReorderFeedbackIndex = nil
|
|
803
|
+
clearAtomicInteractiveReorderState()
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
private func scheduleDeferredReorderRefresh() {
|
|
807
|
+
guard !deferredReorderReconfigureKeys.isEmpty else { return }
|
|
808
|
+
DispatchQueue.main.async { [weak self] in
|
|
809
|
+
self?.flushDeferredReorderRefresh()
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
private func flushDeferredReorderRefresh() {
|
|
814
|
+
guard interactiveReorderSource == nil,
|
|
815
|
+
interactiveReorderCompactKey == nil,
|
|
816
|
+
interactiveReorderAnimator == nil,
|
|
817
|
+
!deferredReorderReconfigureKeys.isEmpty else { return }
|
|
818
|
+
var snapshot = dataSource.snapshot()
|
|
819
|
+
let keys = deferredReorderReconfigureKeys.filter { snapshot.indexOfItem($0) != nil }
|
|
820
|
+
deferredReorderReconfigureKeys.removeAll()
|
|
821
|
+
guard !keys.isEmpty else { return }
|
|
822
|
+
snapshot.reconfigureItems(Array(keys))
|
|
823
|
+
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
|
824
|
+
guard let self else { return }
|
|
825
|
+
self.collectionView.layoutIfNeeded()
|
|
826
|
+
self.emitVisibleRangeIfNeeded()
|
|
827
|
+
self.checkEndReached()
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
724
831
|
private func completeInteractiveReorder(
|
|
725
832
|
_ snapshot: NSDiffableDataSourceSnapshot<Int, String>
|
|
726
833
|
) {
|
|
@@ -729,6 +836,10 @@ final class NativeListView: UIView {
|
|
|
729
836
|
defer {
|
|
730
837
|
interactiveReorderSource = nil
|
|
731
838
|
interactiveReorderCell = nil
|
|
839
|
+
interactiveReorderTargetIndex = nil
|
|
840
|
+
interactiveReorderTargetKey = nil
|
|
841
|
+
interactiveReorderLockedCrossAxisPosition = nil
|
|
842
|
+
scheduleDeferredReorderRefresh()
|
|
732
843
|
}
|
|
733
844
|
let keys = snapshot.itemIdentifiers
|
|
734
845
|
let items = keys.compactMap { itemsByKey[$0] }
|
|
@@ -768,6 +879,10 @@ final class NativeListView: UIView {
|
|
|
768
879
|
self.walletGroupCell(for: key)?.finishWalletGroupReorderExpansion()
|
|
769
880
|
self.interactiveReorderSource = nil
|
|
770
881
|
self.interactiveReorderCell = nil
|
|
882
|
+
self.interactiveReorderTargetIndex = nil
|
|
883
|
+
self.interactiveReorderTargetKey = nil
|
|
884
|
+
self.interactiveReorderAnimator = nil
|
|
885
|
+
self.scheduleDeferredReorderRefresh()
|
|
771
886
|
}
|
|
772
887
|
interactiveReorderAnimator = animator
|
|
773
888
|
animator.startAnimation()
|
|
@@ -785,9 +900,10 @@ final class NativeListView: UIView {
|
|
|
785
900
|
return config?.items[safe: indexPath.item]
|
|
786
901
|
}
|
|
787
902
|
|
|
788
|
-
private func updateAtomicReorderTarget(to targetIndex: Int, animated: Bool) {
|
|
903
|
+
private func updateAtomicReorderTarget(to targetIndex: Int, key targetKey: String, animated: Bool) {
|
|
789
904
|
guard let source = interactiveReorderSource else { return }
|
|
790
905
|
interactiveReorderTargetIndex = targetIndex
|
|
906
|
+
interactiveReorderTargetKey = targetKey
|
|
791
907
|
let distance = CGFloat(68) + flowLayout.minimumLineSpacing
|
|
792
908
|
let updates = { [weak self] in
|
|
793
909
|
guard let self else { return }
|
|
@@ -827,17 +943,28 @@ final class NativeListView: UIView {
|
|
|
827
943
|
|
|
828
944
|
private func finishAtomicInteractiveReorder(cancelled: Bool) {
|
|
829
945
|
interactiveReorderAnimator?.stopAnimation(true)
|
|
946
|
+
interactiveReorderAnimator = nil
|
|
830
947
|
interactiveReorderFeedbackIndex = nil
|
|
831
948
|
guard let source = interactiveReorderSource else { return }
|
|
832
|
-
let targetIndex = interactiveReorderTargetIndex ?? source.index
|
|
833
949
|
collectionView.cancelInteractiveMovement()
|
|
834
950
|
|
|
835
|
-
if !cancelled
|
|
951
|
+
if !cancelled {
|
|
836
952
|
var snapshot = dataSource.snapshot()
|
|
837
953
|
let keys = snapshot.itemIdentifiers
|
|
838
|
-
|
|
954
|
+
guard let targetKey = interactiveReorderTargetKey,
|
|
955
|
+
targetKey != source.key,
|
|
956
|
+
let sourceIndex = keys.firstIndex(of: source.key),
|
|
957
|
+
let targetIndex = keys.firstIndex(of: targetKey) else {
|
|
958
|
+
resetAtomicReorderTransforms()
|
|
959
|
+
interactiveReorderSource = nil
|
|
960
|
+
interactiveReorderCell = nil
|
|
961
|
+
clearAtomicInteractiveReorderState()
|
|
962
|
+
scheduleDeferredReorderRefresh()
|
|
963
|
+
return
|
|
964
|
+
}
|
|
965
|
+
if targetIndex < sourceIndex {
|
|
839
966
|
snapshot.moveItem(source.key, beforeItem: targetKey)
|
|
840
|
-
} else
|
|
967
|
+
} else {
|
|
841
968
|
snapshot.moveItem(source.key, afterItem: targetKey)
|
|
842
969
|
}
|
|
843
970
|
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
|
@@ -852,12 +979,15 @@ final class NativeListView: UIView {
|
|
|
852
979
|
interactiveReorderSource = nil
|
|
853
980
|
interactiveReorderCell = nil
|
|
854
981
|
clearAtomicInteractiveReorderState()
|
|
982
|
+
scheduleDeferredReorderRefresh()
|
|
855
983
|
}
|
|
856
984
|
}
|
|
857
985
|
|
|
858
986
|
private func clearAtomicInteractiveReorderState() {
|
|
859
987
|
interactiveReorderUsesAtomicTargeting = false
|
|
860
988
|
interactiveReorderTargetIndex = nil
|
|
989
|
+
interactiveReorderTargetKey = nil
|
|
990
|
+
interactiveReorderLockedCrossAxisPosition = nil
|
|
861
991
|
interactiveReorderPlaceholder.isHidden = true
|
|
862
992
|
interactiveReorderPlaceholder.alpha = 1
|
|
863
993
|
}
|
|
@@ -1131,7 +1261,7 @@ final class NativeListView: UIView {
|
|
|
1131
1261
|
self?.resolveCheckboxState(item: item, target: target, fallback: fallback) ?? fallback
|
|
1132
1262
|
}
|
|
1133
1263
|
for indexPath in collectionView.indexPathsForVisibleItems {
|
|
1134
|
-
guard let item =
|
|
1264
|
+
guard let item = item(at: indexPath),
|
|
1135
1265
|
let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else { continue }
|
|
1136
1266
|
if changedSummaryKeys.contains(item.key) {
|
|
1137
1267
|
bind(cell: cell, item: item, itemIndex: indexPath.item)
|
|
@@ -1200,6 +1330,101 @@ final class NativeListView: UIView {
|
|
|
1200
1330
|
}
|
|
1201
1331
|
}
|
|
1202
1332
|
|
|
1333
|
+
private func canDeferSnapshotDuringInteractiveReorder(
|
|
1334
|
+
from current: NativeListConfig,
|
|
1335
|
+
to next: NativeListConfig
|
|
1336
|
+
) -> Bool {
|
|
1337
|
+
guard current.layout == next.layout,
|
|
1338
|
+
current.orientation == next.orientation,
|
|
1339
|
+
current.gridColumns == next.gridColumns,
|
|
1340
|
+
current.stickyHeaders == next.stickyHeaders,
|
|
1341
|
+
current.contentPadding == next.contentPadding,
|
|
1342
|
+
current.contentPaddingHorizontal == next.contentPaddingHorizontal,
|
|
1343
|
+
current.contentPaddingTop == next.contentPaddingTop,
|
|
1344
|
+
current.contentPaddingBottom == next.contentPaddingBottom,
|
|
1345
|
+
current.itemSpacing == next.itemSpacing,
|
|
1346
|
+
current.reorderable == next.reorderable,
|
|
1347
|
+
current.sectionIndexEnabled == next.sectionIndexEnabled,
|
|
1348
|
+
current.items.count == next.items.count,
|
|
1349
|
+
hasSameFooterGeometry(current.fixedFooter, next.fixedFooter) else { return false }
|
|
1350
|
+
|
|
1351
|
+
return zip(current.items, next.items).allSatisfy { old, new in
|
|
1352
|
+
guard old.key == new.key,
|
|
1353
|
+
old.type == new.type,
|
|
1354
|
+
old.sectionKey == new.sectionKey,
|
|
1355
|
+
old.isReorderable == new.isReorderable,
|
|
1356
|
+
hasSameNestedReorderStructure(old, new),
|
|
1357
|
+
rowHeight(old, usingCompactReorderHeight: false) == rowHeight(new, usingCompactReorderHeight: false) else {
|
|
1358
|
+
return false
|
|
1359
|
+
}
|
|
1360
|
+
if current.orientation == "horizontal", old.type == "rail", railWidth(old) != railWidth(new) {
|
|
1361
|
+
return false
|
|
1362
|
+
}
|
|
1363
|
+
if old.type == "identity", old.data.string("presentation") != new.data.string("presentation") {
|
|
1364
|
+
return false
|
|
1365
|
+
}
|
|
1366
|
+
if current.sectionIndexEnabled,
|
|
1367
|
+
old.type == "sectionHeader",
|
|
1368
|
+
old.data.string("indexTitle").isEmpty != new.data.string("indexTitle").isEmpty {
|
|
1369
|
+
return false
|
|
1370
|
+
}
|
|
1371
|
+
return true
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
private func hasSameNestedReorderStructure(_ current: NativeListItem, _ next: NativeListItem) -> Bool {
|
|
1376
|
+
guard current.type == "walletGroup" else { return true }
|
|
1377
|
+
guard let currentParent = current.data.dictionary("parent"),
|
|
1378
|
+
let nextParent = next.data.dictionary("parent") else { return false }
|
|
1379
|
+
let currentMembers = [currentParent] + current.data.dictionaries("children")
|
|
1380
|
+
let nextMembers = [nextParent] + next.data.dictionaries("children")
|
|
1381
|
+
guard currentMembers.count == nextMembers.count else { return false }
|
|
1382
|
+
return zip(currentMembers, nextMembers).allSatisfy { old, new in
|
|
1383
|
+
old.string("key") == new.string("key") &&
|
|
1384
|
+
old.string("type") == new.string("type") &&
|
|
1385
|
+
old.string("sectionKey") == new.string("sectionKey") &&
|
|
1386
|
+
old.string("presentation") == new.string("presentation")
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
private func hasSameFooterGeometry(_ current: NativeListItem?, _ next: NativeListItem?) -> Bool {
|
|
1391
|
+
switch (current, next) {
|
|
1392
|
+
case (nil, nil):
|
|
1393
|
+
return true
|
|
1394
|
+
case let (current?, next?):
|
|
1395
|
+
return current.key == next.key &&
|
|
1396
|
+
current.type == next.type &&
|
|
1397
|
+
rowHeight(current, usingCompactReorderHeight: false) == rowHeight(next, usingCompactReorderHeight: false)
|
|
1398
|
+
default:
|
|
1399
|
+
return false
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
private func deferSnapshotDuringInteractiveReorder(
|
|
1404
|
+
from current: NativeListConfig,
|
|
1405
|
+
to next: NativeListConfig
|
|
1406
|
+
) {
|
|
1407
|
+
let oldItems = itemsByKey
|
|
1408
|
+
let themeChanged = !dictionariesEqual(current.theme, next.theme)
|
|
1409
|
+
let selectionChanged = current.selectionMode != next.selectionMode ||
|
|
1410
|
+
current.selectedKeys != next.selectedKeys
|
|
1411
|
+
if current.generation != next.generation { endReachedGeneration = nil }
|
|
1412
|
+
config = next
|
|
1413
|
+
itemsByKey = Dictionary(uniqueKeysWithValues: next.items.map { ($0.key, $0) })
|
|
1414
|
+
let changedKeys = next.items.compactMap { item -> String? in
|
|
1415
|
+
guard let old = oldItems[item.key] else { return nil }
|
|
1416
|
+
return themeChanged || selectionChanged || old.revision != item.revision || old.content != item.content
|
|
1417
|
+
? item.key
|
|
1418
|
+
: nil
|
|
1419
|
+
}
|
|
1420
|
+
deferredReorderReconfigureKeys.formUnion(changedKeys)
|
|
1421
|
+
configureSectionIndex(next)
|
|
1422
|
+
configureRefresh(next)
|
|
1423
|
+
configureFooter(next)
|
|
1424
|
+
emitVisibleRangeIfNeeded()
|
|
1425
|
+
checkEndReached()
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1203
1428
|
// OneKey patch: remove only fields that the existing lightweight binder refreshes.
|
|
1204
1429
|
private func selectionComparisonData(_ data: [String: Any], controlled: Bool) -> [String: Any]? {
|
|
1205
1430
|
guard let type = data["type"] as? String else { return nil }
|
|
@@ -1263,7 +1488,10 @@ final class NativeListView: UIView {
|
|
|
1263
1488
|
return try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys])
|
|
1264
1489
|
}
|
|
1265
1490
|
|
|
1266
|
-
private func rowHeight(
|
|
1491
|
+
private func rowHeight(
|
|
1492
|
+
_ item: NativeListItem,
|
|
1493
|
+
usingCompactReorderHeight: Bool = true
|
|
1494
|
+
) -> CGFloat {
|
|
1267
1495
|
// OneKey patch: honor selector baseline geometry; keep compact drag sizing.
|
|
1268
1496
|
if item.type != "walletGroup", item.data["height"] != nil { return CGFloat(item.data.double("height")) }
|
|
1269
1497
|
if item.type == "system", item.data.string("variant") == "spacer" {
|
|
@@ -1281,7 +1509,7 @@ final class NativeListView: UIView {
|
|
|
1281
1509
|
return 32 + textHeight("title", weight: .medium) + textHeight("message", weight: .regular)
|
|
1282
1510
|
}
|
|
1283
1511
|
if item.type == "walletGroup" {
|
|
1284
|
-
if item.key == interactiveReorderCompactKey { return 68 }
|
|
1512
|
+
if usingCompactReorderHeight, item.key == interactiveReorderCompactKey { return 68 }
|
|
1285
1513
|
let childCount = item.data.dictionaries("children").count
|
|
1286
1514
|
// OneKey patch: wallet badges contribute their own member heights.
|
|
1287
1515
|
// return CGFloat((childCount + 1) * 68 + childCount * 12)
|
|
@@ -982,20 +982,27 @@ function createVisual(context, visual, selectorPresentation) {
|
|
|
982
982
|
return frame;
|
|
983
983
|
}
|
|
984
984
|
|
|
985
|
-
// OneKey patch:
|
|
986
|
-
//
|
|
987
|
-
|
|
988
|
-
frame.style.background = 'backgroundColor' in visual && visual.backgroundColor ? visual.backgroundColor : 'var(--nl-strong)';
|
|
985
|
+
// OneKey patch: NativeList images are usually local and should not flash a
|
|
986
|
+
// placeholder. Callers can opt in with image.loadingStrategy.
|
|
987
|
+
const visualBackground = 'backgroundColor' in visual && visual.backgroundColor ? visual.backgroundColor : 'transparent';
|
|
989
988
|
const source = visual.kind === 'image' ? visual.image : visual.image;
|
|
989
|
+
const showsSourcePlaceholder = !!source && source.loadingStrategy !== undefined && source.loadingStrategy !== 'none';
|
|
990
|
+
frame.style.background = showsSourcePlaceholder ? 'var(--nl-strong)' : visualBackground;
|
|
990
991
|
const fallbackIcon = 'fallbackIcon' in visual ? visual.fallbackIcon : undefined;
|
|
991
|
-
const handlesSourceFallback =
|
|
992
|
-
if (handlesSourceFallback) frame.style.background = 'var(--nl-strong)';
|
|
992
|
+
const handlesSourceFallback = showsSourcePlaceholder && (fallbackIcon !== undefined || 'fallbackText' in visual);
|
|
993
993
|
const image = source ? createImage(context, source) : undefined;
|
|
994
994
|
if (image) {
|
|
995
995
|
image.className = 'ok-native-list-visual-main';
|
|
996
996
|
frame.appendChild(image);
|
|
997
|
+
if (showsSourcePlaceholder) {
|
|
998
|
+
const restoreBackground = () => {
|
|
999
|
+
frame.style.background = visualBackground;
|
|
1000
|
+
};
|
|
1001
|
+
image.addEventListener('load', restoreBackground);
|
|
1002
|
+
if (image.complete && image.naturalWidth > 0) restoreBackground();
|
|
1003
|
+
}
|
|
997
1004
|
if (selectorPresentation) paintSelectorImageBackground(image, frame);
|
|
998
|
-
} else {
|
|
1005
|
+
} else if (!source || showsSourcePlaceholder) {
|
|
999
1006
|
frame.appendChild(createElement(context.document, 'span', 'ok-native-list-visual-fallback', 'fallbackText' in visual ? visual.fallbackText ?? '' : ''));
|
|
1000
1007
|
}
|
|
1001
1008
|
if (visual.kind === 'token' && visual.networkImage) {
|
|
@@ -1010,7 +1017,7 @@ function createVisual(context, visual, selectorPresentation) {
|
|
|
1010
1017
|
}
|
|
1011
1018
|
// OneKey patch: image failure uses the same source-derived fallback as v1.
|
|
1012
1019
|
const hasFallbackText = 'fallbackText' in visual;
|
|
1013
|
-
if (fallbackIcon || hasFallbackText) {
|
|
1020
|
+
if ((fallbackIcon || hasFallbackText) && (!source || handlesSourceFallback)) {
|
|
1014
1021
|
const showFallback = () => {
|
|
1015
1022
|
if (image) {
|
|
1016
1023
|
disposeWebImageRetries(image);
|
|
@@ -2563,8 +2570,25 @@ export class NativeListWebEngine {
|
|
|
2563
2570
|
sectionIndexMetrics(viewportHeight) {
|
|
2564
2571
|
const availableHeight = Math.max(0, viewportHeight - SECTION_INDEX_EDGE_PADDING * 2);
|
|
2565
2572
|
const trackHeight = Math.min(availableHeight, SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length);
|
|
2573
|
+
const centeredOriginY = (viewportHeight - trackHeight) / 2;
|
|
2574
|
+
if (!this.snapshot.capabilities?.sectionIndex?.centeredInWindow) {
|
|
2575
|
+
return {
|
|
2576
|
+
originY: centeredOriginY,
|
|
2577
|
+
trackHeight
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
const view = this.document.defaultView;
|
|
2581
|
+
if (!view) return {
|
|
2582
|
+
originY: centeredOriginY,
|
|
2583
|
+
trackHeight
|
|
2584
|
+
};
|
|
2585
|
+
const visualViewport = view.visualViewport;
|
|
2586
|
+
const windowCenterY = visualViewport ? visualViewport.offsetTop + visualViewport.height / 2 : view.innerHeight / 2;
|
|
2587
|
+
const railTop = this.viewportFrame.getBoundingClientRect().top + (Number.parseFloat(this.indexRail.style.top) || 0);
|
|
2588
|
+
const minOriginY = SECTION_INDEX_EDGE_PADDING;
|
|
2589
|
+
const maxOriginY = Math.max(minOriginY, viewportHeight - SECTION_INDEX_EDGE_PADDING - trackHeight);
|
|
2566
2590
|
return {
|
|
2567
|
-
originY: (
|
|
2591
|
+
originY: Math.min(maxOriginY, Math.max(minOriginY, windowCenterY - railTop - trackHeight / 2)),
|
|
2568
2592
|
trackHeight
|
|
2569
2593
|
};
|
|
2570
2594
|
}
|
|
@@ -19,6 +19,7 @@ export type ImageSource = Readonly<{
|
|
|
19
19
|
autoplay?: boolean;
|
|
20
20
|
optimizeTos?: boolean;
|
|
21
21
|
overscan?: number;
|
|
22
|
+
/** Defaults to `none` in NativeList; use `static` or `skeleton` to opt into loading UI. */
|
|
22
23
|
loadingStrategy?: ImageLoadingStrategy;
|
|
23
24
|
fallbackUri?: string;
|
|
24
25
|
retryTimes?: number;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-native-list",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.120",
|
|
4
4
|
"description": "Template-driven native RecyclerView and UICollectionView for React Native",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"main": "./lib/module/index.js",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"typescript": "^5.9.2"
|
|
84
84
|
},
|
|
85
85
|
"peerDependencies": {
|
|
86
|
-
"@onekeyfe/react-native-image": "3.0.
|
|
87
|
-
"@onekeyfe/react-native-native-logger": "3.0.
|
|
86
|
+
"@onekeyfe/react-native-image": "3.0.120",
|
|
87
|
+
"@onekeyfe/react-native-native-logger": "3.0.120",
|
|
88
88
|
"react": "*",
|
|
89
89
|
"react-native": "*",
|
|
90
90
|
"react-native-nitro-modules": "0.37.0"
|
package/src/models.ts
CHANGED
|
@@ -21,6 +21,7 @@ export type ImageSource = Readonly<{
|
|
|
21
21
|
autoplay?: boolean;
|
|
22
22
|
optimizeTos?: boolean;
|
|
23
23
|
overscan?: number;
|
|
24
|
+
/** Defaults to `none` in NativeList; use `static` or `skeleton` to opt into loading UI. */
|
|
24
25
|
loadingStrategy?: ImageLoadingStrategy;
|
|
25
26
|
// OneKey patch: fallbackUri is Web-only; retryTimes opts all platforms into terminal retries.
|
|
26
27
|
fallbackUri?: string;
|
|
@@ -1557,25 +1557,38 @@ function createVisual(
|
|
|
1557
1557
|
return frame;
|
|
1558
1558
|
}
|
|
1559
1559
|
|
|
1560
|
-
// OneKey patch:
|
|
1561
|
-
//
|
|
1562
|
-
|
|
1563
|
-
frame.style.background =
|
|
1560
|
+
// OneKey patch: NativeList images are usually local and should not flash a
|
|
1561
|
+
// placeholder. Callers can opt in with image.loadingStrategy.
|
|
1562
|
+
const visualBackground =
|
|
1564
1563
|
'backgroundColor' in visual && visual.backgroundColor
|
|
1565
1564
|
? visual.backgroundColor
|
|
1566
|
-
: '
|
|
1565
|
+
: 'transparent';
|
|
1567
1566
|
const source = visual.kind === 'image' ? visual.image : visual.image;
|
|
1567
|
+
const showsSourcePlaceholder =
|
|
1568
|
+
!!source &&
|
|
1569
|
+
source.loadingStrategy !== undefined &&
|
|
1570
|
+
source.loadingStrategy !== 'none';
|
|
1571
|
+
frame.style.background = showsSourcePlaceholder
|
|
1572
|
+
? 'var(--nl-strong)'
|
|
1573
|
+
: visualBackground;
|
|
1568
1574
|
const fallbackIcon =
|
|
1569
1575
|
'fallbackIcon' in visual ? visual.fallbackIcon : undefined;
|
|
1570
1576
|
const handlesSourceFallback =
|
|
1571
|
-
|
|
1572
|
-
|
|
1577
|
+
showsSourcePlaceholder &&
|
|
1578
|
+
(fallbackIcon !== undefined || 'fallbackText' in visual);
|
|
1573
1579
|
const image = source ? createImage(context, source) : undefined;
|
|
1574
1580
|
if (image) {
|
|
1575
1581
|
image.className = 'ok-native-list-visual-main';
|
|
1576
1582
|
frame.appendChild(image);
|
|
1583
|
+
if (showsSourcePlaceholder) {
|
|
1584
|
+
const restoreBackground = () => {
|
|
1585
|
+
frame.style.background = visualBackground;
|
|
1586
|
+
};
|
|
1587
|
+
image.addEventListener('load', restoreBackground);
|
|
1588
|
+
if (image.complete && image.naturalWidth > 0) restoreBackground();
|
|
1589
|
+
}
|
|
1577
1590
|
if (selectorPresentation) paintSelectorImageBackground(image, frame);
|
|
1578
|
-
} else {
|
|
1591
|
+
} else if (!source || showsSourcePlaceholder) {
|
|
1579
1592
|
frame.appendChild(
|
|
1580
1593
|
createElement(
|
|
1581
1594
|
context.document,
|
|
@@ -1608,7 +1621,7 @@ function createVisual(
|
|
|
1608
1621
|
}
|
|
1609
1622
|
// OneKey patch: image failure uses the same source-derived fallback as v1.
|
|
1610
1623
|
const hasFallbackText = 'fallbackText' in visual;
|
|
1611
|
-
if (fallbackIcon || hasFallbackText) {
|
|
1624
|
+
if ((fallbackIcon || hasFallbackText) && (!source || handlesSourceFallback)) {
|
|
1612
1625
|
const showFallback = () => {
|
|
1613
1626
|
if (image) {
|
|
1614
1627
|
disposeWebImageRetries(image);
|
|
@@ -4320,8 +4333,29 @@ export class NativeListWebEngine {
|
|
|
4320
4333
|
availableHeight,
|
|
4321
4334
|
SECTION_INDEX_LABEL_SPACING * this.sectionIndexEntries.length
|
|
4322
4335
|
);
|
|
4336
|
+
const centeredOriginY = (viewportHeight - trackHeight) / 2;
|
|
4337
|
+
if (!this.snapshot.capabilities?.sectionIndex?.centeredInWindow) {
|
|
4338
|
+
return { originY: centeredOriginY, trackHeight };
|
|
4339
|
+
}
|
|
4340
|
+
const view = this.document.defaultView;
|
|
4341
|
+
if (!view) return { originY: centeredOriginY, trackHeight };
|
|
4342
|
+
const visualViewport = view.visualViewport;
|
|
4343
|
+
const windowCenterY = visualViewport
|
|
4344
|
+
? visualViewport.offsetTop + visualViewport.height / 2
|
|
4345
|
+
: view.innerHeight / 2;
|
|
4346
|
+
const railTop =
|
|
4347
|
+
this.viewportFrame.getBoundingClientRect().top +
|
|
4348
|
+
(Number.parseFloat(this.indexRail.style.top) || 0);
|
|
4349
|
+
const minOriginY = SECTION_INDEX_EDGE_PADDING;
|
|
4350
|
+
const maxOriginY = Math.max(
|
|
4351
|
+
minOriginY,
|
|
4352
|
+
viewportHeight - SECTION_INDEX_EDGE_PADDING - trackHeight
|
|
4353
|
+
);
|
|
4323
4354
|
return {
|
|
4324
|
-
originY: (
|
|
4355
|
+
originY: Math.min(
|
|
4356
|
+
maxOriginY,
|
|
4357
|
+
Math.max(minOriginY, windowCenterY - railTop - trackHeight / 2)
|
|
4358
|
+
),
|
|
4325
4359
|
trackHeight,
|
|
4326
4360
|
};
|
|
4327
4361
|
}
|