@onekeyfe/react-native-native-list 3.0.114 → 3.0.116
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/build.gradle +1 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +47 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +795 -30
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +445 -91
- package/ios/NativeListCell.swift +655 -20
- package/ios/NativeListDesignAssets.swift +8 -0
- package/ios/RNCNativeListView.swift +275 -42
- 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 +145 -1
- package/lib/module/web/NativeListWebEngine.js +461 -51
- package/lib/typescript/src/models.d.ts +108 -2
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +37 -3
- package/package.json +4 -2
- package/src/models.ts +144 -3
- package/src/validation.ts +250 -0
- package/src/web/NativeListWebEngine.ts +766 -91
|
@@ -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
|
+
}
|
|
@@ -64,7 +64,7 @@ final class NativeListView: UIView {
|
|
|
64
64
|
private let footerContainer = UIView()
|
|
65
65
|
private let footerCell = NativeListCell(frame: .zero)
|
|
66
66
|
private let sectionIndexView = NativeListSectionIndexView()
|
|
67
|
-
private let sectionIndexPreview =
|
|
67
|
+
private let sectionIndexPreview = NativeListSectionIndexPreviewView()
|
|
68
68
|
private var footerHeightConstraint: NSLayoutConstraint!
|
|
69
69
|
private var dataSource: UICollectionViewDiffableDataSource<Int, String>!
|
|
70
70
|
private var config: NativeListConfig?
|
|
@@ -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)?
|
|
@@ -102,7 +106,8 @@ final class NativeListView: UIView {
|
|
|
102
106
|
|
|
103
107
|
private static let sectionIndexContentInset: CGFloat = 16
|
|
104
108
|
private static let sectionIndexRailWidth: CGFloat = 32
|
|
105
|
-
private static let
|
|
109
|
+
private static let sectionIndexPreviewWidth: CGFloat = 60
|
|
110
|
+
private static let sectionIndexPreviewHeight: CGFloat = 50
|
|
106
111
|
private static let sectionIndexPreviewEndMargin: CGFloat = 40
|
|
107
112
|
|
|
108
113
|
override init(frame: CGRect) {
|
|
@@ -122,6 +127,10 @@ final class NativeListView: UIView {
|
|
|
122
127
|
reorderLongPress.allowableMovement = ReorderAnimation.allowableMovement
|
|
123
128
|
reorderLongPress.delegate = self
|
|
124
129
|
collectionView.addGestureRecognizer(reorderLongPress)
|
|
130
|
+
marketLongPress.minimumPressDuration = 0.8
|
|
131
|
+
marketLongPress.allowableMovement = 10
|
|
132
|
+
marketLongPress.delegate = self
|
|
133
|
+
collectionView.addGestureRecognizer(marketLongPress)
|
|
125
134
|
interactiveReorderPlaceholder.isHidden = true
|
|
126
135
|
interactiveReorderPlaceholder.isUserInteractionEnabled = false
|
|
127
136
|
interactiveReorderPlaceholder.layer.cornerRadius = ReorderAnimation.placeholderRadius
|
|
@@ -158,8 +167,8 @@ final class NativeListView: UIView {
|
|
|
158
167
|
constant: -Self.sectionIndexPreviewEndMargin
|
|
159
168
|
),
|
|
160
169
|
sectionIndexPreview.centerYAnchor.constraint(equalTo: collectionView.centerYAnchor),
|
|
161
|
-
sectionIndexPreview.widthAnchor.constraint(equalToConstant: Self.
|
|
162
|
-
sectionIndexPreview.heightAnchor.constraint(equalToConstant: Self.
|
|
170
|
+
sectionIndexPreview.widthAnchor.constraint(equalToConstant: Self.sectionIndexPreviewWidth),
|
|
171
|
+
sectionIndexPreview.heightAnchor.constraint(equalToConstant: Self.sectionIndexPreviewHeight),
|
|
163
172
|
])
|
|
164
173
|
|
|
165
174
|
sectionIndexView.isHidden = true
|
|
@@ -171,11 +180,6 @@ final class NativeListView: UIView {
|
|
|
171
180
|
}
|
|
172
181
|
sectionIndexPreview.isHidden = true
|
|
173
182
|
sectionIndexPreview.alpha = 0
|
|
174
|
-
sectionIndexPreview.layer.cornerRadius = 14
|
|
175
|
-
sectionIndexPreview.layer.masksToBounds = true
|
|
176
|
-
sectionIndexPreview.textAlignment = .center
|
|
177
|
-
sectionIndexPreview.adjustsFontForContentSizeCategory = true
|
|
178
|
-
sectionIndexPreview.font = nativeListFont(ofSize: 22, weight: .semibold)
|
|
179
183
|
sectionIndexPreview.isAccessibilityElement = false
|
|
180
184
|
|
|
181
185
|
footerCell.onAction = { [weak self] item, action, target, origin in
|
|
@@ -298,8 +302,13 @@ final class NativeListView: UIView {
|
|
|
298
302
|
}
|
|
299
303
|
|
|
300
304
|
var changedKeys: [String] = []
|
|
305
|
+
var marketQuoteKeys = Set<String>()
|
|
301
306
|
var sizeChanged = false
|
|
307
|
+
let marketQuoteFields: Set<String> = [
|
|
308
|
+
"revision", "price", "priceSegments", "change", "accessibilityLabel",
|
|
309
|
+
]
|
|
302
310
|
for (index, changes) in pending {
|
|
311
|
+
let previous = current.items[index]
|
|
303
312
|
var merged = current.items[index].data
|
|
304
313
|
changes.forEach { key, value in
|
|
305
314
|
if key != "key" && key != "type" { merged[key] = value }
|
|
@@ -307,14 +316,30 @@ final class NativeListView: UIView {
|
|
|
307
316
|
guard let item = try? NativeListItem(data: merged) else { return }
|
|
308
317
|
if rowHeight(current.items[index]) != rowHeight(item) { sizeChanged = true }
|
|
309
318
|
current.items[index] = item
|
|
310
|
-
|
|
319
|
+
if previous.type == "market", Set(changes.keys).isSubset(of: marketQuoteFields) {
|
|
320
|
+
marketQuoteKeys.insert(item.key)
|
|
321
|
+
} else {
|
|
322
|
+
changedKeys.append(item.key)
|
|
323
|
+
}
|
|
311
324
|
if let selected = changes["selected"] as? Bool {
|
|
312
325
|
if selected { current.selectedKeys.insert(item.key) } else { current.selectedKeys.remove(item.key) }
|
|
313
326
|
}
|
|
314
327
|
}
|
|
315
|
-
invalidateActionAnchor(reason: "snapshot")
|
|
328
|
+
if !changedKeys.isEmpty { invalidateActionAnchor(reason: "snapshot") }
|
|
316
329
|
config = current
|
|
317
330
|
itemsByKey = Dictionary(uniqueKeysWithValues: current.items.map { ($0.key, $0) })
|
|
331
|
+
for indexPath in collectionView.indexPathsForVisibleItems {
|
|
332
|
+
guard let item = current.items[safe: indexPath.item],
|
|
333
|
+
marketQuoteKeys.contains(item.key),
|
|
334
|
+
let cell = collectionView.cellForItem(at: indexPath) as? NativeListCell else { continue }
|
|
335
|
+
cell.updateMarketQuote(item, theme: current.theme)
|
|
336
|
+
}
|
|
337
|
+
if changedKeys.isEmpty {
|
|
338
|
+
configureFooter(current)
|
|
339
|
+
emitVisibleRangeIfNeeded()
|
|
340
|
+
checkEndReached()
|
|
341
|
+
return
|
|
342
|
+
}
|
|
318
343
|
var snapshot = dataSource.snapshot()
|
|
319
344
|
snapshot.reconfigureItems(changedKeys.filter { snapshot.indexOfItem($0) != nil })
|
|
320
345
|
dataSource.apply(snapshot, animatingDifferences: false) { [weak self] in
|
|
@@ -871,18 +896,21 @@ final class NativeListView: UIView {
|
|
|
871
896
|
sectionIndexHapticsEnabled = config.sectionIndexHapticsEnabled
|
|
872
897
|
sectionIndexView.configure(
|
|
873
898
|
titles: sectionIndexEntries.map(\.title),
|
|
874
|
-
textColor: nativeListColor(config.theme, "
|
|
875
|
-
activeColor: nativeListColor(config.theme, "
|
|
899
|
+
textColor: nativeListColor(config.theme, "disabledText", "#8D8D8D"),
|
|
900
|
+
activeColor: nativeListColor(config.theme, "positive", "#218358"),
|
|
876
901
|
activeTextColor: nativeListColor(config.theme, "inverseText", "#FCFCFC"),
|
|
877
902
|
centeredInWindow: config.sectionIndexCenteredInWindow
|
|
878
903
|
)
|
|
879
904
|
sectionIndexView.isHidden = sectionIndexEntries.isEmpty
|
|
880
|
-
sectionIndexPreview.
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
905
|
+
sectionIndexPreview.configure(
|
|
906
|
+
fillColor: UIColor(
|
|
907
|
+
red: 202.0 / 255.0,
|
|
908
|
+
green: 202.0 / 255.0,
|
|
909
|
+
blue: 202.0 / 255.0,
|
|
910
|
+
alpha: 1
|
|
911
|
+
),
|
|
912
|
+
textColor: .white
|
|
884
913
|
)
|
|
885
|
-
sectionIndexPreview.textColor = nativeListColor(config.theme, "inverseText", "#FCFCFC")
|
|
886
914
|
if let previousKey,
|
|
887
915
|
let index = sectionIndexEntries.firstIndex(where: { $0.key == previousKey }) {
|
|
888
916
|
sectionIndexView.setActiveIndex(index)
|
|
@@ -901,6 +929,7 @@ final class NativeListView: UIView {
|
|
|
901
929
|
if interacting {
|
|
902
930
|
sectionIndexPreview.layer.removeAllAnimations()
|
|
903
931
|
sectionIndexPreview.text = entry.title
|
|
932
|
+
positionSectionIndexPreview(at: sectionIndexView.centerY(for: index))
|
|
904
933
|
sectionIndexPreview.isHidden = false
|
|
905
934
|
sectionIndexPreview.alpha = 1
|
|
906
935
|
if changed && sectionIndexHapticsEnabled {
|
|
@@ -929,6 +958,23 @@ final class NativeListView: UIView {
|
|
|
929
958
|
}
|
|
930
959
|
}
|
|
931
960
|
|
|
961
|
+
private func positionSectionIndexPreview(at sectionIndexY: CGFloat) {
|
|
962
|
+
layoutIfNeeded()
|
|
963
|
+
let targetY = sectionIndexView.convert(
|
|
964
|
+
CGPoint(x: sectionIndexView.bounds.midX, y: sectionIndexY),
|
|
965
|
+
to: self
|
|
966
|
+
).y
|
|
967
|
+
let safeFrame = safeAreaLayoutGuide.layoutFrame
|
|
968
|
+
let halfHeight = Self.sectionIndexPreviewHeight / 2
|
|
969
|
+
let minimumY = safeFrame.minY + halfHeight
|
|
970
|
+
let maximumY = max(minimumY, safeFrame.maxY - halfHeight)
|
|
971
|
+
let clampedY = targetY.clamped(to: minimumY...maximumY)
|
|
972
|
+
sectionIndexPreview.transform = CGAffineTransform(
|
|
973
|
+
translationX: 0,
|
|
974
|
+
y: clampedY - sectionIndexPreview.center.y
|
|
975
|
+
)
|
|
976
|
+
}
|
|
977
|
+
|
|
932
978
|
private func syncSectionIndexToVisibleRows() {
|
|
933
979
|
guard !sectionIndexScrubbing, !sectionIndexEntries.isEmpty else { return }
|
|
934
980
|
let firstVisible = collectionView.indexPathsForVisibleItems.map(\.item).min() ?? 0
|
|
@@ -985,7 +1031,9 @@ final class NativeListView: UIView {
|
|
|
985
1031
|
updateSelection(target: NativeSelectionTarget(scope: "row", key: item.key), sourceKey: item.key)
|
|
986
1032
|
return
|
|
987
1033
|
}
|
|
988
|
-
if item.type == "
|
|
1034
|
+
if item.type == "market", !item.data.string("pressActionKey").isEmpty {
|
|
1035
|
+
emit(onRowAction, rowActionPayload(item: item, actionKey: item.data.string("pressActionKey"), origin: origin))
|
|
1036
|
+
} else if item.type == "action" {
|
|
989
1037
|
emit(onRowAction, rowActionPayload(item: item, actionKey: item.data.string("actionKey"), origin: origin))
|
|
990
1038
|
} else if item.type == "system", item.data.string("variant") == "retry" {
|
|
991
1039
|
emit(onRowAction, rowActionPayload(item: item, actionKey: item.data.string("actionKey"), origin: origin))
|
|
@@ -994,6 +1042,20 @@ final class NativeListView: UIView {
|
|
|
994
1042
|
}
|
|
995
1043
|
}
|
|
996
1044
|
|
|
1045
|
+
@objc private func marketLongPressChanged(_ gesture: UILongPressGestureRecognizer) {
|
|
1046
|
+
guard gesture.state == .began else { return }
|
|
1047
|
+
let point = gesture.location(in: collectionView)
|
|
1048
|
+
guard let indexPath = collectionView.indexPathForItem(at: point),
|
|
1049
|
+
let item = config?.items[safe: indexPath.item],
|
|
1050
|
+
item.type == "market",
|
|
1051
|
+
!item.data.bool("disabled") else { return }
|
|
1052
|
+
let actionKey = item.data.string("longPressActionKey")
|
|
1053
|
+
guard !actionKey.isEmpty else { return }
|
|
1054
|
+
let origin = (collectionView.cellForItem(at: indexPath) as? NativeListCell)?.rowActionOrigin()
|
|
1055
|
+
origin?.windowPoint = gesture.location(in: window)
|
|
1056
|
+
handleAction(item: item, actionKey: actionKey, target: nil, origin: origin)
|
|
1057
|
+
}
|
|
1058
|
+
|
|
997
1059
|
private func handleAction(
|
|
998
1060
|
item: NativeListItem,
|
|
999
1061
|
actionKey: String,
|
|
@@ -1267,10 +1329,18 @@ final class NativeListView: UIView {
|
|
|
1267
1329
|
? 56
|
|
1268
1330
|
: config?.layout == "linear" ? 30 : 36
|
|
1269
1331
|
case "system":
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1332
|
+
if item.data.string("variant") == "loading" && item.data.string("loadingStyle") == "skeleton" {
|
|
1333
|
+
base = 56
|
|
1334
|
+
} else if item.data.string("variant") == "loading" && item.data.string("loadingStyle") == "spinner" {
|
|
1335
|
+
base = 52
|
|
1336
|
+
} else if item.data.string("presentation") == "market" {
|
|
1337
|
+
base = item.data.string("variant") == "loading" ? 68 : 44
|
|
1338
|
+
} else {
|
|
1339
|
+
switch item.data.string("variant") {
|
|
1340
|
+
case "noMatch", "end": base = 36
|
|
1341
|
+
case "retry": base = 44
|
|
1342
|
+
default: base = 56
|
|
1343
|
+
}
|
|
1274
1344
|
}
|
|
1275
1345
|
case "action":
|
|
1276
1346
|
base = item.data.string("presentation") == "accountSelector"
|
|
@@ -1280,6 +1350,14 @@ final class NativeListView: UIView {
|
|
|
1280
1350
|
base = item.data.dictionaries("columns").contains {
|
|
1281
1351
|
!$0.string("secondaryText").isEmpty
|
|
1282
1352
|
} ? 60 : 56
|
|
1353
|
+
case "market":
|
|
1354
|
+
let style = item.data.dictionary("style")
|
|
1355
|
+
let imageHeight = CGFloat(style?.dictionary("image")?.double(
|
|
1356
|
+
"height",
|
|
1357
|
+
default: item.data.string("variant") == "stock" ? 40 : 32
|
|
1358
|
+
) ?? (item.data.string("variant") == "stock" ? 40 : 32))
|
|
1359
|
+
let verticalPadding = CGFloat(style?.double("verticalPadding", default: 12) ?? 12)
|
|
1360
|
+
base = max(item.data.string("variant") == "stock" ? 72 : 68, imageHeight + verticalPadding * 2)
|
|
1283
1361
|
default:
|
|
1284
1362
|
if item.type == "identity", !item.data.string("tertiary").isEmpty {
|
|
1285
1363
|
base = 72
|
|
@@ -1401,6 +1479,9 @@ final class NativeListView: UIView {
|
|
|
1401
1479
|
? "rtl"
|
|
1402
1480
|
: "ltr",
|
|
1403
1481
|
]
|
|
1482
|
+
if let point = origin.windowPoint {
|
|
1483
|
+
anchor["windowPoint"] = ["x": point.x, "y": point.y]
|
|
1484
|
+
}
|
|
1404
1485
|
if let slot = origin.slot { anchor["slot"] = slot }
|
|
1405
1486
|
return anchor
|
|
1406
1487
|
}
|
|
@@ -1490,6 +1571,16 @@ final class NativeListView: UIView {
|
|
|
1490
1571
|
}
|
|
1491
1572
|
|
|
1492
1573
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
1574
|
+
if gestureRecognizer === reorderLongPress || gestureRecognizer === marketLongPress {
|
|
1575
|
+
let point = gestureRecognizer.location(in: collectionView)
|
|
1576
|
+
guard let indexPath = collectionView.indexPathForItem(at: point),
|
|
1577
|
+
let item = item(at: indexPath),
|
|
1578
|
+
!item.data.bool("disabled") else { return false }
|
|
1579
|
+
if gestureRecognizer === reorderLongPress {
|
|
1580
|
+
return config?.reorderable == true && item.isReorderable
|
|
1581
|
+
}
|
|
1582
|
+
return item.type == "market" && !item.data.string("longPressActionKey").isEmpty
|
|
1583
|
+
}
|
|
1493
1584
|
guard gestureRecognizer === listBodyGestureGuard,
|
|
1494
1585
|
let pan = gestureRecognizer as? UIPanGestureRecognizer else { return true }
|
|
1495
1586
|
let velocity = pan.velocity(in: collectionView)
|
|
@@ -1608,6 +1699,14 @@ extension NativeListView: UICollectionViewDelegateFlowLayout {
|
|
|
1608
1699
|
return !item.data.bool("disabled")
|
|
1609
1700
|
}
|
|
1610
1701
|
|
|
1702
|
+
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
|
|
1703
|
+
guard let item = config?.items[safe: indexPath.item], item.type == "market" else { return }
|
|
1704
|
+
let actionKey = item.data.string("pressInActionKey")
|
|
1705
|
+
guard !actionKey.isEmpty else { return }
|
|
1706
|
+
let origin = (collectionView.cellForItem(at: indexPath) as? NativeListCell)?.rowActionOrigin()
|
|
1707
|
+
handleAction(item: item, actionKey: actionKey, target: nil, origin: origin)
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1611
1710
|
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
|
1612
1711
|
guard let item = config?.items[safe: indexPath.item] else { return false }
|
|
1613
1712
|
return !item.data.bool("disabled")
|
|
@@ -1748,9 +1847,81 @@ private struct NativeListSectionIndexEntry {
|
|
|
1748
1847
|
let position: Int
|
|
1749
1848
|
}
|
|
1750
1849
|
|
|
1850
|
+
private final class NativeListSectionIndexPreviewView: UIView {
|
|
1851
|
+
private let shapeLayer = CAShapeLayer()
|
|
1852
|
+
private let label = UILabel()
|
|
1853
|
+
|
|
1854
|
+
var text: String? {
|
|
1855
|
+
get { label.text }
|
|
1856
|
+
set { label.text = newValue }
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
override init(frame: CGRect) {
|
|
1860
|
+
super.init(frame: frame)
|
|
1861
|
+
isUserInteractionEnabled = false
|
|
1862
|
+
backgroundColor = .clear
|
|
1863
|
+
layer.addSublayer(shapeLayer)
|
|
1864
|
+
label.textAlignment = .center
|
|
1865
|
+
label.adjustsFontForContentSizeCategory = true
|
|
1866
|
+
label.font = nativeListFont(ofSize: 30)
|
|
1867
|
+
label.isAccessibilityElement = false
|
|
1868
|
+
addSubview(label)
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
required init?(coder: NSCoder) {
|
|
1872
|
+
fatalError("init(coder:) has not been implemented")
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
func configure(fillColor: UIColor, textColor: UIColor) {
|
|
1876
|
+
shapeLayer.fillColor = fillColor.cgColor
|
|
1877
|
+
label.textColor = textColor
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
override func layoutSubviews() {
|
|
1881
|
+
super.layoutSubviews()
|
|
1882
|
+
let height = bounds.height
|
|
1883
|
+
let bodyWidth = min(height, max(0, bounds.width - 10))
|
|
1884
|
+
let midY = height / 2
|
|
1885
|
+
let bodyMidX = bodyWidth / 2
|
|
1886
|
+
let shoulderX = bodyWidth * 0.92
|
|
1887
|
+
let path = UIBezierPath()
|
|
1888
|
+
path.move(to: CGPoint(x: bodyMidX, y: 0))
|
|
1889
|
+
path.addCurve(
|
|
1890
|
+
to: CGPoint(x: 0, y: midY),
|
|
1891
|
+
controlPoint1: CGPoint(x: bodyWidth * 0.22, y: 0),
|
|
1892
|
+
controlPoint2: CGPoint(x: 0, y: height * 0.22)
|
|
1893
|
+
)
|
|
1894
|
+
path.addCurve(
|
|
1895
|
+
to: CGPoint(x: bodyMidX, y: height),
|
|
1896
|
+
controlPoint1: CGPoint(x: 0, y: height * 0.78),
|
|
1897
|
+
controlPoint2: CGPoint(x: bodyWidth * 0.22, y: height)
|
|
1898
|
+
)
|
|
1899
|
+
path.addCurve(
|
|
1900
|
+
to: CGPoint(x: shoulderX, y: height * 0.75),
|
|
1901
|
+
controlPoint1: CGPoint(x: bodyWidth * 0.72, y: height),
|
|
1902
|
+
controlPoint2: CGPoint(x: bodyWidth * 0.86, y: height * 0.88)
|
|
1903
|
+
)
|
|
1904
|
+
path.addLine(to: CGPoint(x: bounds.width, y: midY))
|
|
1905
|
+
path.addLine(to: CGPoint(x: shoulderX, y: height * 0.25))
|
|
1906
|
+
path.addCurve(
|
|
1907
|
+
to: CGPoint(x: bodyMidX, y: 0),
|
|
1908
|
+
controlPoint1: CGPoint(x: bodyWidth * 0.86, y: height * 0.12),
|
|
1909
|
+
controlPoint2: CGPoint(x: bodyWidth * 0.72, y: 0)
|
|
1910
|
+
)
|
|
1911
|
+
path.close()
|
|
1912
|
+
shapeLayer.frame = bounds
|
|
1913
|
+
shapeLayer.path = path.cgPath
|
|
1914
|
+
label.frame = CGRect(x: 0, y: 0, width: bodyWidth, height: height)
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1751
1918
|
// OneKey patch: arbitrate index scrubbing against ancestor dismissal gestures.
|
|
1752
1919
|
// private final class NativeListSectionIndexView: UIControl {
|
|
1753
1920
|
private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDelegate {
|
|
1921
|
+
private static let edgePadding: CGFloat = 8
|
|
1922
|
+
private static let labelSize: CGFloat = 14
|
|
1923
|
+
private static let labelSpacing: CGFloat = 16
|
|
1924
|
+
|
|
1754
1925
|
var onSelect: ((Int, Bool) -> Void)?
|
|
1755
1926
|
var onInteractionEnded: (() -> Void)?
|
|
1756
1927
|
private var titles: [String] = []
|
|
@@ -1833,16 +2004,28 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
1833
2004
|
override func layoutSubviews() {
|
|
1834
2005
|
super.layoutSubviews()
|
|
1835
2006
|
guard !labels.isEmpty else { return }
|
|
1836
|
-
let
|
|
1837
|
-
let
|
|
1838
|
-
|
|
2007
|
+
let metrics = indexMetrics(count: labels.count)
|
|
2008
|
+
let visibleIndices = visibleLabelIndices(
|
|
2009
|
+
count: labels.count,
|
|
2010
|
+
trackHeight: metrics.trackHeight
|
|
2011
|
+
)
|
|
2012
|
+
labels.forEach { $0.isHidden = true }
|
|
2013
|
+
for (visibleIndex, index) in visibleIndices.sorted().enumerated() {
|
|
2014
|
+
let label = labels[index]
|
|
2015
|
+
label.isHidden = false
|
|
2016
|
+
let centerY = visibleLabelCenterY(
|
|
2017
|
+
visibleIndex: visibleIndex,
|
|
2018
|
+
visibleCount: visibleIndices.count,
|
|
2019
|
+
metrics: metrics
|
|
2020
|
+
)
|
|
1839
2021
|
label.frame = CGRect(
|
|
1840
|
-
x:
|
|
1841
|
-
y:
|
|
1842
|
-
width:
|
|
1843
|
-
height:
|
|
2022
|
+
x: bounds.width - Self.labelSize - 3,
|
|
2023
|
+
y: centerY - Self.labelSize / 2,
|
|
2024
|
+
width: Self.labelSize,
|
|
2025
|
+
height: Self.labelSize
|
|
1844
2026
|
)
|
|
1845
2027
|
}
|
|
2028
|
+
updateLabelStyles()
|
|
1846
2029
|
}
|
|
1847
2030
|
|
|
1848
2031
|
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
|
@@ -1879,25 +2062,75 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
1879
2062
|
}
|
|
1880
2063
|
|
|
1881
2064
|
private func select(at y: CGFloat, interacting: Bool) {
|
|
1882
|
-
let
|
|
1883
|
-
|
|
1884
|
-
let
|
|
2065
|
+
let metrics = indexMetrics(count: titles.count)
|
|
2066
|
+
guard metrics.trackHeight > 0 else { return }
|
|
2067
|
+
let visibleIndices = visibleLabelIndices(
|
|
2068
|
+
count: titles.count,
|
|
2069
|
+
trackHeight: metrics.trackHeight
|
|
2070
|
+
).sorted()
|
|
2071
|
+
let visibleTrackHeight = min(
|
|
2072
|
+
metrics.trackHeight,
|
|
2073
|
+
Self.labelSpacing * CGFloat(visibleIndices.count)
|
|
2074
|
+
)
|
|
2075
|
+
let visibleOriginY = metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2
|
|
2076
|
+
let progress = ((y - visibleOriginY) / visibleTrackHeight).clamped(to: 0...1)
|
|
2077
|
+
let slot = Int(floor(progress * CGFloat(visibleIndices.count)))
|
|
2078
|
+
.clamped(to: 0...(visibleIndices.count - 1))
|
|
2079
|
+
let index = visibleIndices[slot]
|
|
1885
2080
|
if interacting && lastTouchIndex == index { return }
|
|
1886
2081
|
lastTouchIndex = interacting ? index : nil
|
|
1887
2082
|
select(index: index, interacting: interacting)
|
|
1888
2083
|
}
|
|
1889
2084
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2085
|
+
func centerY(for index: Int) -> CGFloat {
|
|
2086
|
+
entryCenterY(index: index, metrics: indexMetrics(count: titles.count))
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
private func indexMetrics(count: Int) -> (originY: CGFloat, trackHeight: CGFloat) {
|
|
2090
|
+
guard count > 0 else { return (bounds.midY, 0) }
|
|
2091
|
+
let availableHeight = max(0, bounds.height - Self.edgePadding * 2)
|
|
2092
|
+
let trackHeight = min(availableHeight, Self.labelSpacing * CGFloat(count))
|
|
2093
|
+
let centeredOriginY = (bounds.height - trackHeight) / 2
|
|
2094
|
+
guard centeredInWindow, let window else { return (centeredOriginY, trackHeight) }
|
|
1894
2095
|
let windowCenterY = window.safeAreaLayoutGuide.layoutFrame.midY
|
|
1895
2096
|
let localCenterY = convert(CGPoint(x: 0, y: windowCenterY), from: window).y
|
|
1896
|
-
return (
|
|
1897
|
-
|
|
2097
|
+
return (
|
|
2098
|
+
(localCenterY - trackHeight / 2).clamped(
|
|
2099
|
+
to: Self.edgePadding...max(Self.edgePadding, bounds.height - Self.edgePadding - trackHeight)
|
|
2100
|
+
),
|
|
2101
|
+
trackHeight
|
|
1898
2102
|
)
|
|
1899
2103
|
}
|
|
1900
2104
|
|
|
2105
|
+
private func entryCenterY(
|
|
2106
|
+
index: Int,
|
|
2107
|
+
metrics: (originY: CGFloat, trackHeight: CGFloat)
|
|
2108
|
+
) -> CGFloat {
|
|
2109
|
+
guard !titles.isEmpty else { return bounds.midY }
|
|
2110
|
+
return metrics.originY + metrics.trackHeight * (CGFloat(index) + 0.5) / CGFloat(titles.count)
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
private func visibleLabelCenterY(
|
|
2114
|
+
visibleIndex: Int,
|
|
2115
|
+
visibleCount: Int,
|
|
2116
|
+
metrics: (originY: CGFloat, trackHeight: CGFloat)
|
|
2117
|
+
) -> CGFloat {
|
|
2118
|
+
let visibleTrackHeight = min(metrics.trackHeight, Self.labelSpacing * CGFloat(visibleCount))
|
|
2119
|
+
let visibleOriginY = metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2
|
|
2120
|
+
return visibleOriginY
|
|
2121
|
+
+ visibleTrackHeight * (CGFloat(visibleIndex) + 0.5) / CGFloat(max(visibleCount, 1))
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
private func visibleLabelIndices(count: Int, trackHeight: CGFloat) -> Set<Int> {
|
|
2125
|
+
guard count > 0 else { return [] }
|
|
2126
|
+
let maxVisible = max(1, Int(floor(trackHeight / Self.labelSpacing)))
|
|
2127
|
+
guard count > maxVisible else { return Set(0..<count) }
|
|
2128
|
+
guard maxVisible > 1 else { return [0] }
|
|
2129
|
+
return Set((0..<maxVisible).map { slot in
|
|
2130
|
+
Int(round(CGFloat(slot * (count - 1)) / CGFloat(maxVisible - 1)))
|
|
2131
|
+
})
|
|
2132
|
+
}
|
|
2133
|
+
|
|
1901
2134
|
private func select(index: Int, interacting: Bool) {
|
|
1902
2135
|
onSelect?(index, interacting)
|
|
1903
2136
|
setActiveIndex(index)
|
|
@@ -1905,12 +2138,12 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
1905
2138
|
|
|
1906
2139
|
private func updateLabelStyles() {
|
|
1907
2140
|
for (index, label) in labels.enumerated() {
|
|
1908
|
-
let active = index == activeIndex
|
|
2141
|
+
let active = index == activeIndex && !label.isHidden
|
|
1909
2142
|
label.textColor = active ? activeTextColor : textColor
|
|
1910
2143
|
label.backgroundColor = active ? activeColor : .clear
|
|
1911
|
-
label.layer.cornerRadius =
|
|
2144
|
+
label.layer.cornerRadius = Self.labelSize / 2
|
|
1912
2145
|
label.layer.masksToBounds = active
|
|
1913
|
-
label.font = nativeListFont(ofSize: 10, weight: active ? .
|
|
2146
|
+
label.font = nativeListFont(ofSize: 10, weight: active ? .medium : .regular)
|
|
1914
2147
|
}
|
|
1915
2148
|
}
|
|
1916
2149
|
}
|
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>
|