@onekeyfe/react-native-native-list 3.0.114 → 3.0.115
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 +17 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +543 -26
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +404 -90
- package/ios/NativeListCell.swift +506 -20
- package/ios/NativeListDesignAssets.swift +8 -0
- package/ios/RNCNativeListView.swift +271 -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 +129 -1
- package/lib/module/web/NativeListWebEngine.js +461 -51
- package/lib/typescript/src/models.d.ts +82 -2
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +37 -3
- package/package.json +4 -2
- package/src/models.ts +121 -3
- package/src/validation.ts +206 -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,19 @@ 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
|
+
handleAction(item: item, actionKey: actionKey, target: nil, origin: origin)
|
|
1056
|
+
}
|
|
1057
|
+
|
|
997
1058
|
private func handleAction(
|
|
998
1059
|
item: NativeListItem,
|
|
999
1060
|
actionKey: String,
|
|
@@ -1267,10 +1328,18 @@ final class NativeListView: UIView {
|
|
|
1267
1328
|
? 56
|
|
1268
1329
|
: config?.layout == "linear" ? 30 : 36
|
|
1269
1330
|
case "system":
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1331
|
+
if item.data.string("variant") == "loading" && item.data.string("loadingStyle") == "skeleton" {
|
|
1332
|
+
base = 56
|
|
1333
|
+
} else if item.data.string("variant") == "loading" && item.data.string("loadingStyle") == "spinner" {
|
|
1334
|
+
base = 52
|
|
1335
|
+
} else if item.data.string("presentation") == "market" {
|
|
1336
|
+
base = item.data.string("variant") == "loading" ? 68 : 44
|
|
1337
|
+
} else {
|
|
1338
|
+
switch item.data.string("variant") {
|
|
1339
|
+
case "noMatch", "end": base = 36
|
|
1340
|
+
case "retry": base = 44
|
|
1341
|
+
default: base = 56
|
|
1342
|
+
}
|
|
1274
1343
|
}
|
|
1275
1344
|
case "action":
|
|
1276
1345
|
base = item.data.string("presentation") == "accountSelector"
|
|
@@ -1280,6 +1349,14 @@ final class NativeListView: UIView {
|
|
|
1280
1349
|
base = item.data.dictionaries("columns").contains {
|
|
1281
1350
|
!$0.string("secondaryText").isEmpty
|
|
1282
1351
|
} ? 60 : 56
|
|
1352
|
+
case "market":
|
|
1353
|
+
let style = item.data.dictionary("style")
|
|
1354
|
+
let imageHeight = CGFloat(style?.dictionary("image")?.double(
|
|
1355
|
+
"height",
|
|
1356
|
+
default: item.data.string("variant") == "stock" ? 40 : 32
|
|
1357
|
+
) ?? (item.data.string("variant") == "stock" ? 40 : 32))
|
|
1358
|
+
let verticalPadding = CGFloat(style?.double("verticalPadding", default: 12) ?? 12)
|
|
1359
|
+
base = max(item.data.string("variant") == "stock" ? 72 : 68, imageHeight + verticalPadding * 2)
|
|
1283
1360
|
default:
|
|
1284
1361
|
if item.type == "identity", !item.data.string("tertiary").isEmpty {
|
|
1285
1362
|
base = 72
|
|
@@ -1490,6 +1567,16 @@ final class NativeListView: UIView {
|
|
|
1490
1567
|
}
|
|
1491
1568
|
|
|
1492
1569
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
|
1570
|
+
if gestureRecognizer === reorderLongPress || gestureRecognizer === marketLongPress {
|
|
1571
|
+
let point = gestureRecognizer.location(in: collectionView)
|
|
1572
|
+
guard let indexPath = collectionView.indexPathForItem(at: point),
|
|
1573
|
+
let item = item(at: indexPath),
|
|
1574
|
+
!item.data.bool("disabled") else { return false }
|
|
1575
|
+
if gestureRecognizer === reorderLongPress {
|
|
1576
|
+
return config?.reorderable == true && item.isReorderable
|
|
1577
|
+
}
|
|
1578
|
+
return item.type == "market" && !item.data.string("longPressActionKey").isEmpty
|
|
1579
|
+
}
|
|
1493
1580
|
guard gestureRecognizer === listBodyGestureGuard,
|
|
1494
1581
|
let pan = gestureRecognizer as? UIPanGestureRecognizer else { return true }
|
|
1495
1582
|
let velocity = pan.velocity(in: collectionView)
|
|
@@ -1608,6 +1695,14 @@ extension NativeListView: UICollectionViewDelegateFlowLayout {
|
|
|
1608
1695
|
return !item.data.bool("disabled")
|
|
1609
1696
|
}
|
|
1610
1697
|
|
|
1698
|
+
func collectionView(_ collectionView: UICollectionView, didHighlightItemAt indexPath: IndexPath) {
|
|
1699
|
+
guard let item = config?.items[safe: indexPath.item], item.type == "market" else { return }
|
|
1700
|
+
let actionKey = item.data.string("pressInActionKey")
|
|
1701
|
+
guard !actionKey.isEmpty else { return }
|
|
1702
|
+
let origin = (collectionView.cellForItem(at: indexPath) as? NativeListCell)?.rowActionOrigin()
|
|
1703
|
+
handleAction(item: item, actionKey: actionKey, target: nil, origin: origin)
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1611
1706
|
func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
|
1612
1707
|
guard let item = config?.items[safe: indexPath.item] else { return false }
|
|
1613
1708
|
return !item.data.bool("disabled")
|
|
@@ -1748,9 +1843,81 @@ private struct NativeListSectionIndexEntry {
|
|
|
1748
1843
|
let position: Int
|
|
1749
1844
|
}
|
|
1750
1845
|
|
|
1846
|
+
private final class NativeListSectionIndexPreviewView: UIView {
|
|
1847
|
+
private let shapeLayer = CAShapeLayer()
|
|
1848
|
+
private let label = UILabel()
|
|
1849
|
+
|
|
1850
|
+
var text: String? {
|
|
1851
|
+
get { label.text }
|
|
1852
|
+
set { label.text = newValue }
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
override init(frame: CGRect) {
|
|
1856
|
+
super.init(frame: frame)
|
|
1857
|
+
isUserInteractionEnabled = false
|
|
1858
|
+
backgroundColor = .clear
|
|
1859
|
+
layer.addSublayer(shapeLayer)
|
|
1860
|
+
label.textAlignment = .center
|
|
1861
|
+
label.adjustsFontForContentSizeCategory = true
|
|
1862
|
+
label.font = nativeListFont(ofSize: 30)
|
|
1863
|
+
label.isAccessibilityElement = false
|
|
1864
|
+
addSubview(label)
|
|
1865
|
+
}
|
|
1866
|
+
|
|
1867
|
+
required init?(coder: NSCoder) {
|
|
1868
|
+
fatalError("init(coder:) has not been implemented")
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
func configure(fillColor: UIColor, textColor: UIColor) {
|
|
1872
|
+
shapeLayer.fillColor = fillColor.cgColor
|
|
1873
|
+
label.textColor = textColor
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
override func layoutSubviews() {
|
|
1877
|
+
super.layoutSubviews()
|
|
1878
|
+
let height = bounds.height
|
|
1879
|
+
let bodyWidth = min(height, max(0, bounds.width - 10))
|
|
1880
|
+
let midY = height / 2
|
|
1881
|
+
let bodyMidX = bodyWidth / 2
|
|
1882
|
+
let shoulderX = bodyWidth * 0.92
|
|
1883
|
+
let path = UIBezierPath()
|
|
1884
|
+
path.move(to: CGPoint(x: bodyMidX, y: 0))
|
|
1885
|
+
path.addCurve(
|
|
1886
|
+
to: CGPoint(x: 0, y: midY),
|
|
1887
|
+
controlPoint1: CGPoint(x: bodyWidth * 0.22, y: 0),
|
|
1888
|
+
controlPoint2: CGPoint(x: 0, y: height * 0.22)
|
|
1889
|
+
)
|
|
1890
|
+
path.addCurve(
|
|
1891
|
+
to: CGPoint(x: bodyMidX, y: height),
|
|
1892
|
+
controlPoint1: CGPoint(x: 0, y: height * 0.78),
|
|
1893
|
+
controlPoint2: CGPoint(x: bodyWidth * 0.22, y: height)
|
|
1894
|
+
)
|
|
1895
|
+
path.addCurve(
|
|
1896
|
+
to: CGPoint(x: shoulderX, y: height * 0.75),
|
|
1897
|
+
controlPoint1: CGPoint(x: bodyWidth * 0.72, y: height),
|
|
1898
|
+
controlPoint2: CGPoint(x: bodyWidth * 0.86, y: height * 0.88)
|
|
1899
|
+
)
|
|
1900
|
+
path.addLine(to: CGPoint(x: bounds.width, y: midY))
|
|
1901
|
+
path.addLine(to: CGPoint(x: shoulderX, y: height * 0.25))
|
|
1902
|
+
path.addCurve(
|
|
1903
|
+
to: CGPoint(x: bodyMidX, y: 0),
|
|
1904
|
+
controlPoint1: CGPoint(x: bodyWidth * 0.86, y: height * 0.12),
|
|
1905
|
+
controlPoint2: CGPoint(x: bodyWidth * 0.72, y: 0)
|
|
1906
|
+
)
|
|
1907
|
+
path.close()
|
|
1908
|
+
shapeLayer.frame = bounds
|
|
1909
|
+
shapeLayer.path = path.cgPath
|
|
1910
|
+
label.frame = CGRect(x: 0, y: 0, width: bodyWidth, height: height)
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1751
1914
|
// OneKey patch: arbitrate index scrubbing against ancestor dismissal gestures.
|
|
1752
1915
|
// private final class NativeListSectionIndexView: UIControl {
|
|
1753
1916
|
private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDelegate {
|
|
1917
|
+
private static let edgePadding: CGFloat = 8
|
|
1918
|
+
private static let labelSize: CGFloat = 14
|
|
1919
|
+
private static let labelSpacing: CGFloat = 16
|
|
1920
|
+
|
|
1754
1921
|
var onSelect: ((Int, Bool) -> Void)?
|
|
1755
1922
|
var onInteractionEnded: (() -> Void)?
|
|
1756
1923
|
private var titles: [String] = []
|
|
@@ -1833,16 +2000,28 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
1833
2000
|
override func layoutSubviews() {
|
|
1834
2001
|
super.layoutSubviews()
|
|
1835
2002
|
guard !labels.isEmpty else { return }
|
|
1836
|
-
let
|
|
1837
|
-
let
|
|
1838
|
-
|
|
2003
|
+
let metrics = indexMetrics(count: labels.count)
|
|
2004
|
+
let visibleIndices = visibleLabelIndices(
|
|
2005
|
+
count: labels.count,
|
|
2006
|
+
trackHeight: metrics.trackHeight
|
|
2007
|
+
)
|
|
2008
|
+
labels.forEach { $0.isHidden = true }
|
|
2009
|
+
for (visibleIndex, index) in visibleIndices.sorted().enumerated() {
|
|
2010
|
+
let label = labels[index]
|
|
2011
|
+
label.isHidden = false
|
|
2012
|
+
let centerY = visibleLabelCenterY(
|
|
2013
|
+
visibleIndex: visibleIndex,
|
|
2014
|
+
visibleCount: visibleIndices.count,
|
|
2015
|
+
metrics: metrics
|
|
2016
|
+
)
|
|
1839
2017
|
label.frame = CGRect(
|
|
1840
|
-
x:
|
|
1841
|
-
y:
|
|
1842
|
-
width:
|
|
1843
|
-
height:
|
|
2018
|
+
x: bounds.width - Self.labelSize - 3,
|
|
2019
|
+
y: centerY - Self.labelSize / 2,
|
|
2020
|
+
width: Self.labelSize,
|
|
2021
|
+
height: Self.labelSize
|
|
1844
2022
|
)
|
|
1845
2023
|
}
|
|
2024
|
+
updateLabelStyles()
|
|
1846
2025
|
}
|
|
1847
2026
|
|
|
1848
2027
|
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
|
|
@@ -1879,25 +2058,75 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
1879
2058
|
}
|
|
1880
2059
|
|
|
1881
2060
|
private func select(at y: CGFloat, interacting: Bool) {
|
|
1882
|
-
let
|
|
1883
|
-
|
|
1884
|
-
let
|
|
2061
|
+
let metrics = indexMetrics(count: titles.count)
|
|
2062
|
+
guard metrics.trackHeight > 0 else { return }
|
|
2063
|
+
let visibleIndices = visibleLabelIndices(
|
|
2064
|
+
count: titles.count,
|
|
2065
|
+
trackHeight: metrics.trackHeight
|
|
2066
|
+
).sorted()
|
|
2067
|
+
let visibleTrackHeight = min(
|
|
2068
|
+
metrics.trackHeight,
|
|
2069
|
+
Self.labelSpacing * CGFloat(visibleIndices.count)
|
|
2070
|
+
)
|
|
2071
|
+
let visibleOriginY = metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2
|
|
2072
|
+
let progress = ((y - visibleOriginY) / visibleTrackHeight).clamped(to: 0...1)
|
|
2073
|
+
let slot = Int(floor(progress * CGFloat(visibleIndices.count)))
|
|
2074
|
+
.clamped(to: 0...(visibleIndices.count - 1))
|
|
2075
|
+
let index = visibleIndices[slot]
|
|
1885
2076
|
if interacting && lastTouchIndex == index { return }
|
|
1886
2077
|
lastTouchIndex = interacting ? index : nil
|
|
1887
2078
|
select(index: index, interacting: interacting)
|
|
1888
2079
|
}
|
|
1889
2080
|
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2081
|
+
func centerY(for index: Int) -> CGFloat {
|
|
2082
|
+
entryCenterY(index: index, metrics: indexMetrics(count: titles.count))
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
private func indexMetrics(count: Int) -> (originY: CGFloat, trackHeight: CGFloat) {
|
|
2086
|
+
guard count > 0 else { return (bounds.midY, 0) }
|
|
2087
|
+
let availableHeight = max(0, bounds.height - Self.edgePadding * 2)
|
|
2088
|
+
let trackHeight = min(availableHeight, Self.labelSpacing * CGFloat(count))
|
|
2089
|
+
let centeredOriginY = (bounds.height - trackHeight) / 2
|
|
2090
|
+
guard centeredInWindow, let window else { return (centeredOriginY, trackHeight) }
|
|
1894
2091
|
let windowCenterY = window.safeAreaLayoutGuide.layoutFrame.midY
|
|
1895
2092
|
let localCenterY = convert(CGPoint(x: 0, y: windowCenterY), from: window).y
|
|
1896
|
-
return (
|
|
1897
|
-
|
|
2093
|
+
return (
|
|
2094
|
+
(localCenterY - trackHeight / 2).clamped(
|
|
2095
|
+
to: Self.edgePadding...max(Self.edgePadding, bounds.height - Self.edgePadding - trackHeight)
|
|
2096
|
+
),
|
|
2097
|
+
trackHeight
|
|
1898
2098
|
)
|
|
1899
2099
|
}
|
|
1900
2100
|
|
|
2101
|
+
private func entryCenterY(
|
|
2102
|
+
index: Int,
|
|
2103
|
+
metrics: (originY: CGFloat, trackHeight: CGFloat)
|
|
2104
|
+
) -> CGFloat {
|
|
2105
|
+
guard !titles.isEmpty else { return bounds.midY }
|
|
2106
|
+
return metrics.originY + metrics.trackHeight * (CGFloat(index) + 0.5) / CGFloat(titles.count)
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
private func visibleLabelCenterY(
|
|
2110
|
+
visibleIndex: Int,
|
|
2111
|
+
visibleCount: Int,
|
|
2112
|
+
metrics: (originY: CGFloat, trackHeight: CGFloat)
|
|
2113
|
+
) -> CGFloat {
|
|
2114
|
+
let visibleTrackHeight = min(metrics.trackHeight, Self.labelSpacing * CGFloat(visibleCount))
|
|
2115
|
+
let visibleOriginY = metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2
|
|
2116
|
+
return visibleOriginY
|
|
2117
|
+
+ visibleTrackHeight * (CGFloat(visibleIndex) + 0.5) / CGFloat(max(visibleCount, 1))
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
private func visibleLabelIndices(count: Int, trackHeight: CGFloat) -> Set<Int> {
|
|
2121
|
+
guard count > 0 else { return [] }
|
|
2122
|
+
let maxVisible = max(1, Int(floor(trackHeight / Self.labelSpacing)))
|
|
2123
|
+
guard count > maxVisible else { return Set(0..<count) }
|
|
2124
|
+
guard maxVisible > 1 else { return [0] }
|
|
2125
|
+
return Set((0..<maxVisible).map { slot in
|
|
2126
|
+
Int(round(CGFloat(slot * (count - 1)) / CGFloat(maxVisible - 1)))
|
|
2127
|
+
})
|
|
2128
|
+
}
|
|
2129
|
+
|
|
1901
2130
|
private func select(index: Int, interacting: Bool) {
|
|
1902
2131
|
onSelect?(index, interacting)
|
|
1903
2132
|
setActiveIndex(index)
|
|
@@ -1905,12 +2134,12 @@ private final class NativeListSectionIndexView: UIControl, UIGestureRecognizerDe
|
|
|
1905
2134
|
|
|
1906
2135
|
private func updateLabelStyles() {
|
|
1907
2136
|
for (index, label) in labels.enumerated() {
|
|
1908
|
-
let active = index == activeIndex
|
|
2137
|
+
let active = index == activeIndex && !label.isHidden
|
|
1909
2138
|
label.textColor = active ? activeTextColor : textColor
|
|
1910
2139
|
label.backgroundColor = active ? activeColor : .clear
|
|
1911
|
-
label.layer.cornerRadius =
|
|
2140
|
+
label.layer.cornerRadius = Self.labelSize / 2
|
|
1912
2141
|
label.layer.masksToBounds = active
|
|
1913
|
-
label.font = nativeListFont(ofSize: 10, weight: active ? .
|
|
2142
|
+
label.font = nativeListFont(ofSize: 10, weight: active ? .medium : .regular)
|
|
1914
2143
|
}
|
|
1915
2144
|
}
|
|
1916
2145
|
}
|
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>
|