@onekeyfe/react-native-native-list 3.0.105 → 3.0.107
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/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +16 -3
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +6 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +671 -52
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +282 -54
- package/ios/HybridNativeList.swift +8 -2
- package/ios/NativeListCell.swift +630 -31
- package/ios/NativeListDesignAssets.swift +24 -1
- package/ios/RNCNativeListView.swift +216 -42
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/icon.svg +14 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/icon.svg +12 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/icon.svg +6 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/icon.svg +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/icon.svg +3 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/icon.svg +7 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/icon.svg +18 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/icon.svg +7 -0
- package/lib/module/NativeList.js +102 -8
- package/lib/module/NativeList.web.js +20 -2
- package/lib/module/avatarPrefetch.js +174 -0
- package/lib/module/validation.js +45 -2
- package/lib/module/web/NativeListAvatarWorker.js +542 -0
- package/lib/module/web/NativeListWebAvatarCache.js +267 -0
- package/lib/module/web/NativeListWebEngine.js +958 -47
- package/lib/typescript/src/NativeList.web.d.ts +2 -2
- package/lib/typescript/src/avatarPrefetch.d.ts +28 -0
- package/lib/typescript/src/models.d.ts +72 -4
- package/lib/typescript/src/web/NativeListWebAvatarCache.d.ts +6 -0
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +10 -1
- package/package.json +2 -2
- package/src/NativeList.tsx +149 -13
- package/src/NativeList.web.tsx +30 -3
- package/src/avatarPrefetch.ts +236 -0
- package/src/models.ts +98 -7
- package/src/validation.ts +131 -2
- package/src/web/NativeListAvatarWorker.js +567 -0
- package/src/web/NativeListWebAvatarCache.ts +314 -0
- package/src/web/NativeListWebEngine.ts +1409 -60
package/ios/NativeListCell.swift
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import Foundation
|
|
2
|
+
// OneKey patch: preserve native font faces while enabling tabular number features.
|
|
3
|
+
import CoreText
|
|
2
4
|
import OneKeyImage
|
|
3
5
|
import UIKit
|
|
4
6
|
|
|
@@ -8,19 +10,56 @@ final class NativeListActionOrigin {
|
|
|
8
10
|
let bindingEpoch: Int
|
|
9
11
|
let source: String
|
|
10
12
|
let slot: Int?
|
|
13
|
+
// OneKey patch: expose the layout slot while preserving the larger hit target.
|
|
14
|
+
let anchorInset: CGFloat
|
|
11
15
|
|
|
12
16
|
init(
|
|
13
17
|
sourceView: UIView,
|
|
14
18
|
ownerCell: NativeListCell,
|
|
15
19
|
bindingEpoch: Int,
|
|
16
20
|
source: String,
|
|
17
|
-
slot: Int? = nil
|
|
21
|
+
slot: Int? = nil,
|
|
22
|
+
anchorInset: CGFloat = 0
|
|
18
23
|
) {
|
|
19
24
|
self.sourceView = sourceView
|
|
20
25
|
self.ownerCell = ownerCell
|
|
21
26
|
self.bindingEpoch = bindingEpoch
|
|
22
27
|
self.source = source
|
|
23
28
|
self.slot = slot
|
|
29
|
+
self.anchorInset = anchorInset
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// OneKey patch: explicit summary actions use the source text's physical-pixel line box.
|
|
34
|
+
private final class NativeListAccessoryButton: UIButton {
|
|
35
|
+
var selectorSummaryLineHeight: CGFloat? {
|
|
36
|
+
didSet { invalidateIntrinsicContentSize(); setNeedsLayout() }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private var sourcePixelScale: CGFloat {
|
|
40
|
+
max(1, window?.screen.scale ?? traitCollection.displayScale)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
override var intrinsicContentSize: CGSize {
|
|
44
|
+
var size = super.intrinsicContentSize
|
|
45
|
+
guard selectorSummaryLineHeight != nil, let title = attributedTitle(for: .normal) else { return size }
|
|
46
|
+
let width = title.boundingRect(
|
|
47
|
+
with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude),
|
|
48
|
+
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
|
49
|
+
context: nil
|
|
50
|
+
).width
|
|
51
|
+
size.width = ceil(width * sourcePixelScale) / sourcePixelScale
|
|
52
|
+
return size
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
override func layoutSubviews() {
|
|
56
|
+
super.layoutSubviews()
|
|
57
|
+
guard let lineHeight = selectorSummaryLineHeight, let titleLabel else { return }
|
|
58
|
+
// OneKey patch: position the final source line box after UIKit has measured the button.
|
|
59
|
+
let top = ceil((bounds.height - lineHeight) / 2 * sourcePixelScale) / sourcePixelScale
|
|
60
|
+
var frame = titleLabel.frame
|
|
61
|
+
frame.origin.y = top
|
|
62
|
+
titleLabel.frame = frame
|
|
24
63
|
}
|
|
25
64
|
}
|
|
26
65
|
|
|
@@ -54,6 +93,24 @@ private final class NativeListDottedUnderlineLabel: UILabel {
|
|
|
54
93
|
didSet { setNeedsLayout() }
|
|
55
94
|
}
|
|
56
95
|
|
|
96
|
+
// OneKey patch: migrated section titles include the source 3-point underline box.
|
|
97
|
+
var reservesDottedUnderlineSpace = false {
|
|
98
|
+
didSet { invalidateIntrinsicContentSize(); setNeedsLayout(); setNeedsDisplay() }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
override var intrinsicContentSize: CGSize {
|
|
102
|
+
var size = super.intrinsicContentSize
|
|
103
|
+
if reservesDottedUnderlineSpace && showsDottedUnderline { size.height += 3 }
|
|
104
|
+
return size
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
override func drawText(in rect: CGRect) {
|
|
108
|
+
let textRect = reservesDottedUnderlineSpace && showsDottedUnderline
|
|
109
|
+
? CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: max(0, rect.height - 3))
|
|
110
|
+
: rect
|
|
111
|
+
super.drawText(in: textRect)
|
|
112
|
+
}
|
|
113
|
+
|
|
57
114
|
var dottedUnderlineColor: UIColor = .clear {
|
|
58
115
|
didSet {
|
|
59
116
|
dottedUnderlineLayer.strokeColor = dottedUnderlineColor.cgColor
|
|
@@ -101,7 +158,9 @@ private final class NativeListDottedUnderlineLabel: UILabel {
|
|
|
101
158
|
width: bounds.width,
|
|
102
159
|
height: bounds.height + 2 + dottedUnderlineVerticalOffset
|
|
103
160
|
)
|
|
104
|
-
|
|
161
|
+
// OneKey patch: explicit header underline occupies the reserved final two points.
|
|
162
|
+
// let y = bounds.height + 1 + dottedUnderlineVerticalOffset
|
|
163
|
+
let y = reservesDottedUnderlineSpace ? bounds.height - 1 : bounds.height + 1 + dottedUnderlineVerticalOffset
|
|
105
164
|
let path = UIBezierPath()
|
|
106
165
|
path.move(to: CGPoint(x: 1, y: y))
|
|
107
166
|
path.addLine(to: CGPoint(x: max(1, textWidth - 1), y: y))
|
|
@@ -257,6 +316,9 @@ private final class NativeListTableColumnView: UIStackView {
|
|
|
257
316
|
|
|
258
317
|
private func textColor(_ tone: String, theme: [String: Any]?) -> UIColor {
|
|
259
318
|
switch tone {
|
|
319
|
+
// OneKey patch: account warnings and hidden balances use existing theme tokens.
|
|
320
|
+
case "disabled": return nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
321
|
+
case "caution": return nativeListColor(theme, "caution", "#AB6400")
|
|
260
322
|
case "secondary": return nativeListColor(theme, "secondaryText", "#646464")
|
|
261
323
|
case "positive": return nativeListColor(theme, "positive", "#218358")
|
|
262
324
|
case "negative": return nativeListColor(theme, "negative", "#CE2C31")
|
|
@@ -273,6 +335,13 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
273
335
|
private let leadingOverlayBackground = UIView()
|
|
274
336
|
private let leadingCornerIconBackground = UIView()
|
|
275
337
|
private let leadingCornerIconImageView = UIImageView()
|
|
338
|
+
// OneKey patch: selector-only views are reset on every native cell binding.
|
|
339
|
+
private var selectorViews: [UIView] = []
|
|
340
|
+
private var selectorConstraints: [NSLayoutConstraint] = []
|
|
341
|
+
private var selectorImages: [OneKeyImageReusableView] = []
|
|
342
|
+
private var selectorBorder: CAShapeLayer?
|
|
343
|
+
private let selectorFullWidthBackground = CALayer()
|
|
344
|
+
private lazy var selectorTitleTap = UITapGestureRecognizer(target: self, action: #selector(selectorTitlePressed))
|
|
276
345
|
private let secondaryImage = OneKeyImageReusableView(frame: .zero)
|
|
277
346
|
private let mediaNetworkImage = OneKeyImageReusableView(frame: .zero)
|
|
278
347
|
private let fallbackLabel = UILabel()
|
|
@@ -296,7 +365,9 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
296
365
|
private let actionStack = UIStackView()
|
|
297
366
|
private let actionButtons = (0..<3).map { _ in UIButton(type: .system) }
|
|
298
367
|
private let trailingStack = UIStackView()
|
|
299
|
-
|
|
368
|
+
// OneKey patch: summary actions opt into source typography while other buttons keep UIKit layout.
|
|
369
|
+
// private let accessoryButtons = (0..<2).map { _ in UIButton(type: .system) }
|
|
370
|
+
private let accessoryButtons = (0..<2).map { _ in NativeListAccessoryButton(type: .system) }
|
|
300
371
|
private let checkboxButton = UIButton(type: .system)
|
|
301
372
|
private let spinner = UIActivityIndicatorView(style: .medium)
|
|
302
373
|
private let dataStack = UIStackView()
|
|
@@ -332,6 +403,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
332
403
|
private var leadingSlotConstraints: [NSLayoutConstraint] = []
|
|
333
404
|
private var dataWeightConstraints: [NSLayoutConstraint] = []
|
|
334
405
|
private var accessorySizeConstraints: [NSLayoutConstraint] = []
|
|
406
|
+
// OneKey patch: restore selector-only font features before a cell is reused.
|
|
407
|
+
private var selectorTypographyRestorers: [() -> Void] = []
|
|
335
408
|
private var currentItem: NativeListItem?
|
|
336
409
|
private var accessoryActions: [(String, NativeSelectionTarget?)] = []
|
|
337
410
|
private var footerActionKeys: [String] = []
|
|
@@ -345,12 +418,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
345
418
|
fallback: .lightGray
|
|
346
419
|
)
|
|
347
420
|
private var checkboxCheckedColor = UIColor(nativeListHex: "#202020", fallback: .black)
|
|
421
|
+
private var checkboxIconColor = UIColor.white
|
|
348
422
|
private var checkboxUncheckedColor = UIColor(nativeListHex: "#FCFCFC", fallback: .white)
|
|
349
423
|
private var checkboxBorderColor = UIColor(nativeListHex: "#CECECE", fallback: .lightGray)
|
|
350
424
|
private var visualBackdropColor = UIColor.white
|
|
351
425
|
private var currentLayout = "linear"
|
|
352
426
|
private var currentTheme: [String: Any]?
|
|
353
427
|
private var currentItemIndex: Int?
|
|
428
|
+
// OneKey patch: delayed image retries belong to the current reusable cell binding.
|
|
429
|
+
private var selectorImageRetries: [ObjectIdentifier: DispatchWorkItem] = [:]
|
|
354
430
|
private(set) var bindingEpoch = 0
|
|
355
431
|
|
|
356
432
|
var onAction: ((NativeListItem, String, NativeSelectionTarget?, NativeListActionOrigin?) -> Void)?
|
|
@@ -615,12 +691,41 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
615
691
|
|
|
616
692
|
override func layoutSubviews() {
|
|
617
693
|
super.layoutSubviews()
|
|
694
|
+
// OneKey patch: extend only the background across the section list outer inset.
|
|
695
|
+
if currentItem?.data.bool("backgroundFullWidth") == true {
|
|
696
|
+
selectorFullWidthBackground.frame = CGRect(x: -frame.minX, y: 0, width: superview?.bounds.width ?? bounds.width, height: bounds.height)
|
|
697
|
+
}
|
|
618
698
|
if currentItem?.type == "mediaTile" {
|
|
619
699
|
mediaHeight.constant = max(0, contentView.bounds.width - 20)
|
|
620
700
|
}
|
|
701
|
+
if currentItem?.type == "identity", currentItem?.data["height"] != nil,
|
|
702
|
+
currentItem?.data.string("presentation") == "accountSelector",
|
|
703
|
+
let accessory = currentItem?.data.dictionaries("trailing").first,
|
|
704
|
+
accessory.string("kind") == "icon", accessory.string("name") == "PlusSmallOutline" {
|
|
705
|
+
// OneKey patch: PlusButton's fixed top18 slot and negative7 margin place its frame at11.
|
|
706
|
+
let button = accessoryButtons[0]
|
|
707
|
+
button.transform = .identity
|
|
708
|
+
let origin = button.convert(button.bounds, to: contentView).minY
|
|
709
|
+
button.transform = CGAffineTransform(translationX: 0, y: 11 - origin)
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// OneKey patch: trim only detached/recycled members, never a live compact
|
|
714
|
+
// drag proxy or an expanding group. Small groups retain eight reusable cells.
|
|
715
|
+
private func trimWalletGroupCells(keeping required: Int) {
|
|
716
|
+
let retained = max(8, required)
|
|
717
|
+
while walletGroupCells.count > retained {
|
|
718
|
+
let cell = walletGroupCells.removeLast()
|
|
719
|
+
rootStack.removeArrangedSubview(cell)
|
|
720
|
+
cell.removeFromSuperview()
|
|
721
|
+
cell.prepareForReuse()
|
|
722
|
+
cell.onAction = nil
|
|
723
|
+
cell.onBindingInvalidated = nil
|
|
724
|
+
}
|
|
621
725
|
}
|
|
622
726
|
|
|
623
727
|
override func prepareForReuse() {
|
|
728
|
+
let canTrimMembers = !walletGroupCompactAppearanceActive && (rootStack.layer.animationKeys()?.isEmpty ?? true)
|
|
624
729
|
super.prepareForReuse()
|
|
625
730
|
invalidateCurrentBinding()
|
|
626
731
|
isHighlighted = false
|
|
@@ -632,6 +737,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
632
737
|
walletGroupCompactContainer.alpha = 1
|
|
633
738
|
walletGroupCompactCell?.prepareForReuse()
|
|
634
739
|
walletGroupCells.forEach { $0.prepareForReuse() }
|
|
740
|
+
walletGroupMembers.removeAll()
|
|
741
|
+
if canTrimMembers { trimWalletGroupCells(keeping: 0) }
|
|
635
742
|
leadingImages.forEach { $0.prepareForReuse() }
|
|
636
743
|
secondaryImage.prepareForReuse()
|
|
637
744
|
mediaNetworkImage.prepareForReuse()
|
|
@@ -645,6 +752,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
645
752
|
selected: Bool,
|
|
646
753
|
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
647
754
|
) {
|
|
755
|
+
// OneKey patch: a same-row snapshot refresh must not clear a touch that is still held.
|
|
756
|
+
let shouldRestoreHighlight = isHighlighted && currentItem?.key == item.key
|
|
648
757
|
invalidateCurrentBinding()
|
|
649
758
|
bindingEpoch &+= 1
|
|
650
759
|
currentLayout = layout
|
|
@@ -664,6 +773,14 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
664
773
|
// Checkbox uses the literal neutral7 alpha token. Applying opacity to the
|
|
665
774
|
// opaque primary text color produces a different RGB result.
|
|
666
775
|
checkboxBorderColor = UIColor(nativeListHex: "#00000031", fallback: .lightGray)
|
|
776
|
+
checkboxIconColor = checkboxUncheckedColor
|
|
777
|
+
if item.data.string("presentation") == "networkSelector" {
|
|
778
|
+
checkboxCheckedColor = nativeListColor(theme, "checkboxBackground", "#202020")
|
|
779
|
+
checkboxBorderColor = nativeListColor(theme, "checkboxBorder", "#00000031")
|
|
780
|
+
checkboxIconColor = nativeListColor(theme, "checkboxIcon", "#FFFFFF")
|
|
781
|
+
// OneKey patch: the V1 checkbox fills even its unchecked body with iconInverse.
|
|
782
|
+
checkboxUncheckedColor = checkboxIconColor
|
|
783
|
+
}
|
|
667
784
|
visualBackdropColor = nativeListColor(theme, "rowBackground", "#FFFFFF")
|
|
668
785
|
titleLabel.textColor = primary
|
|
669
786
|
subtitleLabel.textColor = secondary
|
|
@@ -693,6 +810,11 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
693
810
|
pressedBackgroundColor = restingBackgroundColor
|
|
694
811
|
}
|
|
695
812
|
updateBackgroundColor()
|
|
813
|
+
if item.data.bool("backgroundFullWidth"), let background = item.data["backgroundColor"] as? String {
|
|
814
|
+
selectorFullWidthBackground.backgroundColor = UIColor(nativeListHex: background, fallback: .clear).cgColor
|
|
815
|
+
contentView.layer.insertSublayer(selectorFullWidthBackground, at: 0)
|
|
816
|
+
clipsToBounds = false
|
|
817
|
+
}
|
|
696
818
|
if layout == "table" {
|
|
697
819
|
if item.type == "dataRow" {
|
|
698
820
|
rootLeadingConstraint.constant = 20
|
|
@@ -707,8 +829,12 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
707
829
|
}
|
|
708
830
|
applyGroupPosition(item.data.string("groupPosition"))
|
|
709
831
|
isUserInteractionEnabled = !item.data.bool("disabled")
|
|
710
|
-
|
|
832
|
+
// OneKey patch: deprecated wallets remain interactive while dimmed.
|
|
833
|
+
// contentView.alpha = isUserInteractionEnabled ? 1 : 0.5
|
|
834
|
+
contentView.alpha = CGFloat(item.data.double("opacity", default: 1)) * (isUserInteractionEnabled ? 1 : 0.5)
|
|
711
835
|
accessibilityLabel = item.data.string("accessibilityLabel", default: item.data.string("title"))
|
|
836
|
+
// OneKey patch: keep existing selector automation identifiers.
|
|
837
|
+
accessibilityIdentifier = item.data["testID"] as? String
|
|
712
838
|
|
|
713
839
|
switch item.type {
|
|
714
840
|
case "walletGroup": bindWalletGroup(item, theme: theme, layout: layout, checkboxState)
|
|
@@ -724,6 +850,17 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
724
850
|
case "system": bindSystem(item, theme: theme)
|
|
725
851
|
default: break
|
|
726
852
|
}
|
|
853
|
+
applySelectorTypography(item)
|
|
854
|
+
if shouldRestoreHighlight && isUserInteractionEnabled {
|
|
855
|
+
isHighlighted = true
|
|
856
|
+
}
|
|
857
|
+
if item.type == "sectionHeader", item.data.string("presentation") == "networkSelector", item.data["height"] != nil, item.data.dictionary("checkbox") != nil, !item.data.string("value").isEmpty {
|
|
858
|
+
// OneKey patch: UIKit must reserve only the total's intrinsic width before the checkbox.
|
|
859
|
+
let valueWidth = accessoryButtons[0].intrinsicContentSize.width
|
|
860
|
+
let width = trailingStack.widthAnchor.constraint(equalToConstant: valueWidth + 12 + 20)
|
|
861
|
+
width.isActive = true
|
|
862
|
+
selectorConstraints.append(width)
|
|
863
|
+
}
|
|
727
864
|
}
|
|
728
865
|
|
|
729
866
|
func updateSelection(
|
|
@@ -732,6 +869,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
732
869
|
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
733
870
|
) {
|
|
734
871
|
guard currentItem?.key == item.key else { return }
|
|
872
|
+
restoreSelectorTypography()
|
|
873
|
+
defer { applySelectorTypography(item) }
|
|
735
874
|
if item.type == "walletGroup" {
|
|
736
875
|
currentItem = item
|
|
737
876
|
let memberData = [item.data.dictionary("parent")].compactMap { $0 }
|
|
@@ -777,6 +916,13 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
777
916
|
selected ? "#FFFFFFED" : "#FFFFFFAF"
|
|
778
917
|
)
|
|
779
918
|
}
|
|
919
|
+
// OneKey patch: retain the latest descriptor when a controlled echo avoids full binding.
|
|
920
|
+
if boundCheckboxData != nil {
|
|
921
|
+
let latestCheckbox = item.type == "identity"
|
|
922
|
+
? item.data.dictionaries("trailing").last { $0.string("kind") == "checkbox" }
|
|
923
|
+
: item.data.dictionary("checkbox")
|
|
924
|
+
boundCheckboxData = latestCheckbox ?? boundCheckboxData
|
|
925
|
+
}
|
|
780
926
|
guard let data = boundCheckboxData, let target = boundCheckboxTarget else { return }
|
|
781
927
|
updateCheckboxPresentation(
|
|
782
928
|
item,
|
|
@@ -786,12 +932,55 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
786
932
|
)
|
|
787
933
|
}
|
|
788
934
|
|
|
935
|
+
// OneKey patch: match SizableText TABULAR_NUMS on every selector text run, retaining its face and size.
|
|
936
|
+
private func selectorTabularFont(_ font: UIFont) -> UIFont {
|
|
937
|
+
var settings = font.fontDescriptor.fontAttributes[.featureSettings] as? [[UIFontDescriptor.FeatureKey: Int]] ?? []
|
|
938
|
+
settings.removeAll { $0[.type] == kNumberSpacingType }
|
|
939
|
+
settings.append([.type: kNumberSpacingType, .selector: kMonospacedNumbersSelector])
|
|
940
|
+
return UIFont(descriptor: font.fontDescriptor.addingAttributes([.featureSettings: settings]), size: font.pointSize)
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
private func selectorTabularText(_ original: NSAttributedString) -> NSAttributedString {
|
|
944
|
+
let result = NSMutableAttributedString(attributedString: original)
|
|
945
|
+
// OneKey patch: body typography explicitly supplies letterSpacing=0 in Tamagui.
|
|
946
|
+
result.addAttribute(.kern, value: 0, range: NSRange(location: 0, length: result.length))
|
|
947
|
+
original.enumerateAttribute(.font, in: NSRange(location: 0, length: original.length)) { value, range, _ in
|
|
948
|
+
if let font = value as? UIFont { result.addAttribute(.font, value: self.selectorTabularFont(font), range: range) }
|
|
949
|
+
}
|
|
950
|
+
return result
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
private func restoreSelectorTypography() {
|
|
954
|
+
selectorTypographyRestorers.reversed().forEach { $0() }
|
|
955
|
+
selectorTypographyRestorers.removeAll()
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
private func applySelectorTypography(_ item: NativeListItem) {
|
|
959
|
+
guard ["accountSelector", "networkSelector", "walletSidebar"].contains(item.data.string("presentation")) || item.type == "system" && item.data.string("variant") == "warning" else { return }
|
|
960
|
+
func visit(_ view: UIView) {
|
|
961
|
+
if let button = view as? UIButton {
|
|
962
|
+
if let original = button.attributedTitle(for: .normal) {
|
|
963
|
+
selectorTypographyRestorers.append { button.setAttributedTitle(original, for: .normal) }
|
|
964
|
+
button.setAttributedTitle(selectorTabularText(original), for: .normal)
|
|
965
|
+
}
|
|
966
|
+
} else if let label = view as? UILabel, let font = label.font {
|
|
967
|
+
let original = label.attributedText
|
|
968
|
+
selectorTypographyRestorers.append { label.font = font; label.attributedText = original }
|
|
969
|
+
label.font = selectorTabularFont(font)
|
|
970
|
+
if let original { label.attributedText = selectorTabularText(original) }
|
|
971
|
+
}
|
|
972
|
+
for child in view.subviews { visit(child) }
|
|
973
|
+
}
|
|
974
|
+
visit(contentView)
|
|
975
|
+
}
|
|
976
|
+
|
|
789
977
|
private func updateSummaryText(_ item: NativeListItem) {
|
|
790
978
|
let title = item.data.string("title")
|
|
791
979
|
titleLabel.isHidden = title.isEmpty
|
|
792
980
|
setLineHeight(titleLabel, text: title, lineHeight: 24)
|
|
793
981
|
|
|
794
982
|
let value = item.data.string("value")
|
|
983
|
+
let isExplicitNetworkHeader = item.data.string("presentation") == "networkSelector" && item.data["height"] != nil
|
|
795
984
|
let valueButton = accessoryButtons[0]
|
|
796
985
|
valueButton.isHidden = value.isEmpty
|
|
797
986
|
if value.isEmpty {
|
|
@@ -801,7 +990,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
801
990
|
setButtonLine(
|
|
802
991
|
valueButton,
|
|
803
992
|
text: value,
|
|
804
|
-
font: nativeListFont(ofSize: 16),
|
|
993
|
+
font: nativeListFont(ofSize: 16, weight: isExplicitNetworkHeader ? .medium : .regular),
|
|
805
994
|
color: nativeListColor(currentTheme, "secondaryText", "#646464"),
|
|
806
995
|
lineHeight: 24
|
|
807
996
|
)
|
|
@@ -826,7 +1015,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
826
1015
|
// Selection is communicated by the destination state for these source
|
|
827
1016
|
// components; neither has a persistent selected tile background.
|
|
828
1017
|
color = nativeListColor(theme, "rowBackground", "#FFFFFF")
|
|
829
|
-
} else if layout == "sectioned" {
|
|
1018
|
+
} else if layout == "sectioned", !item.data.bool("selected") {
|
|
830
1019
|
// Checkbox-backed section lists in app-monorepo keep rows on $bg;
|
|
831
1020
|
// selection is represented by the checkbox itself.
|
|
832
1021
|
color = nativeListColor(theme, "rowBackground", "#FFFFFF")
|
|
@@ -836,10 +1025,27 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
836
1025
|
!selected {
|
|
837
1026
|
color = nativeListColor(theme, "subduedBackground", "#F9F9F9")
|
|
838
1027
|
}
|
|
1028
|
+
// OneKey patch: portfolio group headers retain their source background.
|
|
1029
|
+
if let backgroundColor = item.data["backgroundColor"] as? String {
|
|
1030
|
+
return UIColor(nativeListHex: backgroundColor, fallback: color)
|
|
1031
|
+
}
|
|
839
1032
|
return color
|
|
840
1033
|
}
|
|
841
1034
|
|
|
842
1035
|
private func reset() {
|
|
1036
|
+
restoreSelectorTypography()
|
|
1037
|
+
// OneKey patch: remove selector decorations before rebinding recycled cells.
|
|
1038
|
+
selectorViews.forEach { $0.removeFromSuperview() }
|
|
1039
|
+
selectorViews.removeAll()
|
|
1040
|
+
NSLayoutConstraint.deactivate(selectorConstraints)
|
|
1041
|
+
selectorConstraints.removeAll()
|
|
1042
|
+
selectorImages.forEach { $0.prepareForReuse() }
|
|
1043
|
+
selectorImages.removeAll()
|
|
1044
|
+
selectorFullWidthBackground.removeFromSuperlayer()
|
|
1045
|
+
selectorBorder?.removeFromSuperlayer()
|
|
1046
|
+
selectorBorder = nil
|
|
1047
|
+
titleLabel.removeGestureRecognizer(selectorTitleTap)
|
|
1048
|
+
titleLabel.isUserInteractionEnabled = false
|
|
843
1049
|
walletGroupCompactCell?.invalidateCurrentBinding()
|
|
844
1050
|
walletGroupCells.forEach { $0.invalidateCurrentBinding() }
|
|
845
1051
|
isHighlighted = false
|
|
@@ -848,6 +1054,10 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
848
1054
|
$0.removeFromSuperview()
|
|
849
1055
|
}
|
|
850
1056
|
walletGroupMembers.removeAll()
|
|
1057
|
+
// OneKey patch: reset has detached the old hierarchy, including on direct
|
|
1058
|
+
// large-to-small binds that do not pass through UICollectionView reuse.
|
|
1059
|
+
let requiredMembers = currentItem?.type == "walletGroup" ? (currentItem?.data.dictionaries("children").count ?? 0) + 1 : 0
|
|
1060
|
+
trimWalletGroupCells(keeping: requiredMembers)
|
|
851
1061
|
walletGroupCompactAppearanceActive = false
|
|
852
1062
|
walletGroupCompactContainer.isHidden = true
|
|
853
1063
|
walletGroupCompactContainer.alpha = 1
|
|
@@ -978,11 +1188,13 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
978
1188
|
$0.backgroundColor = .clear
|
|
979
1189
|
}
|
|
980
1190
|
accessoryButtons.enumerated().forEach { index, button in
|
|
1191
|
+
button.selectorSummaryLineHeight = nil
|
|
981
1192
|
button.titleLabel?.font = nativeListFont(
|
|
982
1193
|
ofSize: index == 0 ? 16 : 14,
|
|
983
1194
|
weight: index == 0 ? .medium : .regular
|
|
984
1195
|
)
|
|
985
1196
|
button.isHidden = true
|
|
1197
|
+
button.accessibilityIdentifier = nil
|
|
986
1198
|
button.setTitle(nil, for: .normal)
|
|
987
1199
|
button.setAttributedTitle(nil, for: .normal)
|
|
988
1200
|
button.setImage(nil, for: .normal)
|
|
@@ -994,6 +1206,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
994
1206
|
button.backgroundColor = .clear
|
|
995
1207
|
button.layer.cornerRadius = 0
|
|
996
1208
|
button.contentEdgeInsets = .zero
|
|
1209
|
+
button.transform = .identity
|
|
997
1210
|
}
|
|
998
1211
|
checkboxButton.isHidden = true
|
|
999
1212
|
checkboxButton.alpha = 1
|
|
@@ -1028,6 +1241,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1028
1241
|
contentView.clipsToBounds = false
|
|
1029
1242
|
leadingContainer.alpha = 1
|
|
1030
1243
|
titleLabel.showsDottedUnderline = false
|
|
1244
|
+
titleLabel.reservesDottedUnderlineSpace = false
|
|
1031
1245
|
titleLabel.dottedUnderlineVerticalOffset = 0
|
|
1032
1246
|
}
|
|
1033
1247
|
|
|
@@ -1073,7 +1287,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1073
1287
|
while walletGroupCells.count < walletGroupMembers.count {
|
|
1074
1288
|
let memberCell = NativeListCell(frame: .zero)
|
|
1075
1289
|
memberCell.translatesAutoresizingMaskIntoConstraints = false
|
|
1076
|
-
|
|
1290
|
+
// OneKey patch: each member's current height is applied when bound.
|
|
1291
|
+
// memberCell.heightAnchor.constraint(equalToConstant: 68).isActive = true
|
|
1077
1292
|
walletGroupCells.append(memberCell)
|
|
1078
1293
|
}
|
|
1079
1294
|
rootStack.axis = .vertical
|
|
@@ -1083,8 +1298,18 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1083
1298
|
rootTrailingConstraint.constant = 0
|
|
1084
1299
|
rootTopConstraint.constant = 0
|
|
1085
1300
|
rootBottomConstraint.constant = 0
|
|
1301
|
+
if memberData.first?["height"] != nil {
|
|
1302
|
+
// OneKey patch: the source group's one-point border occupies layout space.
|
|
1303
|
+
rootLeadingConstraint.constant = 1
|
|
1304
|
+
rootTrailingConstraint.constant = -1
|
|
1305
|
+
rootTopConstraint.constant = 1
|
|
1306
|
+
rootBottomConstraint.constant = -1
|
|
1307
|
+
}
|
|
1086
1308
|
walletGroupMembers.enumerated().forEach { index, member in
|
|
1087
1309
|
let memberCell = walletGroupCells[index]
|
|
1310
|
+
// OneKey patch: badges add a second line within their logical wallet group.
|
|
1311
|
+
memberCell.constraints.filter { $0.firstAttribute == .height && $0.secondItem == nil }.forEach { $0.isActive = false }
|
|
1312
|
+
memberCell.heightAnchor.constraint(equalToConstant: CGFloat(member.data.double("height", default: member.data.dictionaries("badges").isEmpty ? 68 : 92))).isActive = true
|
|
1088
1313
|
memberCell.onAction = { [weak self] source, action, target, origin in
|
|
1089
1314
|
self?.onAction?(source, action, target, origin)
|
|
1090
1315
|
}
|
|
@@ -1140,7 +1365,10 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1140
1365
|
let point = gesture.location(in: rootStack)
|
|
1141
1366
|
for (index, cell) in walletGroupCells.prefix(walletGroupMembers.count).enumerated()
|
|
1142
1367
|
where cell.frame.contains(point) {
|
|
1143
|
-
|
|
1368
|
+
// OneKey patch: group member press gating must not disable accessory controls.
|
|
1369
|
+
if !walletGroupMembers[index].data.bool("pressDisabled") {
|
|
1370
|
+
onAction?(walletGroupMembers[index], "press", nil, cell.rowActionOrigin())
|
|
1371
|
+
}
|
|
1144
1372
|
return
|
|
1145
1373
|
}
|
|
1146
1374
|
}
|
|
@@ -1269,8 +1497,14 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1269
1497
|
contentView.clipsToBounds = true
|
|
1270
1498
|
} else {
|
|
1271
1499
|
applyGroupPosition(currentItem?.data.string("groupPosition") ?? "")
|
|
1272
|
-
|
|
1500
|
+
// OneKey patch: explicit account and network selectors preserve ListItem radius while idle.
|
|
1501
|
+
// let restingRadius: CGFloat = currentItem?.type == "metricCard" ? 12 : 0
|
|
1502
|
+
let isSelectorIdentity = currentItem?.type == "identity" && currentItem?.data["height"] != nil
|
|
1503
|
+
let isAccountSelector = isSelectorIdentity && ["accountSelector", "networkSelector"].contains(currentItem?.data.string("presentation") ?? "")
|
|
1504
|
+
let isWalletSidebar = isSelectorIdentity && currentItem?.data.string("presentation") == "walletSidebar"
|
|
1505
|
+
let restingRadius: CGFloat = isWalletSidebar ? 20 : currentItem?.type == "metricCard" || isAccountSelector ? 12 : 0
|
|
1273
1506
|
contentView.layer.cornerRadius = restingRadius
|
|
1507
|
+
contentView.layer.cornerCurve = isWalletSidebar ? .continuous : .circular
|
|
1274
1508
|
contentView.clipsToBounds = restingRadius > 0
|
|
1275
1509
|
}
|
|
1276
1510
|
}
|
|
@@ -1299,6 +1533,17 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1299
1533
|
fallbackLabel.font = nativeListFont(ofSize: 28)
|
|
1300
1534
|
addLeading(item.data.dictionary("leading"), key: item.key)
|
|
1301
1535
|
rootStack.addArrangedSubview(mainStack)
|
|
1536
|
+
// OneKey patch: activate width constraints only after both stacks share an ancestor.
|
|
1537
|
+
if item.data["height"] != nil {
|
|
1538
|
+
titleLabel.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
1539
|
+
titleRowStack.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
1540
|
+
let width = mainStack.widthAnchor.constraint(equalTo: rootStack.widthAnchor)
|
|
1541
|
+
width.isActive = true
|
|
1542
|
+
selectorConstraints.append(width)
|
|
1543
|
+
let titleWidth = titleRowStack.widthAnchor.constraint(lessThanOrEqualTo: mainStack.widthAnchor)
|
|
1544
|
+
titleWidth.isActive = true
|
|
1545
|
+
selectorConstraints.append(titleWidth)
|
|
1546
|
+
}
|
|
1302
1547
|
show(titleLabel, item.data.string("title"), lines: 1)
|
|
1303
1548
|
setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 16)
|
|
1304
1549
|
titleLabel.textColor = nativeListColor(
|
|
@@ -1306,6 +1551,32 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1306
1551
|
selected ? "primaryText" : "secondaryText",
|
|
1307
1552
|
selected ? "#FFFFFFED" : "#FFFFFFAF"
|
|
1308
1553
|
)
|
|
1554
|
+
// OneKey patch: wallet tags belong below the centered name.
|
|
1555
|
+
let badges = item.data.dictionaries("badges")
|
|
1556
|
+
if !badges.isEmpty {
|
|
1557
|
+
let line = UIStackView()
|
|
1558
|
+
line.axis = .horizontal
|
|
1559
|
+
line.spacing = 4
|
|
1560
|
+
line.alignment = .center
|
|
1561
|
+
for badge in badges {
|
|
1562
|
+
let label = NativeListInsetLabel()
|
|
1563
|
+
let isSelector = item.data["height"] != nil
|
|
1564
|
+
let isWarning = badge.string("tone") == "warning"
|
|
1565
|
+
label.font = nativeListFont(ofSize: isSelector ? 11 : 12)
|
|
1566
|
+
label.textColor = nativeListColor(theme, isSelector && isWarning ? "caution" : "secondaryText", isSelector && isWarning ? "#AB6400" : "#646464")
|
|
1567
|
+
label.backgroundColor = nativeListColor(theme, isSelector ? (isWarning ? "cautionBackground" : "subduedBackground") : "strongBackground", isSelector && isWarning ? "#FFF8C5" : "#F0F0F0")
|
|
1568
|
+
label.horizontalInset = isSelector ? 6 : 4
|
|
1569
|
+
label.topInset = 2
|
|
1570
|
+
label.bottomInset = 2
|
|
1571
|
+
label.layer.cornerRadius = 4
|
|
1572
|
+
label.clipsToBounds = true
|
|
1573
|
+
setLineHeight(label, text: badge.string("text"), lineHeight: isSelector ? 14 : 16)
|
|
1574
|
+
line.addArrangedSubview(label)
|
|
1575
|
+
}
|
|
1576
|
+
mainStack.spacing = 4
|
|
1577
|
+
mainStack.addArrangedSubview(line)
|
|
1578
|
+
selectorViews.append(line)
|
|
1579
|
+
}
|
|
1309
1580
|
return
|
|
1310
1581
|
}
|
|
1311
1582
|
if item.data.string("presentation") == "accountSelector" {
|
|
@@ -1344,6 +1615,12 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1344
1615
|
rootStack.setCustomSpacing(5, after: leadingActionButton)
|
|
1345
1616
|
}
|
|
1346
1617
|
addLeading(item.data.dictionary("leading"), key: item.key)
|
|
1618
|
+
// OneKey patch: custom network initials match LetterAvatar size 32.
|
|
1619
|
+
if item.data.string("presentation") == "networkSelector", let leading = item.data.dictionary("leading"), leading.dictionary("image") == nil, leading.dictionary("fallbackIcon") == nil, !leading.string("fallbackText").isEmpty {
|
|
1620
|
+
fallbackLabel.font = nativeListFont(ofSize: 19, weight: .semibold)
|
|
1621
|
+
fallbackLabel.textColor = nativeListColor(theme, "inverseText", "#FCFCFC")
|
|
1622
|
+
setLineHeight(fallbackLabel, text: leading.string("fallbackText"), lineHeight: 27)
|
|
1623
|
+
}
|
|
1347
1624
|
rootStack.addArrangedSubview(mainStack)
|
|
1348
1625
|
show(titleLabel, item.data.string("title"), lines: item.data.int("titleLines", default: 1))
|
|
1349
1626
|
show(subtitleLabel, item.data.string("subtitle"), lines: item.data.int("subtitleLines", default: 1))
|
|
@@ -1353,6 +1630,77 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1353
1630
|
setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 24)
|
|
1354
1631
|
setLineHeight(subtitleLabel, text: item.data.string("subtitle"), lineHeight: 20)
|
|
1355
1632
|
}
|
|
1633
|
+
// OneKey patch: preserve independent balance/address truncation and warning tones.
|
|
1634
|
+
let segments = item.data.dictionaries("subtitleSegments")
|
|
1635
|
+
if !segments.isEmpty {
|
|
1636
|
+
subtitleLabel.isHidden = true
|
|
1637
|
+
mainStack.spacing = 0
|
|
1638
|
+
setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 24)
|
|
1639
|
+
let line = UIStackView()
|
|
1640
|
+
line.axis = .horizontal
|
|
1641
|
+
line.alignment = .center
|
|
1642
|
+
line.spacing = 0
|
|
1643
|
+
for segment in segments {
|
|
1644
|
+
if segment.bool("separatorBefore") {
|
|
1645
|
+
let gap = UIView()
|
|
1646
|
+
gap.translatesAutoresizingMaskIntoConstraints = false
|
|
1647
|
+
let dot = UIView()
|
|
1648
|
+
dot.translatesAutoresizingMaskIntoConstraints = false
|
|
1649
|
+
dot.backgroundColor = nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
1650
|
+
dot.layer.cornerRadius = 2
|
|
1651
|
+
gap.addSubview(dot)
|
|
1652
|
+
NSLayoutConstraint.activate([
|
|
1653
|
+
gap.widthAnchor.constraint(equalToConstant: 16),
|
|
1654
|
+
gap.heightAnchor.constraint(equalToConstant: 20),
|
|
1655
|
+
dot.widthAnchor.constraint(equalToConstant: 4),
|
|
1656
|
+
dot.heightAnchor.constraint(equalToConstant: 4),
|
|
1657
|
+
dot.centerXAnchor.constraint(equalTo: gap.centerXAnchor),
|
|
1658
|
+
dot.centerYAnchor.constraint(equalTo: gap.centerYAnchor),
|
|
1659
|
+
])
|
|
1660
|
+
line.addArrangedSubview(gap)
|
|
1661
|
+
}
|
|
1662
|
+
let label = UILabel()
|
|
1663
|
+
label.font = nativeListFont(ofSize: 14)
|
|
1664
|
+
label.lineBreakMode = .byTruncatingTail
|
|
1665
|
+
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
1666
|
+
label.textColor = dataTextColor(segment.string("tone", default: "secondary"), theme: theme)
|
|
1667
|
+
setLineHeight(label, text: segment.string("text"), lineHeight: 20)
|
|
1668
|
+
let runs = segment.dictionaries("textSegments")
|
|
1669
|
+
if !runs.isEmpty {
|
|
1670
|
+
let value = NSMutableAttributedString(string: "")
|
|
1671
|
+
let paragraph = NSMutableParagraphStyle()
|
|
1672
|
+
paragraph.minimumLineHeight = 20
|
|
1673
|
+
paragraph.maximumLineHeight = 20
|
|
1674
|
+
for run in runs {
|
|
1675
|
+
value.append(NSAttributedString(string: run.string("text"), attributes: [
|
|
1676
|
+
.font: nativeListFont(ofSize: run.string("style") == "subscript" ? 9 : 14),
|
|
1677
|
+
.foregroundColor: label.textColor as Any,
|
|
1678
|
+
.paragraphStyle: paragraph,
|
|
1679
|
+
.baselineOffset: max(0, (20 - nativeListFont(ofSize: 14).lineHeight) / 2),
|
|
1680
|
+
]))
|
|
1681
|
+
}
|
|
1682
|
+
label.attributedText = value
|
|
1683
|
+
}
|
|
1684
|
+
line.addArrangedSubview(label)
|
|
1685
|
+
}
|
|
1686
|
+
let filler = UIView()
|
|
1687
|
+
filler.setContentHuggingPriority(UILayoutPriority(1), for: .horizontal)
|
|
1688
|
+
line.addArrangedSubview(filler)
|
|
1689
|
+
mainStack.insertArrangedSubview(line, at: 2)
|
|
1690
|
+
selectorViews.append(line)
|
|
1691
|
+
}
|
|
1692
|
+
let matches = item.data.dictionaries("titleMatch")
|
|
1693
|
+
if !matches.isEmpty {
|
|
1694
|
+
let text = NSMutableAttributedString(attributedString: titleLabel.attributedText ?? NSAttributedString(string: item.data.string("title")))
|
|
1695
|
+
for match in matches {
|
|
1696
|
+
let start = match.int("start")
|
|
1697
|
+
let end = match.int("end")
|
|
1698
|
+
if start >= 0 && end > start && end <= text.length {
|
|
1699
|
+
text.addAttribute(.foregroundColor, value: nativeListColor(theme, "info", "#0D74CE"), range: NSRange(location: start, length: end - start))
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
titleLabel.attributedText = text
|
|
1703
|
+
}
|
|
1356
1704
|
tertiaryLabel.textColor = nativeListColor(
|
|
1357
1705
|
theme,
|
|
1358
1706
|
item.data.string("tertiaryTone") == "info" ? "info" : "secondaryText",
|
|
@@ -1997,15 +2345,29 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1997
2345
|
) {
|
|
1998
2346
|
rootStack.addArrangedSubview(mainStack)
|
|
1999
2347
|
let variant = item.data.string("variant")
|
|
2348
|
+
// OneKey patch: title help is a separate target from checkbox and value actions.
|
|
2349
|
+
if !item.data.string("titleActionKey").isEmpty {
|
|
2350
|
+
titleLabel.isUserInteractionEnabled = true
|
|
2351
|
+
titleLabel.addGestureRecognizer(selectorTitleTap)
|
|
2352
|
+
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
2353
|
+
}
|
|
2000
2354
|
let isSummary = variant == "summary"
|
|
2001
2355
|
let isGallery = variant == "gallery"
|
|
2002
2356
|
let isTable = layout == "table"
|
|
2003
2357
|
let isNetworkSelector = item.data.string("presentation") == "networkSelector"
|
|
2358
|
+
let isExplicitNetworkHeader = isNetworkSelector && item.data["height"] != nil
|
|
2359
|
+
if isExplicitNetworkHeader {
|
|
2360
|
+
// OneKey patch: the flexible title consumes spare space before trailing totals.
|
|
2361
|
+
trailingStack.setContentHuggingPriority(.required, for: .horizontal)
|
|
2362
|
+
trailingStack.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
2363
|
+
}
|
|
2364
|
+
titleLabel.reservesDottedUnderlineSpace = isExplicitNetworkHeader && !item.data.string("titleActionKey").isEmpty
|
|
2004
2365
|
let isHistory = variant == "history" ||
|
|
2005
2366
|
item.key.hasPrefix("history-") ||
|
|
2006
2367
|
(item.sectionKey?.hasPrefix("history-") ?? false)
|
|
2007
2368
|
let headerWeight: NativeListFontWeight = isSummary
|
|
2008
2369
|
? .medium
|
|
2370
|
+
: isExplicitNetworkHeader && (item.data.dictionary("checkbox") != nil || item.data.string("titleActionKey").isEmpty) ? .semibold
|
|
2009
2371
|
: isNetworkSelector ? .medium
|
|
2010
2372
|
: isGallery || layout == "sectioned" ? .semibold : .regular
|
|
2011
2373
|
titleLabel.font = nativeListFont(
|
|
@@ -2019,8 +2381,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2019
2381
|
)
|
|
2020
2382
|
show(titleLabel, item.data.string("title"), lines: 1)
|
|
2021
2383
|
if isNetworkSelector {
|
|
2022
|
-
titleLabel.showsDottedUnderline =
|
|
2023
|
-
titleLabel.dottedUnderlineVerticalOffset = 2
|
|
2384
|
+
titleLabel.showsDottedUnderline = !isExplicitNetworkHeader || !item.data.string("titleActionKey").isEmpty
|
|
2385
|
+
titleLabel.dottedUnderlineVerticalOffset = isExplicitNetworkHeader ? 1 : 2
|
|
2024
2386
|
titleLabel.dottedUnderlineColor = nativeListColor(
|
|
2025
2387
|
theme,
|
|
2026
2388
|
"secondaryText",
|
|
@@ -2028,8 +2390,9 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2028
2390
|
)
|
|
2029
2391
|
rootLeadingConstraint.constant = 12
|
|
2030
2392
|
rootTrailingConstraint.constant = -12
|
|
2031
|
-
|
|
2032
|
-
|
|
2393
|
+
let isAlphabet = isExplicitNetworkHeader && item.data.string("titleActionKey").isEmpty
|
|
2394
|
+
rootTopConstraint.constant = isAlphabet ? 8 : 12
|
|
2395
|
+
rootBottomConstraint.constant = isAlphabet ? -8 : isExplicitNetworkHeader ? -12 : -15
|
|
2033
2396
|
setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 20)
|
|
2034
2397
|
} else if isHistory {
|
|
2035
2398
|
rootLeadingConstraint.constant = 0
|
|
@@ -2095,6 +2458,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2095
2458
|
rootTopConstraint.constant = 24
|
|
2096
2459
|
rootBottomConstraint.constant = -20
|
|
2097
2460
|
setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 24)
|
|
2461
|
+
accessoryButtons[0].accessibilityIdentifier = item.data["valueActionTestID"] as? String
|
|
2098
2462
|
let valueActionKey = item.data.string("valueActionKey")
|
|
2099
2463
|
let action: (String, NativeSelectionTarget?)? = valueActionKey.isEmpty
|
|
2100
2464
|
? nil
|
|
@@ -2105,11 +2469,11 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2105
2469
|
action: action,
|
|
2106
2470
|
color: nativeListColor(theme, "secondaryText", "#646464")
|
|
2107
2471
|
)
|
|
2108
|
-
accessoryButtons[0].titleLabel?.font = nativeListFont(ofSize: 16)
|
|
2472
|
+
accessoryButtons[0].titleLabel?.font = nativeListFont(ofSize: 16, weight: isExplicitNetworkHeader ? .medium : .regular)
|
|
2109
2473
|
setButtonLine(
|
|
2110
2474
|
accessoryButtons[0],
|
|
2111
2475
|
text: item.data.string("value"),
|
|
2112
|
-
font: nativeListFont(ofSize: 16),
|
|
2476
|
+
font: nativeListFont(ofSize: 16, weight: isExplicitNetworkHeader ? .medium : .regular),
|
|
2113
2477
|
color: nativeListColor(theme, "secondaryText", "#646464"),
|
|
2114
2478
|
lineHeight: 24
|
|
2115
2479
|
)
|
|
@@ -2169,6 +2533,28 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2169
2533
|
)
|
|
2170
2534
|
}
|
|
2171
2535
|
}
|
|
2536
|
+
applyValueSegments(item.data.dictionaries("valueSegments"), to: accessoryButtons[0], theme: theme)
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// OneKey patch: small zero-count digits remain on the regular amount baseline.
|
|
2540
|
+
private func applyValueSegments(_ segments: [[String: Any]], to button: UIButton, theme: [String: Any]?) {
|
|
2541
|
+
guard !segments.isEmpty else { return }
|
|
2542
|
+
let value = NSMutableAttributedString(string: "")
|
|
2543
|
+
let color = nativeListColor(theme, "primaryText", "#202020")
|
|
2544
|
+
// OneKey patch: rich currency changes font runs without dropping the established line baseline.
|
|
2545
|
+
let current = button.attributedTitle(for: .normal)
|
|
2546
|
+
var attributes = current.flatMap { $0.length > 0 ? $0.attributes(at: 0, effectiveRange: nil) : nil } ?? [:]
|
|
2547
|
+
attributes[.foregroundColor] = color
|
|
2548
|
+
if currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil {
|
|
2549
|
+
let paragraph = (attributes[.paragraphStyle] as? NSParagraphStyle)?.mutableCopy() as? NSMutableParagraphStyle ?? NSMutableParagraphStyle()
|
|
2550
|
+
paragraph.alignment = .right
|
|
2551
|
+
attributes[.paragraphStyle] = paragraph
|
|
2552
|
+
}
|
|
2553
|
+
for segment in segments {
|
|
2554
|
+
attributes[.font] = nativeListTabularFont(ofSize: segment.string("style") == "subscript" ? 10 : 16, weight: .medium)
|
|
2555
|
+
value.append(NSAttributedString(string: segment.string("text"), attributes: attributes))
|
|
2556
|
+
}
|
|
2557
|
+
button.setAttributedTitle(value, for: .normal)
|
|
2172
2558
|
}
|
|
2173
2559
|
|
|
2174
2560
|
private func bindAction(
|
|
@@ -2185,6 +2571,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2185
2571
|
addLeading(icon, key: item.key)
|
|
2186
2572
|
if isAccountSelector {
|
|
2187
2573
|
leadingContainer.layer.cornerCurve = .continuous
|
|
2574
|
+
leadingContainer.layer.cornerRadius = 8
|
|
2575
|
+
leadingContainer.layer.borderWidth = 0
|
|
2188
2576
|
}
|
|
2189
2577
|
if icon["backgroundColor"] == nil {
|
|
2190
2578
|
leadingContainer.backgroundColor = .clear
|
|
@@ -2195,8 +2583,9 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2195
2583
|
rootStack.addArrangedSubview(mainStack)
|
|
2196
2584
|
show(titleLabel, item.data.string("title"), lines: 1)
|
|
2197
2585
|
if isAccountSelector {
|
|
2198
|
-
|
|
2199
|
-
titleLabel.
|
|
2586
|
+
// OneKey patch: ListItem.Text is medium; empty-search actions use regular body text.
|
|
2587
|
+
titleLabel.font = nativeListFont(ofSize: 16, weight: item.data.dictionary("icon") == nil ? .regular : .medium)
|
|
2588
|
+
titleLabel.textColor = nativeListColor(theme, item.data.string("tone") == "primary" ? "primaryText" : "secondaryText", item.data.string("tone") == "primary" ? "#202020" : "#646464")
|
|
2200
2589
|
} else if item.data.string("tone") == "danger" {
|
|
2201
2590
|
titleLabel.textColor = nativeListColor(theme, "negative", "#CE2C31")
|
|
2202
2591
|
}
|
|
@@ -2217,8 +2606,19 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2217
2606
|
}
|
|
2218
2607
|
}
|
|
2219
2608
|
|
|
2609
|
+
// OneKey patch: preserve the actual title frame for Popover placement.
|
|
2610
|
+
@objc private func selectorTitlePressed() {
|
|
2611
|
+
guard let item = currentItem else { return }
|
|
2612
|
+
let action = item.data.string("titleActionKey")
|
|
2613
|
+
guard !action.isEmpty else { return }
|
|
2614
|
+
onAction?(item, action, nil, actionOrigin(sourceView: titleLabel, source: "leadingAction"))
|
|
2615
|
+
}
|
|
2616
|
+
|
|
2220
2617
|
private func dataTextColor(_ tone: String, theme: [String: Any]?) -> UIColor {
|
|
2221
2618
|
switch tone.isEmpty ? "primary" : tone {
|
|
2619
|
+
// OneKey patch: account warnings and hidden balances use existing theme tokens.
|
|
2620
|
+
case "disabled": return nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
2621
|
+
case "caution": return nativeListColor(theme, "caution", "#AB6400")
|
|
2222
2622
|
case "secondary": return nativeListColor(theme, "secondaryText", "#646464")
|
|
2223
2623
|
case "positive": return nativeListColor(theme, "positive", "#218358")
|
|
2224
2624
|
case "negative": return nativeListColor(theme, "negative", "#CE2C31")
|
|
@@ -2230,6 +2630,36 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2230
2630
|
rootStack.alignment = .center
|
|
2231
2631
|
rootStack.distribution = .fill
|
|
2232
2632
|
let variant = item.data.string("variant")
|
|
2633
|
+
// OneKey patch: deprecated-wallet warnings stay inside the scrolling list.
|
|
2634
|
+
if variant == "warning" {
|
|
2635
|
+
rootStack.addArrangedSubview(mainStack)
|
|
2636
|
+
rootTopConstraint.constant = 14
|
|
2637
|
+
rootBottomConstraint.constant = -14
|
|
2638
|
+
mainStack.spacing = 4
|
|
2639
|
+
titleLabel.font = nativeListFont(ofSize: 14, weight: .medium)
|
|
2640
|
+
titleLabel.numberOfLines = 0
|
|
2641
|
+
subtitleLabel.font = nativeListFont(ofSize: 14)
|
|
2642
|
+
subtitleLabel.numberOfLines = 0
|
|
2643
|
+
show(titleLabel, item.data.string("title"), lines: 0)
|
|
2644
|
+
show(subtitleLabel, item.data.string("message"), lines: 0)
|
|
2645
|
+
setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 20)
|
|
2646
|
+
setLineHeight(subtitleLabel, text: item.data.string("message"), lineHeight: 20)
|
|
2647
|
+
let borderColor = UIColor(nativeListHex: item.data.string("borderColor", default: "#E0E0E0"), fallback: .lightGray)
|
|
2648
|
+
for top in [true, false] {
|
|
2649
|
+
let border = UIView()
|
|
2650
|
+
border.translatesAutoresizingMaskIntoConstraints = false
|
|
2651
|
+
border.backgroundColor = borderColor
|
|
2652
|
+
contentView.addSubview(border)
|
|
2653
|
+
NSLayoutConstraint.activate([
|
|
2654
|
+
border.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: -8),
|
|
2655
|
+
border.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 8),
|
|
2656
|
+
border.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale),
|
|
2657
|
+
top ? border.topAnchor.constraint(equalTo: contentView.topAnchor) : border.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
|
|
2658
|
+
])
|
|
2659
|
+
selectorViews.append(border)
|
|
2660
|
+
}
|
|
2661
|
+
return
|
|
2662
|
+
}
|
|
2233
2663
|
if variant == "loading" {
|
|
2234
2664
|
leadingWidth.constant = 40
|
|
2235
2665
|
leadingHeight.constant = 40
|
|
@@ -2357,7 +2787,105 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2357
2787
|
tokenPair: tokenPair,
|
|
2358
2788
|
shape: shape
|
|
2359
2789
|
))
|
|
2360
|
-
|
|
2790
|
+
if currentItem?.data.string("presentation") == "networkSelector", currentItem?.data["height"] != nil, visibleSources.count == 1, cornerIcon == nil, visual.dictionaries("overlays").isEmpty {
|
|
2791
|
+
// OneKey patch: a single outer mask matches NetworkAvatar's edge antialiasing.
|
|
2792
|
+
imageView.layer.cornerRadius = 0
|
|
2793
|
+
imageView.clipsToBounds = false
|
|
2794
|
+
}
|
|
2795
|
+
let fallbackIcon = index == 0 ? visual.dictionary("fallbackIcon") : nil
|
|
2796
|
+
let expectedEpoch = bindingEpoch
|
|
2797
|
+
bindImage(source.data, into: imageView, token: key, slot: index, variant: source.variant,
|
|
2798
|
+
onLoad: fallbackIcon == nil ? nil : { [weak self, weak imageView] in
|
|
2799
|
+
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
2800
|
+
imageView?.isHidden = false
|
|
2801
|
+
self.leadingIconImageView.isHidden = true
|
|
2802
|
+
},
|
|
2803
|
+
onError: fallbackIcon == nil ? nil : { [weak self, weak imageView] in
|
|
2804
|
+
guard let self, self.bindingEpoch == expectedEpoch, let fallbackIcon else { return }
|
|
2805
|
+
imageView?.isHidden = true
|
|
2806
|
+
self.fallbackLabel.isHidden = true
|
|
2807
|
+
self.leadingIconImageView.image = nativeListIcon(named: fallbackIcon.string("name"))
|
|
2808
|
+
self.leadingIconImageView.tintColor = UIColor(nativeListHex: fallbackIcon.string("tintColor", default: "#646464"), fallback: .darkGray)
|
|
2809
|
+
self.leadingIconImageView.isHidden = false
|
|
2810
|
+
})
|
|
2811
|
+
}
|
|
2812
|
+
// OneKey patch: source-derived wallet decorations may occupy both corners.
|
|
2813
|
+
let overlays = visual.dictionaries("overlays")
|
|
2814
|
+
if !overlays.isEmpty { leadingContainer.clipsToBounds = false }
|
|
2815
|
+
for (index, overlay) in overlays.enumerated() {
|
|
2816
|
+
let size = CGFloat(overlay.double("size", default: 20))
|
|
2817
|
+
let isWalletText = currentItem?.data.string("presentation") == "walletSidebar" && currentItem?.data["height"] != nil && !overlay.string("text").isEmpty && overlay.dictionary("image") == nil && overlay.string("name").isEmpty
|
|
2818
|
+
let inset = CGFloat(overlay.double("padding", default: 0))
|
|
2819
|
+
let offsetX = CGFloat(overlay.double("offsetX", default: overlay.double("offset", default: 2)))
|
|
2820
|
+
let offsetY = CGFloat(overlay.double("offsetY", default: overlay.double("offset", default: 2)))
|
|
2821
|
+
let height = CGFloat(overlay.double("height", default: isWalletText ? 16 : Double(size)))
|
|
2822
|
+
let textWidth = (overlay.string("text") as NSString).size(withAttributes: [.font: nativeListTabularFont(ofSize: 12), .kern: 0]).width
|
|
2823
|
+
let naturalTextWidth = ceil(textWidth * UIScreen.main.scale) / UIScreen.main.scale + 4
|
|
2824
|
+
let width = CGFloat(overlay.double("width", default: isWalletText ? Double(naturalTextWidth) : Double(size)))
|
|
2825
|
+
let frame = UIView()
|
|
2826
|
+
frame.translatesAutoresizingMaskIntoConstraints = false
|
|
2827
|
+
frame.backgroundColor = UIColor(nativeListHex: overlay.string("backgroundColor", default: "#FFFFFF"), fallback: .clear)
|
|
2828
|
+
frame.layer.cornerRadius = min(width, height) / 2
|
|
2829
|
+
frame.clipsToBounds = true
|
|
2830
|
+
leadingContainer.addSubview(frame)
|
|
2831
|
+
selectorViews.append(frame)
|
|
2832
|
+
let topLeft = overlay.string("position") == "topLeft"
|
|
2833
|
+
leadingSlotConstraints.append(contentsOf: [
|
|
2834
|
+
frame.widthAnchor.constraint(equalToConstant: width),
|
|
2835
|
+
frame.heightAnchor.constraint(equalToConstant: height),
|
|
2836
|
+
topLeft ? frame.leadingAnchor.constraint(equalTo: leadingContainer.leadingAnchor, constant: -offsetX) : frame.trailingAnchor.constraint(equalTo: leadingContainer.trailingAnchor, constant: offsetX),
|
|
2837
|
+
topLeft ? frame.topAnchor.constraint(equalTo: leadingContainer.topAnchor, constant: -offsetY) : frame.bottomAnchor.constraint(equalTo: leadingContainer.bottomAnchor, constant: offsetY),
|
|
2838
|
+
])
|
|
2839
|
+
let content: UIView
|
|
2840
|
+
if let image = overlay.dictionary("image") {
|
|
2841
|
+
let imageView = OneKeyImageReusableView(frame: .zero)
|
|
2842
|
+
bindImage(image, into: imageView, token: key, slot: 10 + index, variant: "generic")
|
|
2843
|
+
selectorImages.append(imageView)
|
|
2844
|
+
content = imageView
|
|
2845
|
+
} else if !overlay.string("text").isEmpty {
|
|
2846
|
+
let label = UILabel()
|
|
2847
|
+
label.text = overlay.string("text")
|
|
2848
|
+
label.textAlignment = .center
|
|
2849
|
+
label.font = nativeListFont(ofSize: isWalletText ? 12 : 10, weight: isWalletText ? .regular : .medium)
|
|
2850
|
+
label.textColor = UIColor(nativeListHex: overlay.string("tintColor", default: "#646464"), fallback: .darkGray)
|
|
2851
|
+
if isWalletText { setLineHeight(label, text: overlay.string("text"), lineHeight: 16) }
|
|
2852
|
+
content = label
|
|
2853
|
+
} else {
|
|
2854
|
+
let imageView = UIImageView(image: nativeListIcon(named: overlay.string("name")))
|
|
2855
|
+
imageView.contentMode = .scaleAspectFit
|
|
2856
|
+
imageView.tintColor = UIColor(nativeListHex: overlay.string("tintColor", default: "#646464"), fallback: .darkGray)
|
|
2857
|
+
content = imageView
|
|
2858
|
+
}
|
|
2859
|
+
content.translatesAutoresizingMaskIntoConstraints = false
|
|
2860
|
+
frame.addSubview(content)
|
|
2861
|
+
NSLayoutConstraint.activate([
|
|
2862
|
+
content.leadingAnchor.constraint(equalTo: frame.leadingAnchor, constant: isWalletText ? 2 : inset),
|
|
2863
|
+
content.trailingAnchor.constraint(equalTo: frame.trailingAnchor, constant: isWalletText ? -2 : -inset),
|
|
2864
|
+
content.topAnchor.constraint(equalTo: frame.topAnchor, constant: isWalletText ? 0 : inset),
|
|
2865
|
+
content.bottomAnchor.constraint(equalTo: frame.bottomAnchor, constant: isWalletText ? 0 : -inset),
|
|
2866
|
+
])
|
|
2867
|
+
}
|
|
2868
|
+
if let fallbackIcon = visual.dictionary("fallbackIcon"), sources.isEmpty {
|
|
2869
|
+
fallbackLabel.isHidden = true
|
|
2870
|
+
leadingIconImageView.isHidden = false
|
|
2871
|
+
leadingIconImageView.image = nativeListIcon(named: fallbackIcon.string("name"))
|
|
2872
|
+
leadingIconImageView.tintColor = UIColor(nativeListHex: fallbackIcon.string("tintColor", default: "#646464"), fallback: .darkGray)
|
|
2873
|
+
if currentItem?.data.string("presentation") == "walletSidebar", currentItem?.data["height"] != nil, fallbackIcon.string("name") == "LockSolid" {
|
|
2874
|
+
leadingIconWidth.constant = 40
|
|
2875
|
+
leadingIconHeight.constant = 40
|
|
2876
|
+
leadingContainer.clipsToBounds = false
|
|
2877
|
+
}
|
|
2878
|
+
}
|
|
2879
|
+
if visual.string("borderStyle") == "dashed" {
|
|
2880
|
+
let border = CAShapeLayer()
|
|
2881
|
+
border.strokeColor = UIColor(nativeListHex: visual.string("borderColor", default: "#8D8D8D"), fallback: .gray).cgColor
|
|
2882
|
+
border.fillColor = UIColor.clear.cgColor
|
|
2883
|
+
border.lineWidth = currentItem?.data.string("presentation") == "walletSidebar" && currentItem?.data["height"] != nil ? 1 : 2
|
|
2884
|
+
border.lineDashPattern = [4, 4]
|
|
2885
|
+
let borderInset = border.lineWidth / 2
|
|
2886
|
+
border.path = UIBezierPath(ovalIn: CGRect(x: borderInset, y: borderInset, width: leadingWidth.constant - border.lineWidth, height: leadingHeight.constant - border.lineWidth)).cgPath
|
|
2887
|
+
leadingContainer.layer.addSublayer(border)
|
|
2888
|
+
selectorBorder = border
|
|
2361
2889
|
}
|
|
2362
2890
|
NSLayoutConstraint.activate(leadingSlotConstraints)
|
|
2363
2891
|
}
|
|
@@ -2439,6 +2967,11 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2439
2967
|
switch accessory.string("kind") {
|
|
2440
2968
|
case "value":
|
|
2441
2969
|
showAccessory(textIndex, accessory.string("text"))
|
|
2970
|
+
if item.data.string("presentation") == "networkSelector" && item.data["height"] != nil {
|
|
2971
|
+
accessoryButtons[textIndex].contentHorizontalAlignment = .trailing
|
|
2972
|
+
accessoryButtons[textIndex].titleLabel?.textAlignment = .right
|
|
2973
|
+
}
|
|
2974
|
+
applyValueSegments(accessory.dictionaries("textSegments"), to: accessoryButtons[textIndex], theme: theme)
|
|
2442
2975
|
textIndex += 1
|
|
2443
2976
|
case "valuePair":
|
|
2444
2977
|
showValuePairAccessory(textIndex, accessory, theme: theme)
|
|
@@ -2519,7 +3052,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2519
3052
|
state == "unchecked" ? nil : nativeListIcon(named: glyphName),
|
|
2520
3053
|
for: .normal
|
|
2521
3054
|
)
|
|
2522
|
-
checkboxButton.tintColor =
|
|
3055
|
+
checkboxButton.tintColor = checkboxIconColor
|
|
2523
3056
|
// A disabled ListItem already applies 0.5 to its complete content. Avoid
|
|
2524
3057
|
// multiplying that opacity on the nested control a second time.
|
|
2525
3058
|
checkboxButton.alpha = item.data.bool("disabled") ? 1 : accessoryDisabled ? 0.5 : 1
|
|
@@ -2543,11 +3076,24 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2543
3076
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
2544
3077
|
paragraphStyle.minimumLineHeight = lineHeight
|
|
2545
3078
|
paragraphStyle.maximumLineHeight = lineHeight
|
|
3079
|
+
if currentItem?.data.string("presentation") == "walletSidebar" {
|
|
3080
|
+
// OneKey patch: attributed paragraphs must preserve wallet name alignment and tail ellipsis.
|
|
3081
|
+
paragraphStyle.alignment = label.textAlignment
|
|
3082
|
+
paragraphStyle.lineBreakMode = label.lineBreakMode
|
|
3083
|
+
}
|
|
2546
3084
|
var attributes: [NSAttributedString.Key: Any] = [
|
|
2547
3085
|
.font: label.font as Any,
|
|
2548
3086
|
.foregroundColor: label.textColor as Any,
|
|
2549
3087
|
.paragraphStyle: paragraphStyle,
|
|
2550
3088
|
]
|
|
3089
|
+
if (currentItem?.data["height"] != nil && (["accountSelector", "walletSidebar"].contains(currentItem?.data.string("presentation") ?? "") || currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector")) || currentItem?.type == "system" && currentItem?.data.string("variant") == "warning" {
|
|
3090
|
+
// OneKey patch: React Native centers font metrics inside explicit line heights.
|
|
3091
|
+
let baselineOffset = max(0, (lineHeight - label.font.lineHeight) / 2)
|
|
3092
|
+
// OneKey patch: TextKit's 14/20 headings align their baseline to the upper physical pixel.
|
|
3093
|
+
let isSelectorHeading = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && lineHeight == 20
|
|
3094
|
+
let scale = window?.screen.scale ?? traitCollection.displayScale
|
|
3095
|
+
attributes[.baselineOffset] = isSelectorHeading && scale > 0 ? ceil(baselineOffset * scale) / scale : baselineOffset
|
|
3096
|
+
}
|
|
2551
3097
|
if letterSpacing != 0 { attributes[.kern] = letterSpacing }
|
|
2552
3098
|
label.attributedText = NSAttributedString(string: text, attributes: attributes)
|
|
2553
3099
|
}
|
|
@@ -2563,7 +3109,20 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2563
3109
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
2564
3110
|
paragraphStyle.minimumLineHeight = lineHeight
|
|
2565
3111
|
paragraphStyle.maximumLineHeight = lineHeight
|
|
2566
|
-
|
|
3112
|
+
let isSelectorValue = currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") != "summary"
|
|
3113
|
+
let isSelectorSummary = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") == "summary"
|
|
3114
|
+
(button as? NativeListAccessoryButton)?.selectorSummaryLineHeight = isSelectorSummary ? lineHeight : nil
|
|
3115
|
+
// OneKey patch: summary text uses its source line box; currency retains trailing alignment.
|
|
3116
|
+
paragraphStyle.alignment = isSelectorValue ? .right : isSelectorSummary ? .natural : .center
|
|
3117
|
+
if isSelectorSummary {
|
|
3118
|
+
button.contentHorizontalAlignment = .leading
|
|
3119
|
+
button.titleLabel?.textAlignment = .natural
|
|
3120
|
+
}
|
|
3121
|
+
if isSelectorValue {
|
|
3122
|
+
button.contentHorizontalAlignment = .trailing
|
|
3123
|
+
button.titleLabel?.textAlignment = .right
|
|
3124
|
+
}
|
|
3125
|
+
let baselineOffset: CGFloat = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil ? max(0, (lineHeight - font.lineHeight) / 2) : 0
|
|
2567
3126
|
button.setAttributedTitle(
|
|
2568
3127
|
NSAttributedString(
|
|
2569
3128
|
string: text,
|
|
@@ -2571,6 +3130,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2571
3130
|
.font: font,
|
|
2572
3131
|
.foregroundColor: color,
|
|
2573
3132
|
.paragraphStyle: paragraphStyle,
|
|
3133
|
+
.baselineOffset: baselineOffset,
|
|
2574
3134
|
]
|
|
2575
3135
|
),
|
|
2576
3136
|
for: .normal
|
|
@@ -2671,6 +3231,9 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2671
3231
|
switch tone.isEmpty ? defaultTone : tone {
|
|
2672
3232
|
case "positive": return nativeListColor(theme, "positive", "#218358")
|
|
2673
3233
|
case "negative": return nativeListColor(theme, "negative", "#CE2C31")
|
|
3234
|
+
// OneKey patch: account warnings and hidden balances use existing theme tokens.
|
|
3235
|
+
case "disabled": return nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
3236
|
+
case "caution": return nativeListColor(theme, "caution", "#AB6400")
|
|
2674
3237
|
case "secondary": return nativeListColor(theme, "secondaryText", "#646464")
|
|
2675
3238
|
default: return nativeListColor(theme, "primaryText", "#202020")
|
|
2676
3239
|
}
|
|
@@ -2682,10 +3245,10 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2682
3245
|
button.isHidden = false
|
|
2683
3246
|
button.isEnabled = !data.bool("disabled")
|
|
2684
3247
|
button.alpha = button.isEnabled ? 1 : 0.4
|
|
2685
|
-
let
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
3248
|
+
let isAccountCreate = currentItem?.data.string("presentation") == "accountSelector" && currentItem?.data["height"] != nil && data.string("name") == "PlusSmallOutline"
|
|
3249
|
+
let tintColor = data["tintColor"] == nil && isAccountCreate
|
|
3250
|
+
? nativeListColor(currentTheme, "iconSubdued", "#8D8D8D")
|
|
3251
|
+
: UIColor(nativeListHex: data.string("tintColor", default: "#646464"), fallback: .darkGray)
|
|
2689
3252
|
button.tintColor = tintColor
|
|
2690
3253
|
if let image = nativeListIcon(named: data.string("name")) {
|
|
2691
3254
|
button.setImage(image, for: .normal)
|
|
@@ -2695,7 +3258,12 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2695
3258
|
)
|
|
2696
3259
|
}
|
|
2697
3260
|
let isDrillIn = data.string("kind") == "chevron"
|
|
2698
|
-
let
|
|
3261
|
+
let isAccountIcon = currentItem?.data.string("presentation") == "accountSelector" && !isDrillIn
|
|
3262
|
+
let size: CGFloat = isDrillIn ? 24 : isAccountIcon && !isAccountCreate ? 38 : 36
|
|
3263
|
+
if isAccountCreate { button.layer.cornerRadius = 8 }
|
|
3264
|
+
if isAccountIcon { rootStack.setCustomSpacing(5, after: mainStack) }
|
|
3265
|
+
button.accessibilityIdentifier = data["testID"] as? String
|
|
3266
|
+
button.accessibilityLabel = data["accessibilityLabel"] as? String
|
|
2699
3267
|
if !isDrillIn, !data.string("actionKey").isEmpty {
|
|
2700
3268
|
// Reproduce the trailing edge of IconButton's m=-7 while keeping its
|
|
2701
3269
|
// full 36-point frame for padding/highlight behavior.
|
|
@@ -2726,8 +3294,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2726
3294
|
into imageView: OneKeyImageReusableView,
|
|
2727
3295
|
token: String,
|
|
2728
3296
|
slot: Int,
|
|
2729
|
-
variant: String
|
|
3297
|
+
variant: String,
|
|
3298
|
+
onLoad: (() -> Void)? = nil,
|
|
3299
|
+
onError: (() -> Void)? = nil,
|
|
3300
|
+
retryAttempt: Int = 0
|
|
2730
3301
|
) {
|
|
3302
|
+
let imageID = ObjectIdentifier(imageView)
|
|
3303
|
+
selectorImageRetries.removeValue(forKey: imageID)?.cancel()
|
|
3304
|
+
let expectedEpoch = bindingEpoch
|
|
3305
|
+
let retryLimit = max(0, source.int("retryTimes", default: 0))
|
|
2731
3306
|
let headersJson: String?
|
|
2732
3307
|
if let headers = source.dictionary("headers"),
|
|
2733
3308
|
JSONSerialization.isValidJSONObject(headers),
|
|
@@ -2743,10 +3318,27 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2743
3318
|
contentFit: source.string("contentFit", default: "cover"),
|
|
2744
3319
|
cachePolicy: source.string("cachePolicy", default: "memory-disk"),
|
|
2745
3320
|
autoplay: source.bool("autoplay"),
|
|
2746
|
-
recyclingKey: "\(token):\(slot)",
|
|
2747
|
-
optimizeTos: source["optimizeTos"] == nil || source.bool("optimizeTos"),
|
|
3321
|
+
recyclingKey: retryAttempt == 0 ? "\(token):\(slot)" : "\(token):\(slot):retry:\(retryAttempt)",
|
|
3322
|
+
optimizeTos: retryAttempt == 0 && (source["optimizeTos"] == nil || source.bool("optimizeTos")),
|
|
2748
3323
|
overscan: source["overscan"] == nil ? 1.1 : source.double("overscan"),
|
|
2749
|
-
loadingStrategy: source.string("loadingStrategy", default: "static")
|
|
3324
|
+
loadingStrategy: source.string("loadingStrategy", default: "static"),
|
|
3325
|
+
onLoad: retryLimit == 0 ? onLoad : { [weak self] in
|
|
3326
|
+
guard let self, self.bindingEpoch == expectedEpoch else { return }
|
|
3327
|
+
self.selectorImageRetries.removeValue(forKey: imageID)?.cancel()
|
|
3328
|
+
onLoad?()
|
|
3329
|
+
},
|
|
3330
|
+
onError: retryLimit == 0 ? onError : { [weak self, weak imageView] in
|
|
3331
|
+
guard let self, self.bindingEpoch == expectedEpoch, let imageView else { return }
|
|
3332
|
+
guard retryAttempt < retryLimit else { onError?(); return }
|
|
3333
|
+
guard self.selectorImageRetries[imageID] == nil else { return }
|
|
3334
|
+
let retry = DispatchWorkItem { [weak self, weak imageView] in
|
|
3335
|
+
guard let self, self.bindingEpoch == expectedEpoch, let imageView else { return }
|
|
3336
|
+
self.selectorImageRetries.removeValue(forKey: imageID)
|
|
3337
|
+
self.bindImage(source, into: imageView, token: token, slot: slot, variant: variant, onLoad: onLoad, onError: onError, retryAttempt: retryAttempt + 1)
|
|
3338
|
+
}
|
|
3339
|
+
self.selectorImageRetries[imageID] = retry
|
|
3340
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + Double(Int.random(in: 0...2)), execute: retry)
|
|
3341
|
+
}
|
|
2750
3342
|
)
|
|
2751
3343
|
}
|
|
2752
3344
|
|
|
@@ -2837,12 +3429,17 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2837
3429
|
source: String,
|
|
2838
3430
|
slot: Int? = nil
|
|
2839
3431
|
) -> NativeListActionOrigin {
|
|
2840
|
-
|
|
3432
|
+
let isAccountIcon = currentItem?.data.string("presentation") == "accountSelector"
|
|
3433
|
+
&& source == "trailingAccessory"
|
|
3434
|
+
&& accessoryButtons.contains { $0 === sourceView }
|
|
3435
|
+
&& sourceView.bounds.width == 38
|
|
3436
|
+
return NativeListActionOrigin(
|
|
2841
3437
|
sourceView: sourceView,
|
|
2842
3438
|
ownerCell: self,
|
|
2843
3439
|
bindingEpoch: bindingEpoch,
|
|
2844
3440
|
source: source,
|
|
2845
|
-
slot: slot
|
|
3441
|
+
slot: slot,
|
|
3442
|
+
anchorInset: isAccountIcon ? 7 : 0
|
|
2846
3443
|
)
|
|
2847
3444
|
}
|
|
2848
3445
|
|
|
@@ -2851,6 +3448,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2851
3448
|
}
|
|
2852
3449
|
|
|
2853
3450
|
private func invalidateCurrentBinding() {
|
|
3451
|
+
selectorImageRetries.values.forEach { $0.cancel() }
|
|
3452
|
+
selectorImageRetries.removeAll()
|
|
2854
3453
|
guard currentItem != nil else { return }
|
|
2855
3454
|
onBindingInvalidated?(self, bindingEpoch)
|
|
2856
3455
|
bindingEpoch &+= 1
|