@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.
@@ -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)
@@ -248,14 +251,20 @@ final class NativeListView: UIView {
248
251
  guard let next = try? NativeListConfig.parse(json: json) else { return }
249
252
  invalidateActionAnchor(reason: "snapshot")
250
253
  if let current = config, isControlledSelectionSnapshotUpdate(from: current, to: next) {
251
- let changedSummaryKeys = Set(zip(current.items, next.items).compactMap { old, new in
252
- old.content != new.content ? new.key : nil
253
- })
254
254
  config = next
255
255
  itemsByKey = Dictionary(uniqueKeysWithValues: next.items.map { ($0.key, $0) })
256
- refreshVisibleSelection(changedSummaryKeys: changedSummaryKeys)
256
+ // OneKey patch: A controlled selection echo is fully handled by the
257
+ // lightweight updater. Rebinding unchanged visuals clears cached images.
258
+ refreshVisibleSelection()
257
259
  return
258
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
+ }
259
268
  let oldItems = itemsByKey
260
269
  let themeChanged = !dictionariesEqual(config?.theme, next.theme)
261
270
  if config?.generation != next.generation { endReachedGeneration = nil }
@@ -270,11 +279,13 @@ final class NativeListView: UIView {
270
279
  snapshot.appendSections([0])
271
280
  let keys = next.items.map(\.key)
272
281
  snapshot.appendItems(keys, toSection: 0)
273
- let changedKeys = keys.filter { key in
282
+ var changedKeys = Set(keys.filter { key in
274
283
  guard let old = oldItems[key], let new = itemsByKey[key] else { return false }
275
284
  return themeChanged || old.revision != new.revision || old.content != new.content
276
- }
277
- snapshot.reconfigureItems(changedKeys)
285
+ })
286
+ changedKeys.formUnion(deferredReorderReconfigureKeys)
287
+ deferredReorderReconfigureKeys.removeAll()
288
+ snapshot.reconfigureItems(changedKeys.filter { itemsByKey[$0] != nil })
278
289
  dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
279
290
  guard let self else { return }
280
291
  self.collectionView.layoutIfNeeded()
@@ -301,9 +312,10 @@ final class NativeListView: UIView {
301
312
  pending.append((index, changes))
302
313
  }
303
314
 
304
- var changedKeys: [String] = []
315
+ var changedKeys = Set<String>()
305
316
  var marketQuoteKeys = Set<String>()
306
317
  var sizeChanged = false
318
+ var reorderStructureChanged = false
307
319
  let marketQuoteFields: Set<String> = [
308
320
  "revision", "price", "priceSegments", "change", "accessibilityLabel",
309
321
  ]
@@ -314,22 +326,37 @@ final class NativeListView: UIView {
314
326
  if key != "key" && key != "type" { merged[key] = value }
315
327
  }
316
328
  guard let item = try? NativeListItem(data: merged) else { return }
317
- if rowHeight(current.items[index]) != rowHeight(item) { sizeChanged = true }
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
+ }
318
342
  current.items[index] = item
319
343
  if previous.type == "market", Set(changes.keys).isSubset(of: marketQuoteFields) {
320
344
  marketQuoteKeys.insert(item.key)
321
345
  } else {
322
- changedKeys.append(item.key)
346
+ changedKeys.insert(item.key)
323
347
  }
324
348
  if let selected = changes["selected"] as? Bool {
325
349
  if selected { current.selectedKeys.insert(item.key) } else { current.selectedKeys.remove(item.key) }
326
350
  }
327
351
  }
328
352
  if !changedKeys.isEmpty { invalidateActionAnchor(reason: "snapshot") }
353
+ if interactiveReorderSource != nil, sizeChanged || reorderStructureChanged {
354
+ cancelInteractiveReorderForStructuralUpdate()
355
+ }
329
356
  config = current
330
357
  itemsByKey = Dictionary(uniqueKeysWithValues: current.items.map { ($0.key, $0) })
