@onekeyfe/react-native-native-list 3.0.112 → 3.0.113

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.
@@ -118,6 +118,7 @@ func nativeListIcon(named name: String) -> UIImage? {
118
118
  case "DragOutline": assetName = "onekey_drag"
119
119
  case "StarOutline": assetName = "onekey_star"
120
120
  case "StarSolid": assetName = "onekey_star_solid"
121
+ case "BadgeVerifiedSolid": assetName = "onekey_badge_verified_solid"
121
122
  case "ChevronGrabberVerOutline": assetName = "onekey_chevron_grabber_ver"
122
123
  case "ChevronBottomOutline": assetName = "onekey_chevron_bottom"
123
124
  case "ChevronTopOutline": assetName = "onekey_chevron_top"
@@ -131,3 +132,10 @@ func nativeListIcon(named name: String) -> UIImage? {
131
132
  return UIImage(named: assetName, in: NativeListResources.bundle, compatibleWith: nil)?
132
133
  .withRenderingMode(["GoogleIllus", "BotIllus", "AccountErrorCustom"].contains(name) ? .alwaysOriginal : .alwaysTemplate)
133
134
  }
135
+
136
+ func nativeListIcon(named name: String, size: CGSize) -> UIImage? {
137
+ guard let image = nativeListIcon(named: name) else { return nil }
138
+ return UIGraphicsImageRenderer(size: size).image { _ in
139
+ image.draw(in: CGRect(origin: .zero, size: size))
140
+ }.withRenderingMode(.alwaysTemplate)
141
+ }
@@ -83,6 +83,10 @@ final class NativeListView: UIView {
83
83
  target: self,
84
84
  action: #selector(reorderLongPressChanged(_:))
85
85
  )
86
+ private lazy var marketLongPress = UILongPressGestureRecognizer(
87
+ target: self,
88
+ action: #selector(marketLongPressChanged(_:))
89
+ )
86
90
  // OneKey patch: claim only vertical drags so held rows and ancestor pagers stay responsive.
87
91
  private lazy var listBodyGestureGuard = UIPanGestureRecognizer(target: nil, action: nil)
88
92
  private var interactiveReorderSource: (key: String, index: Int)?
@@ -122,6 +126,10 @@ final class NativeListView: UIView {
122
126
  reorderLongPress.allowableMovement = ReorderAnimation.allowableMovement
123
127
  reorderLongPress.delegate = self
124
128
  collectionView.addGestureRecognizer(reorderLongPress)
129
+ marketLongPress.minimumPressDuration = 0.8
130
+ marketLongPress.allowableMovement = 10
131
+ marketLongPress.delegate = self
132
+ collectionView.addGestureRecognizer(marketLongPress)
125
133
  interactiveReorderPlaceholder.isHidden = true
126
134
  interactiveReorderPlaceholder.isUserInteractionEnabled = false
127
135
  interactiveReorderPlaceholder.layer.cornerRadius = ReorderAnimation.placeholderRadius
@@ -298,8 +306,13 @@ final class NativeListView: UIView {
298
306
  }
299
307
 
300
308
  var changedKeys: [String] = []
309
+ var marketQuoteKeys = Set<String>()
301
310
  var sizeChanged = false
311
+ let marketQuoteFields: Set<String> = [
312
+ "revision", "price", "priceSegments", "change", "accessibilityLabel",
313
+ ]
302
314
  for (index, changes) in pending {
315
+ let previous = current.items[index]
303
316
  var merged = current.items[index].data
304
317
  changes.forEach { key, value in
305
318
  if key != "key" && key != "type" { merged[key] = value }
@@ -307,14 +320,30 @@ final class NativeListView: UIView {
307
320
  guard let item = try? NativeListItem(data: merged) else { return }
308
321
  if rowHeight(current.items[index]) != rowHeight(item) { sizeChanged = true }
309
322
  current.items[index] = item
310
- changedKeys.append(item.key)
323
+ if previous.type == "market", Set(changes.keys).isSubset(of: marketQuoteFields) {
324
+ marketQuoteKeys.insert(item.key)
325
+ } else {
326
+ changedKeys.append(item.key)
327
+ }
311
328
  if let selected = changes["selected"] as? Bool {
312
329
  if selected { current.selectedKeys.insert(item.key) } else { current.selectedKeys.remove(item.key) }
313
330
  }
314
331
  }
315
- invalidateActionAnchor(reason: "snapshot")
332
+ if !changedKeys.isEmpty { invalidateActionAnchor(reason: "snapshot") }
316
333
  config = current
317
334
  itemsByKey = Dictionary(uniqueKeysWithValues: current.items.map { ($0.key, $0) })
335
+ for indexPath in collectionView.indexPathsForVisibleItems {
336
+ guard let item = current.items[safe: indexPath.item],
337
+ marketQuoteKeys.contains(item.key),
338
+ let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else { continue }
339
+ cell.updateMarketQuote(item, theme: current.theme)
340
+ }
341
+ if changedKeys.isEmpty {
342
+ configureFooter(current)
343
+ emitVisibleRangeIfNeeded()
344
+ checkEndReached()
345
+ return
346
+ }
318
347
  var snapshot = dataSource.snapshot()
319
348
  snapshot.reconfigureItems(changedKeys.filter { snapshot.indexOfItem($0) != nil })
320
349
  dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
@@ -985,7 +1014,9 @@ final class NativeListView: UIView {
985
1014
  updateSelection(target: NativeSelectionTarget(scope: "row", key: item.key), sourceKey: item.key)
986
1015
  return
987
1016
  }
988
- if item.type == "action" {
1017
+ if item.type == "market", !item.data.string("pressActionKey").isEmpty {
1018
+ emit(onRowAction, rowActionPayload(item: item, actionKey: item.data.string("pressActionKey"), origin: origin))
1019
+ } else if item.type == "action" {
989
1020
  emit(onRowAction, rowActionPayload(item: item, actionKey: item.data.string("actionKey"), origin: origin))
990
1021
  } else if item.type == "system", item.data.string("variant") == "retry" {
991
1022
  emit(onRowAction, rowActionPayload(item: item, actionKey: item.data.string("actionKey"), origin: origin))
@@ -994,6 +1025,19 @@ final class NativeListView: UIView {
994
1025
  }
995
1026
  }
996
1027
 
1028
+ @objc private func marketLongPressChanged(_ gesture: UILongPressGestureRecognizer) {
1029
+ guard gesture.state == .began else { return }
1030
+ let point = gesture.location(in: collectionView)
1031
+ guard let indexPath = collectionView.indexPathForItem(at: point),
1032
+ let item = config?.items[safe: indexPath.item],
1033
+ item.type == "market",
1034
+ !item.data.bool("disabled") else { return }
1035
+ let actionKey = item.data.string("longPressActionKey")
1036
+ guard !actionKey.isEmpty else { return }
1037
+ let origin = (collectionView.cellForItem(at: indexPath) as? NativeListCell)?.rowActionOrigin()
1038
+ handleAction(item: item, actionKey: actionKey, target: nil, origin: origin)
1039
+ }
1040
+
997
1041
  private func handleAction(
998
1042
  item: NativeListItem,
999
1043
  actionKey: String,
@@ -1267,10 +1311,18 @@ final class NativeListView: UIView {
1267
1311
  ? 56
1268
1312
  : config?.layout == "linear" ? 30 : 36
1269
1313
  case "system":
1270
- switch item.data.string("variant") {
1271
- case "noMatch", "end": base = 36
1272
- case "retry": base = 44
1273
- default: base = 56
1314
+ if item.data.string("variant") == "loading" && item.data.string("loadingStyle") == "skeleton" {
1315
+ base = 56
1316
+ } else if item.data.string("variant") == "loading" && item.data.string("loadingStyle") == "spinner" {
1317
+ base = 52
1318
+ } else if item.data.string("presentation") == "market" {
1319
+ base = item.data.string("variant") == "loading" ? 68 : 44
1320
+ } else {
1321
+ switch item.data.string("variant") {
1322
+ case "noMatch", "end": base = 36
1323
+ case "retry": base = 44
1324
+ default: base = 56
1325
+ }
1274
1326
  }
1275
1327
  case "action":
1276
1328
  base = item.data.string("presentation") == "accountSelector"
@@ -1280,6 +1332,14 @@ final class NativeListView: UIView {
1280
1332
  base = item.data.dictionaries("columns").contains {
1281
1333
  !$0.string("secondaryText").isEmpty
1282
1334
  } ? 60 : 56
1335
+ case "market":
1336
+ let style = item.data.dictionary("style")
1337
+ let imageHeight = CGFloat(style?.dictionary("image")?.double(
1338
+ "height",
1339
+ default: item.data.string("variant") == "stock" ? 40 : 32
1340
+ ) ?? (item.data.string("variant") == "stock" ? 40 : 32))
1341
+ let verticalPadding = CGFloat(style?.double("verticalPadding", default: 12) ?? 12)
1342
+ base = max(item.data.string("variant") == "stock" ? 72 : 68, imageHeight + verticalPadding * 2)
1283
1343
  default:
1284
1344
  if item.type == "identity", !item.data.string("tertiary").isEmpty {
1285
1345
  base = 72
@@ -1490,6 +1550,16 @@ final class NativeListView: UIView {
1490
1550
  }
1491
1551
 
1492
1552
  override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
1553
+ if gestureRecognizer === reorderLongPress || gestureRecognizer === marketLongPress {
1554
+ let point = gestureRecognizer.location(in: collectionView)
1555
+ guard let indexPath = collectionView.indexPathForItem(at: point),
1556
+ let item = item(at: indexPath),
1557
+ !item.data.bool("disabled") else { return false }
1558
+ if gestureRecognizer === reorderLongPress {
1559
+ return config?.reorderable == true && item.isReorderable
1560
+ }
1561
+ return item.type == "market" && !item.data.string("longPressActionKey").isEmpty
1562
+ }
1493
1563
  guard gestureRecognizer === listBodyGestureGuard,
1494
1564
  let pan = gestureRecognizer as? UIPanGestureRecognizer else { return true }
1495
1565
  let velocity = pan.velocity(in: collectionView)
@@ -1608,6 +1678,14 @@ extension NativeListView: UICollectionViewDelegateFlowLayout {
1608
1678
  return !item.data.bool("disabled")
1609
1679
  }
1610
1680
 
1681
+ func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
1682
+ guard let item = config?.items[safe: indexPath.item], item.type == "market" else { return }
1683
+ let actionKey = item.data.string("pressInActionKey")
1684
+ guard !actionKey.isEmpty else { return }
1685
+ let origin = (collectionView.cellForItem(at: indexPath) as? NativeListCell)?.rowActionOrigin()
1686
+ handleAction(item: item, actionKey: actionKey, target: nil, origin: origin)
1687
+ }
1688
+
1611
1689
  func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
1612
1690
  guard let item = config?.items[safe: indexPath.item] else { return false }
1613
1691
  return !item.data.bool("disabled")
@@ -0,0 +1 @@
1
+ {"images":[{"filename":"icon.svg","idiom":"universal"}],"info":{"author":"xcode","version":1},"properties":{"preserves-vector-representation":true,"template-rendering-intent":"template"}}
@@ -0,0 +1 @@
1
+ <svg fill="#000" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" d="M9.483 11.458v3.5h-1v-3.5z M10.467 2.698a2.03 2.03 0 0 1 3.065 0l1.358 1.564a.03.03 0 0 0 .028.01l2.046-.325a2.03 2.03 0 0 1 2.347 1.971l.037 2.07q0 .016.014.026l1.776 1.066a2.03 2.03 0 0 1 .532 3.019l-1.304 1.609a.03.03 0 0 0-.005.03l.675 1.956a2.03 2.03 0 0 1-1.533 2.656l-2.033.394a.03.03 0 0 0-.023.019l-.741 1.933a2.03 2.03 0 0 1-2.88 1.05l-1.811-1.006a.03.03 0 0 0-.03 0l-1.811 1.005a2.03 2.03 0 0 1-2.88-1.049l-.742-1.933a.03.03 0 0 0-.023-.019l-2.033-.394a2.03 2.03 0 0 1-1.532-2.656l.675-1.957a.03.03 0 0 0-.005-.029l-1.304-1.61a2.03 2.03 0 0 1 .532-3.018l1.776-1.066a.03.03 0 0 0 .014-.026l.035-2.07a2.03 2.03 0 0 1 2.349-1.97l2.045.324a.03.03 0 0 0 .028-.01zm1.516 3.76a.5.5 0 0 0-.447.276l-1.861 3.724H8.483a1 1 0 0 0-1 1v3.5a1 1 0 0 0 1 1h6.692a2 2 0 0 0 1.981-1.73l.341-2.5a2 2 0 0 0-1.982-2.27h-1.939l.197-1.269a1.5 1.5 0 0 0-1.481-1.731z"/></svg>
@@ -10,6 +10,7 @@ const MAX_IMAGE_HEADERS = 32;
10
10
  const MAX_HEADER_LENGTH = 4096;
11
11
  const MAX_SPACER_HEIGHT = 512;
12
12
  const MAX_SECTION_INDEX_TITLE_LENGTH = 8;
13
+ const MAX_MARKET_BADGES = 3;
13
14
  function fail(path, message) {
14
15
  throw new Error(`NativeList ${path}: ${message}`);
15
16
  }
@@ -62,6 +63,105 @@ function assertTextTone(tone, path) {
62
63
  fail(path, 'must be primary, secondary, positive, or negative');
63
64
  }
64
65
  }
66
+ function assertBoundedStyleNumber(value, path, min, max) {
67
+ if (value !== undefined && (!Number.isFinite(value) || value < min || value > max)) {
68
+ fail(path, `must be within ${min}...${max}`);
69
+ }
70
+ }
71
+ function assertMarketTextStyle(style, path) {
72
+ if (!style) return;
73
+ assertBoundedStyleNumber(style.fontSize, `${path}.fontSize`, 8, 48);
74
+ assertBoundedStyleNumber(style.lineHeight, `${path}.lineHeight`, 8, 64);
75
+ if (style.fontWeight !== undefined && !['regular', 'medium', 'semibold', 'bold'].includes(style.fontWeight)) {
76
+ fail(`${path}.fontWeight`, 'must be regular, medium, semibold, or bold');
77
+ }
78
+ if (style.alignment !== undefined && !['start', 'center', 'end'].includes(style.alignment)) {
79
+ fail(`${path}.alignment`, 'must be start, center, or end');
80
+ }
81
+ if (style.lines !== undefined && ![1, 2].includes(style.lines)) {
82
+ fail(`${path}.lines`, 'must be 1 or 2');
83
+ }
84
+ }
85
+ function assertMarketStyle(style, path) {
86
+ if (!style) return;
87
+ for (const field of ['horizontalPadding', 'verticalPadding', 'leadingGap', 'titleBadgeGap', 'trailingGap']) {
88
+ assertBoundedStyleNumber(style[field], `${path}.${field}`, 0, 64);
89
+ }
90
+ assertBoundedStyleNumber(style.lineGap, `${path}.lineGap`, 0, 16);
91
+ assertBoundedStyleNumber(style.changeWidth, `${path}.changeWidth`, 1, 160);
92
+ assertBoundedStyleNumber(style.changeHeight, `${path}.changeHeight`, 1, 160);
93
+ assertBoundedStyleNumber(style.changeCornerRadius, `${path}.changeCornerRadius`, 0, 80);
94
+ if (style.image) {
95
+ assertBoundedStyleNumber(style.image.width, `${path}.image.width`, 1, 160);
96
+ assertBoundedStyleNumber(style.image.height, `${path}.image.height`, 1, 160);
97
+ assertBoundedStyleNumber(style.image.cornerRadius, `${path}.image.cornerRadius`, 0, 80);
98
+ assertVisualShape(style.image.shape, `${path}.image.shape`);
99
+ if (style.image.contentFit !== undefined && !['cover', 'contain', 'fill', 'center'].includes(style.image.contentFit)) {
100
+ fail(`${path}.image.contentFit`, 'must be cover, contain, fill, or center');
101
+ }
102
+ }
103
+ assertMarketTextStyle(style.title, `${path}.title`);
104
+ assertMarketTextStyle(style.subtitle, `${path}.subtitle`);
105
+ assertMarketTextStyle(style.price, `${path}.price`);
106
+ assertMarketTextStyle(style.change, `${path}.change`);
107
+ }
108
+ function assertMarketRow(row, path) {
109
+ if (!['token', 'stock', 'perp'].includes(row.variant)) {
110
+ fail(`${path}.variant`, 'must be token, stock, or perp');
111
+ }
112
+ assertText(row.title, `${path}.title`);
113
+ assertText(row.subtitle, `${path}.subtitle`);
114
+ assertText(row.price, `${path}.price`);
115
+ assertText(row.change.text, `${path}.change.text`);
116
+ assertLeadingVisual(row.leading, `${path}.leading`);
117
+ const assertSegments = (segments, segmentPath) => {
118
+ segments?.forEach((segment, index) => {
119
+ assertText(segment.text, `${segmentPath}[${index}].text`);
120
+ if (segment.style !== undefined && segment.style !== 'subscript') {
121
+ fail(`${segmentPath}[${index}].style`, 'must be subscript when provided');
122
+ }
123
+ });
124
+ };
125
+ assertSegments(row.subtitleSegments, `${path}.subtitleSegments`);
126
+ assertSegments(row.priceSegments, `${path}.priceSegments`);
127
+ assertSegments(row.change.textSegments, `${path}.change.textSegments`);
128
+ if (!['positive', 'negative', 'neutral'].includes(row.change.tone)) {
129
+ fail(`${path}.change.tone`, 'must be positive, negative, or neutral');
130
+ }
131
+ if ((row.badges?.length ?? 0) > MAX_MARKET_BADGES) {
132
+ fail(`${path}.badges`, `supports at most ${MAX_MARKET_BADGES} badges`);
133
+ }
134
+ const badgeKeys = new Set();
135
+ row.badges?.forEach((badge, index) => {
136
+ const badgePath = `${path}.badges[${index}]`;
137
+ assertKey(badge.key, `${badgePath}.key`);
138
+ if (badgeKeys.has(badge.key)) {
139
+ fail(`${path}.badges`, `duplicate badge key "${badge.key}"`);
140
+ }
141
+ badgeKeys.add(badge.key);
142
+ assertText(badge.text, `${badgePath}.text`);
143
+ if (badge.iconName !== undefined && badge.iconName !== 'verified') {
144
+ fail(`${badgePath}.iconName`, 'must be verified when provided');
145
+ }
146
+ if (badge.iconName !== undefined && badge.icon !== undefined) {
147
+ fail(`${badgePath}.icon`, 'cannot be combined with iconName');
148
+ }
149
+ assertImage(badge.icon, `${badgePath}.icon`);
150
+ if (badge.tone !== undefined && !['neutral', 'info', 'success', 'warning', 'danger'].includes(badge.tone)) {
151
+ fail(`${badgePath}.tone`, 'must be neutral, info, success, warning, or danger');
152
+ }
153
+ if (!badge.text && !badge.icon && !badge.iconName) {
154
+ fail(badgePath, 'requires text, icon, or iconName');
155
+ }
156
+ if (badge.actionKey !== undefined) {
157
+ assertKey(badge.actionKey, `${badgePath}.actionKey`);
158
+ }
159
+ });
160
+ for (const [key, value] of [['pressActionKey', row.pressActionKey], ['pressInActionKey', row.pressInActionKey], ['longPressActionKey', row.longPressActionKey], ['diagnostics.imageBindActionKey', row.diagnostics?.imageBindActionKey]]) {
161
+ if (value !== undefined) assertKey(value, `${path}.${key}`);
162
+ }
163
+ assertMarketStyle(row.style, `${path}.style`);
164
+ }
65
165
  function assertSectionHeaderVariant(variant, path) {
66
166
  if (variant !== undefined && !['summary', 'gallery', 'history'].includes(variant)) {
67
167
  fail(path, 'must be summary, gallery, or history when provided');
@@ -150,7 +250,7 @@ function assertLeadingVisual(visual, path) {
150
250
  }
151
251
  }
152
252
  function assertVisual(row, path) {
153
- const visuals = row.type === 'identity' ? [row.leading] : row.type === 'rail' ? [row.visual] : row.type === 'activity' ? [row.leading, row.secondaryLeading] : row.type === 'message' ? [row.leading] : row.type === 'dataRow' ? [row.leading] : row.type === 'metricCard' ? [row.visual] : [];
253
+ const visuals = row.type === 'identity' ? [row.leading] : row.type === 'rail' ? [row.visual] : row.type === 'activity' ? [row.leading, row.secondaryLeading] : row.type === 'message' ? [row.leading] : row.type === 'dataRow' ? [row.leading] : row.type === 'market' ? [row.leading] : row.type === 'metricCard' ? [row.visual] : [];
154
254
  visuals.forEach((visual, index) => {
155
255
  assertLeadingVisual(visual, `${path}.visual[${index}]`);
156
256
  });
@@ -262,6 +362,9 @@ function assertRow(row, index, path = `rows[${index}]`) {
262
362
  fail(`${path}.badges`, `supports at most ${MAX_BADGES} badges`);
263
363
  }
264
364
  break;
365
+ case 'market':
366
+ assertMarketRow(row, path);
367
+ break;
265
368
  case 'mediaTile':
266
369
  assertImage(row.image, `${path}.image`);
267
370
  if (row.imageState !== undefined && !['empty', 'error'].includes(row.imageState)) {
@@ -327,12 +430,19 @@ function assertRow(row, index, path = `rows[${index}]`) {
327
430
  assertTrailingAccessories(row.trailing, `${path}.trailing`);
328
431
  break;
329
432
  case 'system':
433
+ const presentation = 'presentation' in row ? row.presentation : undefined;
434
+ if (presentation !== undefined && presentation !== 'market') {
435
+ fail(`${path}.presentation`, 'must be market when provided');
436
+ }
330
437
  if (
331
438
  // OneKey patch: deprecated-wallet warnings retain the original scrolling semantics.
332
439
  !['loading', 'retry', 'noMatch', 'end', 'spacer', 'warning'].includes(row.variant)) {
333
440
  fail(`${path}.variant`, 'must be loading, retry, noMatch, end, spacer, or warning');
334
441
  }
335
442
  if (row.variant === 'warning') assertText(row.title, `${path}.title`);
443
+ if (row.variant === 'loading' && row.loadingStyle !== undefined && row.loadingStyle !== 'skeleton' && row.loadingStyle !== 'spinner') {
444
+ fail(`${path}.loadingStyle`, 'must be skeleton or spinner when provided');
445
+ }
336
446
  if (row.variant !== 'spacer') {
337
447
  assertText(row.message, `${path}.message`);
338
448
  }
@@ -503,6 +613,24 @@ function assertPatchChanges(patch, index) {
503
613
  fail(`${path}.badges`, `supports at most ${MAX_BADGES} badges`);
504
614
  }
505
615
  break;
616
+ case 'market':
617
+ assertMarketRow({
618
+ type: 'market',
619
+ key: patch.key,
620
+ variant: 'token',
621
+ leading: {
622
+ kind: 'icon',
623
+ name: 'placeholder'
624
+ },
625
+ title: '',
626
+ price: '',
627
+ change: {
628
+ text: '',
629
+ tone: 'neutral'
630
+ },
631
+ ...patch.changes
632
+ }, path);
633
+ break;
506
634
  case 'mediaTile':
507
635
  assertImage(patch.changes.image, `${path}.image`);
508
636
  if (patch.changes.imageState !== undefined && !['empty', 'error'].includes(patch.changes.imageState)) {