@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.
- package/README.md +57 -3
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +17 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +543 -26
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +26 -2
- package/ios/NativeListCell.swift +504 -20
- package/ios/NativeListDesignAssets.swift +8 -0
- package/ios/RNCNativeListView.swift +85 -7
- package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/icon.svg +1 -0
- package/lib/module/validation.js +129 -1
- package/lib/module/web/NativeListWebEngine.js +357 -5
- package/lib/typescript/src/models.d.ts +82 -2
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +26 -1
- package/package.json +2 -2
- package/src/models.ts +121 -3
- package/src/validation.ts +206 -0
- package/src/web/NativeListWebEngine.ts +578 -4
|
@@ -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
|
-
|
|
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 == "
|
|
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
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
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")
|
package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/Contents.json
ADDED
|
@@ -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>
|
package/lib/module/validation.js
CHANGED
|
@@ -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)) {
|