331
358
  for indexPath in collectionView.indexPathsForVisibleItems {
332
- guard let item = current.items[safe: indexPath.item],
359
+ guard let item = item(at: indexPath),
333
360
  marketQuoteKeys.contains(item.key),
334
361
  let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else { continue }
335
362
  cell.updateMarketQuote(item, theme: current.theme)
@@ -340,6 +367,13 @@ final class NativeListView: UIView {
340
367
  checkEndReached()
341
368
  return
342
369
  }
370
+ if interactiveReorderSource != nil {
371
+ deferredReorderReconfigureKeys.formUnion(changedKeys)
372
+ configureFooter(current)
373
+ return
374
+ }
375
+ changedKeys.formUnion(deferredReorderReconfigureKeys)
376
+ deferredReorderReconfigureKeys.removeAll()
343
377
  var snapshot = dataSource.snapshot()
344
378
  snapshot.reconfigureItems(changedKeys.filter { snapshot.indexOfItem($0) != nil })
345
379
  dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
@@ -568,6 +602,9 @@ final class NativeListView: UIView {
568
602
  let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else {
569
603
  interactiveReorderSource = nil
570
604
  interactiveReorderCell = nil
605
+ interactiveReorderTargetIndex = nil
606
+ interactiveReorderTargetKey = nil
607
+ interactiveReorderLockedCrossAxisPosition = nil
571
608
  return
572
609
  }
573
610
  // OneKey patch: a hidden wallet child starts dragging its whole logical group.
@@ -582,6 +619,11 @@ final class NativeListView: UIView {
582
619
  item.data.string("presentation") == "walletSidebar" &&
583
620
  current.items.contains { $0.type == "walletGroup" }
584
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
585
627
  if item.type == "walletGroup" {
586
628
  interactiveReorderCompactKey = item.key
587
629
  cell.setWalletGroupReorderCompact(true)
@@ -597,6 +639,8 @@ final class NativeListView: UIView {
597
639
  interactiveReorderCell = nil
598
640
  interactiveReorderUsesAtomicTargeting = false
599
641
  interactiveReorderTargetIndex = nil
642
+ interactiveReorderTargetKey = nil
643
+ interactiveReorderLockedCrossAxisPosition = nil
600
644
  interactiveReorderFeedbackIndex = nil
601
645
  flowLayout.invalidateLayout()
602
646
  collectionView.layoutIfNeeded()
@@ -609,13 +653,20 @@ final class NativeListView: UIView {
609
653
  showInteractiveReorderPlaceholder(at: indexPath, item: item, config: current)
610
654
  case .changed:
611
655
  guard interactiveReorderSource != nil else { return }
612
- let point = gesture.location(in: collectionView)
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
+ }
613
664
  collectionView.updateInteractiveMovementTargetPosition(point)
614
665
  if let indexPath = nearestReorderIndexPath(to: point),
615
666
  let item = item(at: indexPath) {
616
667
  let targetChanged = interactiveReorderFeedbackIndex != indexPath.item
617
668
  if interactiveReorderUsesAtomicTargeting {
618
- updateAtomicReorderTarget(to: indexPath.item, animated: targetChanged)
669
+ updateAtomicReorderTarget(to: indexPath.item, key: item.key, animated: targetChanged)
619
670
  }
620
671
  if targetChanged {
621
672
  interactiveReorderFeedbackIndex = indexPath.item
@@ -629,6 +680,7 @@ final class NativeListView: UIView {
629
680
  interactiveReorderCell?.setPressed(false)
630
681
  finishInteractiveReorder(cancelled: false)
631
682
  case .cancelled, .failed:
683
+ guard interactiveReorderSource != nil else { return }
632
684
  interactiveReorderCell?.setPressed(false)
633
685
  finishInteractiveReorder(cancelled: true)
634
686
  default:
@@ -637,10 +689,17 @@ final class NativeListView: UIView {
637
689
  }
638
690
 
639
691
  private func nearestReorderIndexPath(to point: CGPoint) -> IndexPath? {
640
- collectionView.indexPathsForVisibleItems.min { lhs, rhs in
692
+ let isHorizontal = config?.orientation == "horizontal"
693
+ return collectionView.indexPathsForVisibleItems.min { lhs, rhs in
641
694
  let lhsFrame = flowLayout.layoutAttributesForItem(at: lhs)?.frame ?? .zero
642
695
  let rhsFrame = flowLayout.layoutAttributesForItem(at: rhs)?.frame ?? .zero
643
- return abs(lhsFrame.midY - point.y) < abs(rhsFrame.midY - point.y)
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
644
703
  }
645
704
  }
646
705
 
@@ -686,6 +745,7 @@ final class NativeListView: UIView {
686
745
  }
687
746
 
688
747
  private func finishInteractiveReorder(cancelled: Bool) {
748
+ interactiveReorderLockedCrossAxisPosition = nil
689
749
  if interactiveReorderUsesAtomicTargeting {
690
750
  finishAtomicInteractiveReorder(cancelled: cancelled)
691
751
  return
@@ -716,12 +776,58 @@ final class NativeListView: UIView {
716
776
  } else {
717
777
  self.interactiveReorderSource = nil
718
778
  self.interactiveReorderCell = nil
779
+ self.interactiveReorderTargetIndex = nil
780
+ self.interactiveReorderTargetKey = nil
781
+ self.scheduleDeferredReorderRefresh()
719
782
  }
720
783
  }
721
784
  interactiveReorderAnimator = animator
722
785
  animator.startAnimation()
723
786
  }
724
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
+
725
831
  private func completeInteractiveReorder(
726
832
  _ snapshot: NSDiffableDataSourceSnapshot<Int, String>
727
833
  ) {
@@ -730,6 +836,10 @@ final class NativeListView: UIView {
730
836
  defer {
731
837
  interactiveReorderSource = nil
732
838
  interactiveReorderCell = nil
839
+ interactiveReorderTargetIndex = nil
840
+ interactiveReorderTargetKey = nil
841
+ interactiveReorderLockedCrossAxisPosition = nil
842
+ scheduleDeferredReorderRefresh()
733
843
  }
734
844
  let keys = snapshot.itemIdentifiers
735
845
  let items = keys.compactMap { itemsByKey[$0] }
@@ -769,6 +879,10 @@ final class NativeListView: UIView {
769
879
  self.walletGroupCell(for: key)?.finishWalletGroupReorderExpansion()
770
880
  self.interactiveReorderSource = nil
771
881
  self.interactiveReorderCell = nil
882
+ self.interactiveReorderTargetIndex = nil
883
+ self.interactiveReorderTargetKey = nil
884
+ self.interactiveReorderAnimator = nil
885
+ self.scheduleDeferredReorderRefresh()
772
886
  }
773
887
  interactiveReorderAnimator = animator
774
888
  animator.startAnimation()
@@ -786,9 +900,10 @@ final class NativeListView: UIView {
786
900
  return config?.items[safe: indexPath.item]
787
901
  }
788
902
 
789
- private func updateAtomicReorderTarget(to targetIndex: Int, animated: Bool) {
903
+ private func updateAtomicReorderTarget(to targetIndex: Int, key targetKey: String, animated: Bool) {
790
904
  guard let source = interactiveReorderSource else { return }
791
905
  interactiveReorderTargetIndex = targetIndex
906
+ interactiveReorderTargetKey = targetKey
792
907
  let distance = CGFloat(68) + flowLayout.minimumLineSpacing
793
908
  let updates = { [weak self] in
794
909
  guard let self else { return }
@@ -828,17 +943,28 @@ final class NativeListView: UIView {
828
943
 
829
944
  private func finishAtomicInteractiveReorder(cancelled: Bool) {
830
945
  interactiveReorderAnimator?.stopAnimation(true)
946
+ interactiveReorderAnimator = nil
831
947
  interactiveReorderFeedbackIndex = nil
832
948
  guard let source = interactiveReorderSource else { return }
833
- let targetIndex = interactiveReorderTargetIndex ?? source.index
834
949
  collectionView.cancelInteractiveMovement()
835
950
 
836
- if !cancelled, targetIndex != source.index {
951
+ if !cancelled {
837
952
  var snapshot = dataSource.snapshot()
838
953
  let keys = snapshot.itemIdentifiers
839
- if targetIndex < source.index, let targetKey = keys[safe: targetIndex] {
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 {
840
966
  snapshot.moveItem(source.key, beforeItem: targetKey)
841
- } else if let targetKey = keys[safe: targetIndex] {
967
+ } else {
842
968
  snapshot.moveItem(source.key, afterItem: targetKey)
843
969
  }
844
970
  dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
@@ -853,12 +979,15 @@ final class NativeListView: UIView {
853
979
  interactiveReorderSource = nil
854
980
  interactiveReorderCell = nil
855
981
  clearAtomicInteractiveReorderState()
982
+ scheduleDeferredReorderRefresh()
856
983
  }
857
984
  }
858
985
 
859
986
  private func clearAtomicInteractiveReorderState() {
860
987
  interactiveReorderUsesAtomicTargeting = false
861
988
  interactiveReorderTargetIndex = nil
989
+ interactiveReorderTargetKey = nil
990
+ interactiveReorderLockedCrossAxisPosition = nil
862
991
  interactiveReorderPlaceholder.isHidden = true
863
992
  interactiveReorderPlaceholder.alpha = 1
864
993
  }
@@ -1132,7 +1261,7 @@ final class NativeListView: UIView {
1132
1261
  self?.resolveCheckboxState(item: item, target: target, fallback: fallback) ?? fallback
1133
1262
  }
1134
1263
  for indexPath in collectionView.indexPathsForVisibleItems {
1135
- guard let item = config.items[safe: indexPath.item],
1264
+ guard let item = item(at: indexPath),
1136
1265
  let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else { continue }
1137
1266
  if changedSummaryKeys.contains(item.key) {
1138
1267
  bind(cell: cell, item: item, itemIndex: indexPath.item)
@@ -1201,6 +1330,101 @@ final class NativeListView: UIView {
1201
1330
  }
1202
1331
  }
1203
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
+
1204
1428
  // OneKey patch: remove only fields that the existing lightweight binder refreshes.
1205
1429
  private func selectionComparisonData(_ data: [String: Any], controlled: Bool) -> [String: Any]? {
1206
1430
  guard let type = data["type"] as? String else { return nil }
@@ -1264,7 +1488,10 @@ final class NativeListView: UIView {
1264
1488
  return try? JSONSerialization.data(withJSONObject: value, options: [.sortedKeys])
1265
1489
  }
1266
1490
 
1267
- private func rowHeight(_ item: NativeListItem) -> CGFloat {
1491
+ private func rowHeight(
1492
+ _ item: NativeListItem,
1493
+ usingCompactReorderHeight: Bool = true
1494
+ ) -> CGFloat {
1268
1495
  // OneKey patch: honor selector baseline geometry; keep compact drag sizing.
1269
1496
  if item.type != "walletGroup", item.data["height"] != nil { return CGFloat(item.data.double("height")) }
1270
1497
  if item.type == "system", item.data.string("variant") == "spacer" {
@@ -1282,7 +1509,7 @@ final class NativeListView: UIView {
1282
1509
  return 32 + textHeight("title", weight: .medium) + textHeight("message", weight: .regular)
1283
1510
  }
1284
1511
  if item.type == "walletGroup" {
1285
- if item.key == interactiveReorderCompactKey { return 68 }
1512
+ if usingCompactReorderHeight, item.key == interactiveReorderCompactKey { return 68 }
1286
1513
  let childCount = item.data.dictionaries("children").count
1287
1514
  // OneKey patch: wallet badges contribute their own member heights.
1288
1515
  // return CGFloat((childCount + 1) * 68 + childCount * 12)
@@ -10,6 +10,17 @@ import { applyRowPatches, serializePatches, serializeSnapshot } from "./validati
10
10
  import { jsx as _jsx } from "react/jsx-runtime";
11
11
  const NativeListConfig = require('../nitrogen/generated/shared/json/NativeListConfig.json');
12
12
  const NativeListHost = getHostComponent('NativeList', () => NativeListConfig);
13
+ export function preloadNativeListAvatarImages(sources) {
14
+ return OneKeyImageCache.preload(sources.map(source => ({
15
+ uri: source.uri,
16
+ headers: source.headers,
17
+ resizeWidth: source.width,
18
+ resizeHeight: source.height,
19
+ optimizeTos: source.optimizeTos !== false,
20
+ overscan: source.overscan,
21
+ cachePolicy: source.cachePolicy === 'memory' ? OneKeyImageCachePolicy.MEMORY : source.cachePolicy === 'disk' ? OneKeyImageCachePolicy.DISK : OneKeyImageCachePolicy.MEMORY_DISK
22
+ })));
23
+ }
13
24
  function parsePayload(payloadJson) {
14
25
  return JSON.parse(payloadJson);
15
26
  }
@@ -78,14 +89,7 @@ export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
78
89
  updateAvatarPrefetchRef.current = updateAvatarPrefetch;
79
90
  useEffect(() => {
80
91
  avatarLifecycle.current = 'mounted';
81
- const queue = new NativeAvatarPrefetchQueue(source => OneKeyImageCache.preload([{
82
- uri: source.uri,
83
- headers: source.headers,
84
- resizeWidth: source.width,
85
- resizeHeight: source.height,
86
- optimizeTos: false,
87
- cachePolicy: source.cachePolicy === 'memory' ? OneKeyImageCachePolicy.MEMORY : source.cachePolicy === 'disk' ? OneKeyImageCachePolicy.DISK : OneKeyImageCachePolicy.MEMORY_DISK
88
- }]));
92
+ const queue = new NativeAvatarPrefetchQueue(source => preloadNativeListAvatarImages([source]));
89
93
  avatarQueueRef.current = queue;
90
94
  updateAvatarPrefetchRef.current();
91
95
  return () => {
@@ -5,7 +5,21 @@ import { View } from 'react-native';
5
5
  import { normalizeIndexScroll, normalizeKeyScroll, normalizePositionScroll, resolveLocationIndex, scrollFailure, validateOffset } from "./scrolling.js";
6
6
  import { serializePatches, validateSnapshot } from "./validation.js";
7
7
  import { NativeListWebEngine } from "./web/NativeListWebEngine.js";
8
+ import { acquireNativeListAvatar, canonicalNativeListAvatarUri } from "./web/NativeListWebAvatarCache.js";
8
9
  import { jsx as _jsx } from "react/jsx-runtime";
10
+ export function preloadNativeListAvatarImages(sources) {
11
+ if (typeof document === 'undefined') return Promise.resolve(false);
12
+ const uris = [...new Set(sources.map(source => canonicalNativeListAvatarUri(source.uri)).filter(uri => uri !== undefined))];
13
+ if (!uris.length) return Promise.resolve(false);
14
+ return Promise.all(uris.map(uri => new Promise(resolve => {
15
+ let release;
16
+ const settle = success => {
17
+ resolve(success);
18
+ queueMicrotask(() => release?.());
19
+ };
20
+ release = acquireNativeListAvatar(document, uri, () => settle(true), () => settle(false), 0);
21
+ }))).then(results => results.every(Boolean));
22
+ }
9
23
  export const NativeList = /*#__PURE__*/forwardRef(function NativeList({
10
24
  snapshot,
11
25
  webVirtualizationEnabled = true,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
 
3
- export { NativeList } from './NativeList';
3
+ export { NativeList, preloadNativeListAvatarImages } from './NativeList';
4
4
  export { applyRowPatches, serializePatches, serializeSnapshot, validatePatches, validateSnapshot } from "./validation.js";
5
5
  export { checkboxStateForKeys, checkboxStateForSection, isSelectableRow, reduceSelection, selectionStateFromSnapshot } from "./selection.js";
6
6
  //# sourceMappingURL=index.js.map