@onekeyfe/react-native-native-list 3.0.157-alpha.248 → 3.0.157-alpha.250

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.
Files changed (130) hide show
  1. package/README.md +21 -5
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
  5. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
  6. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
  12. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
  16. package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
  18. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
  19. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
  20. package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
  21. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
  22. package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
  23. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
  24. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
  25. package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
  26. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
  27. package/docs/DESIGN.md +518 -9
  28. package/docs/MIGRATION_BASELINE.md +117 -0
  29. package/docs/ROW_TEMPLATES.md +359 -0
  30. package/docs/SPEC.md +358 -0
  31. package/docs/STYLE_SPEC.md +978 -0
  32. package/docs/images/list-capabilities.svg +39 -0
  33. package/docs/images/row-action.svg +17 -0
  34. package/docs/images/row-activity.svg +24 -0
  35. package/docs/images/row-data.svg +24 -0
  36. package/docs/images/row-identity.svg +25 -0
  37. package/docs/images/row-market.svg +23 -0
  38. package/docs/images/row-media-tile.svg +26 -0
  39. package/docs/images/row-message.svg +20 -0
  40. package/docs/images/row-metric-card.svg +33 -0
  41. package/docs/images/row-rail.svg +18 -0
  42. package/docs/images/row-section-header.svg +19 -0
  43. package/docs/images/row-style-regions.svg +30 -0
  44. package/docs/images/row-system.svg +30 -0
  45. package/docs/images/row-wallet-group.svg +26 -0
  46. package/ios/NativeListAccessoryStack.swift +680 -0
  47. package/ios/NativeListActionRenderer.swift +108 -0
  48. package/ios/NativeListActivityRenderer.swift +179 -0
  49. package/ios/NativeListDataRowRenderer.swift +444 -0
  50. package/ios/NativeListIdentityRenderer.swift +685 -0
  51. package/ios/NativeListImageSlot.swift +140 -0
  52. package/ios/NativeListLeadingVisual.swift +372 -0
  53. package/ios/NativeListMarketRenderer.swift +698 -0
  54. package/ios/NativeListMediaTileRenderer.swift +184 -0
  55. package/ios/NativeListMessageRenderer.swift +168 -0
  56. package/ios/NativeListMetricCardRenderer.swift +420 -0
  57. package/ios/NativeListModels.swift +32 -0
  58. package/ios/NativeListRailRenderer.swift +152 -0
  59. package/ios/NativeListRendererCell.swift +311 -0
  60. package/ios/NativeListRendererRegistry.swift +50 -0
  61. package/ios/NativeListResolvedText.swift +122 -0
  62. package/ios/NativeListRowHost.swift +63 -0
  63. package/ios/NativeListSectionHeaderRenderer.swift +708 -0
  64. package/ios/NativeListSystemRenderer.swift +568 -0
  65. package/ios/NativeListTextLabel.swift +42 -0
  66. package/ios/NativeListTextStyles.swift +236 -0
  67. package/ios/NativeListWalletGroupRenderer.swift +255 -0
  68. package/ios/RNCNativeListView.swift +111 -180
  69. package/lib/module/models.js +54 -0
  70. package/lib/module/validation.js +324 -26
  71. package/lib/module/web/NativeListWebEngine.js +148 -936
  72. package/lib/module/web/templates/ActionRowRenderer.js +97 -0
  73. package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
  74. package/lib/module/web/templates/DataRowRenderer.js +145 -0
  75. package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
  76. package/lib/module/web/templates/MarketRowRenderer.js +310 -0
  77. package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
  78. package/lib/module/web/templates/MessageRowRenderer.js +165 -0
  79. package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
  80. package/lib/module/web/templates/RailRowRenderer.js +98 -0
  81. package/lib/module/web/templates/RowContainerStyle.js +100 -0
  82. package/lib/module/web/templates/RowElements.js +93 -0
  83. package/lib/module/web/templates/RowRendererRegistry.js +70 -0
  84. package/lib/module/web/templates/RowText.js +121 -0
  85. package/lib/module/web/templates/RowVisual.js +43 -0
  86. package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
  87. package/lib/module/web/templates/SystemRowRenderer.js +161 -0
  88. package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
  89. package/lib/typescript/src/models.d.ts +166 -13
  90. package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
  91. package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
  92. package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
  93. package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
  94. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
  95. package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
  96. package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
  97. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
  98. package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
  99. package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
  100. package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
  101. package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
  102. package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
  103. package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
  104. package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
  105. package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
  106. package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
  107. package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
  108. package/package.json +3 -3
  109. package/src/models.ts +281 -19
  110. package/src/validation.ts +501 -46
  111. package/src/web/NativeListWebEngine.ts +361 -1932
  112. package/src/web/templates/ActionRowRenderer.ts +123 -0
  113. package/src/web/templates/ActivityRowRenderer.ts +159 -0
  114. package/src/web/templates/DataRowRenderer.ts +212 -0
  115. package/src/web/templates/IdentityRowRenderer.ts +527 -0
  116. package/src/web/templates/MarketRowRenderer.ts +467 -0
  117. package/src/web/templates/MediaTileRowRenderer.ts +182 -0
  118. package/src/web/templates/MessageRowRenderer.ts +306 -0
  119. package/src/web/templates/MetricCardRowRenderer.ts +337 -0
  120. package/src/web/templates/RailRowRenderer.ts +136 -0
  121. package/src/web/templates/RowContainerStyle.ts +131 -0
  122. package/src/web/templates/RowElements.ts +160 -0
  123. package/src/web/templates/RowRendererRegistry.ts +111 -0
  124. package/src/web/templates/RowText.ts +181 -0
  125. package/src/web/templates/RowVisual.ts +115 -0
  126. package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
  127. package/src/web/templates/SystemRowRenderer.ts +282 -0
  128. package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
  129. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
  130. package/ios/NativeListCell.swift +0 -4300
@@ -0,0 +1,708 @@
1
+ import UIKit
2
+
3
+ final class NativeListDottedUnderlineLabel: NativeListTextLabel {
4
+ var showsDottedUnderline = false {
5
+ didSet { setNeedsLayout() }
6
+ }
7
+
8
+ // OneKey patch: migrated section titles include the source 3-point underline box.
9
+ var reservesDottedUnderlineSpace = false {
10
+ didSet {
11
+ invalidateIntrinsicContentSize()
12
+ setNeedsLayout()
13
+ setNeedsDisplay()
14
+ }
15
+ }
16
+
17
+ override var intrinsicContentSize: CGSize {
18
+ var size = super.intrinsicContentSize
19
+ if reservesDottedUnderlineSpace && showsDottedUnderline { size.height += 3 }
20
+ return size
21
+ }
22
+
23
+ override func drawText(in rect: CGRect) {
24
+ let textRect =
25
+ reservesDottedUnderlineSpace && showsDottedUnderline
26
+ ? CGRect(x: rect.minX, y: rect.minY, width: rect.width, height: max(0, rect.height - 3))
27
+ : rect
28
+ super.drawText(in: textRect)
29
+ }
30
+
31
+ var dottedUnderlineColor: UIColor = .clear {
32
+ didSet {
33
+ dottedUnderlineLayer.strokeColor = dottedUnderlineColor.cgColor
34
+ setNeedsLayout()
35
+ }
36
+ }
37
+
38
+ var dottedUnderlineVerticalOffset: CGFloat = 0 {
39
+ didSet { setNeedsLayout() }
40
+ }
41
+
42
+ private let dottedUnderlineLayer = CAShapeLayer()
43
+
44
+ override init(frame: CGRect) {
45
+ super.init(frame: frame)
46
+ dottedUnderlineLayer.fillColor = UIColor.clear.cgColor
47
+ dottedUnderlineLayer.lineWidth = 1.5
48
+ dottedUnderlineLayer.lineCap = .round
49
+ dottedUnderlineLayer.lineDashPattern = [0, 4]
50
+ layer.addSublayer(dottedUnderlineLayer)
51
+ }
52
+
53
+ required init?(coder: NSCoder) {
54
+ fatalError("init(coder:) has not been implemented")
55
+ }
56
+
57
+ override func layoutSubviews() {
58
+ super.layoutSubviews()
59
+ CATransaction.begin()
60
+ CATransaction.setDisableActions(true)
61
+ defer { CATransaction.commit() }
62
+ guard showsDottedUnderline, let attributedText else {
63
+ dottedUnderlineLayer.path = nil
64
+ dottedUnderlineLayer.isHidden = true
65
+ return
66
+ }
67
+ let textWidth = ceil(
68
+ attributedText.boundingRect(
69
+ with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: 24),
70
+ options: [.usesLineFragmentOrigin, .usesFontLeading],
71
+ context: nil
72
+ ).width)
73
+ dottedUnderlineLayer.frame = CGRect(
74
+ x: 0,
75
+ y: 0,
76
+ width: bounds.width,
77
+ height: bounds.height + 2 + dottedUnderlineVerticalOffset
78
+ )
79
+ // OneKey patch: explicit header underline occupies the reserved final two points.
80
+ // let y = bounds.height + 1 + dottedUnderlineVerticalOffset
81
+ let y =
82
+ reservesDottedUnderlineSpace
83
+ ? bounds.height - 1 : bounds.height + 1 + dottedUnderlineVerticalOffset
84
+ let path = UIBezierPath()
85
+ path.move(to: CGPoint(x: 1, y: y))
86
+ path.addLine(to: CGPoint(x: max(1, textWidth - 1), y: y))
87
+ dottedUnderlineLayer.path = path.cgPath
88
+ dottedUnderlineLayer.isHidden = false
89
+ }
90
+ }
91
+
92
+ final class NativeListSectionHeaderCell: NativeListRendererCell {
93
+ override class func appliesSizePreset(_ item: NativeListItem) -> Bool {
94
+ !["summary", "gallery"].contains(item.data.string("variant"))
95
+ }
96
+ override class func measure(
97
+ _ item: NativeListItem, width: CGFloat, theme: [String: Any]?, layout: String
98
+ ) -> CGFloat? {
99
+ if layout == "table" { return 28 }
100
+ if item.data.string("presentation") == "networkSelector" { return 47 }
101
+ if item.data.string("variant") == "history" {
102
+ return 16
103
+ }
104
+ if item.data.string("variant") == "summary" { return 68 }
105
+ if item.data.string("variant") == "gallery" { return 32 }
106
+ if item.data.dictionary("checkbox") != nil { return 56 }
107
+ return layout == "linear" ? 30 : 36
108
+ }
109
+ override var defaultCornerRadius: CGFloat { isHighlighted ? 12 : 0 }
110
+ private let mainStack = UIStackView()
111
+ private let titleRowStack = UIStackView()
112
+ private let titleLabel = NativeListDottedUnderlineLabel()
113
+ private let subtitleLabel = NativeListTextLabel()
114
+ private let trailingStack = UIStackView()
115
+ private let accessoryButtons = [NativeListAccessoryButton(type: .system)]
116
+ private let checkboxControl = NativeListAccessoryStack(frame: .zero)
117
+ private let headerTitleIconImageView = UIImageView()
118
+ private let headerValueIconImageView = UIImageView()
119
+ private var accessoryActions: [(String, NativeSelectionTarget?)] = []
120
+ private var currentItem: NativeListItem?
121
+ private var currentTheme: [String: Any]?
122
+ private var leftInset: CGFloat = 12
123
+ private var rightInset: CGFloat = -12
124
+ private var topInset: CGFloat = 8
125
+ private var bottomInset: CGFloat = -8
126
+ private var selectorConstraints: [NSLayoutConstraint] = []
127
+ private var selectorTypographyRestorers: [() -> Void] = []
128
+ private lazy var selectorTitleTap = UITapGestureRecognizer(
129
+ target: self, action: #selector(titlePressed))
130
+ override init(frame: CGRect) {
131
+ super.init(frame: frame)
132
+ root.axis = .horizontal
133
+ root.alignment = .center
134
+ root.spacing = 12
135
+ mainStack.axis = .vertical
136
+ mainStack.alignment = .fill
137
+ mainStack.spacing = 2
138
+ mainStack.setContentHuggingPriority(UILayoutPriority(1), for: .horizontal)
139
+ mainStack.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
140
+ titleRowStack.axis = .horizontal
141
+ titleRowStack.alignment = .center
142
+ titleRowStack.spacing = 8
143
+ titleRowStack.addArrangedSubview(titleLabel)
144
+ mainStack.addArrangedSubview(titleRowStack)
145
+ mainStack.addArrangedSubview(subtitleLabel)
146
+ titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
147
+ titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
148
+ trailingStack.axis = .vertical
149
+ trailingStack.alignment = .trailing
150
+ trailingStack.spacing = 2
151
+ trailingStack.setContentHuggingPriority(.required, for: .horizontal)
152
+ trailingStack.setContentCompressionResistancePriority(.required, for: .horizontal)
153
+ trailingStack.addArrangedSubview(accessoryButtons[0])
154
+ trailingStack.addArrangedSubview(checkboxControl)
155
+ accessoryButtons[0].addTarget(self, action: #selector(valuePressed), for: .touchUpInside)
156
+ for icon in [headerTitleIconImageView, headerValueIconImageView] {
157
+ icon.translatesAutoresizingMaskIntoConstraints = false
158
+ icon.contentMode = .scaleAspectFit
159
+ NSLayoutConstraint.activate([
160
+ icon.widthAnchor.constraint(equalToConstant: 12),
161
+ icon.heightAnchor.constraint(equalToConstant: 12),
162
+ ])
163
+ }
164
+ checkboxControl.onAction = { [weak self] key, view, slot, target in
165
+ self?.emitAction(key, from: view, source: "trailingAccessory", slot: slot, target: target)
166
+ }
167
+ }
168
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
169
+ override func bindContent(
170
+ _ item: NativeListItem, theme: [String: Any]?, layout: String,
171
+ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
172
+ ) {
173
+ selectorTypographyRestorers.reversed().forEach { $0() }
174
+ selectorTypographyRestorers = []
175
+ currentItem = item
176
+ currentTheme = theme
177
+ NSLayoutConstraint.deactivate(selectorConstraints)
178
+ selectorConstraints = []
179
+ root.arrangedSubviews.forEach {
180
+ root.removeArrangedSubview($0)
181
+ $0.removeFromSuperview()
182
+ }
183
+ for icon in [headerTitleIconImageView, headerValueIconImageView] {
184
+ if let parent = icon.superview as? UIStackView { parent.removeArrangedSubview(icon) }
185
+ icon.removeFromSuperview()
186
+ icon.image = nil
187
+ icon.isHidden = true
188
+ }
189
+ titleLabel.removeGestureRecognizer(selectorTitleTap)
190
+ titleLabel.isUserInteractionEnabled = false
191
+ titleLabel.showsDottedUnderline = false
192
+ titleLabel.reservesDottedUnderlineSpace = false
193
+ titleLabel.dottedUnderlineVerticalOffset = 0
194
+ for label in [titleLabel, subtitleLabel] {
195
+ label.attributedText = nil
196
+ label.text = nil
197
+ label.isHidden = true
198
+ label.numberOfLines = 1
199
+ label.lineBreakMode = .byTruncatingTail
200
+ label.textAlignment = .natural
201
+ label.rowOffsetY = 0
202
+ label.rowVerticalAlignment = nil
203
+ }
204
+ titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
205
+ subtitleLabel.font = nativeListFont(ofSize: 14)
206
+ subtitleLabel.textColor = nativeListColor(theme, "secondaryText", "#646464")
207
+ mainStack.alignment = .fill
208
+ mainStack.spacing = 2
209
+ titleRowStack.spacing = 8
210
+ trailingStack.axis = .vertical
211
+ trailingStack.alignment = .trailing
212
+ trailingStack.spacing = 2
213
+ let value = accessoryButtons[0]
214
+ value.setTitle(nil, for: .normal)
215
+ value.setAttributedTitle(nil, for: .normal)
216
+ value.titleLabel?.font = nativeListFont(ofSize: 16, weight: .medium)
217
+ value.titleLabel?.numberOfLines = 1
218
+ value.titleLabel?.lineBreakMode = .byTruncatingTail
219
+ value.contentHorizontalAlignment = .center
220
+ value.contentVerticalAlignment = .center
221
+ value.rowTextOffsetY = 0
222
+ value.marketLineHeight = nil
223
+ value.selectorSummaryLineHeight = nil
224
+ value.isHidden = true
225
+ value.accessibilityIdentifier = nil
226
+ value.setTitleColor(nativeListColor(theme, "primaryText", "#202020"), for: .normal)
227
+ accessoryActions = []
228
+ checkboxControl.reset()
229
+ checkboxControl.isHidden = true
230
+ leftInset = layout == "table" ? 16 : 12
231
+ rightInset = -leftInset
232
+ topInset = 8
233
+ bottomInset = -8
234
+ bindSectionHeader(item, theme: theme, layout: layout, checkboxState)
235
+ let style = item.data.dictionary("style") ?? [:]
236
+ if style["horizontalPadding"] != nil {
237
+ leftInset = CGFloat(style.double("horizontalPadding"))
238
+ rightInset = -leftInset
239
+ }
240
+ if style["verticalPadding"] != nil {
241
+ topInset = CGFloat(style.double("verticalPadding"))
242
+ bottomInset = -topInset
243
+ }
244
+ if style["lineGap"] != nil { mainStack.spacing = CGFloat(style.double("lineGap")) }
245
+ if style["trailingGap"] != nil { trailingStack.spacing = CGFloat(style.double("trailingGap")) }
246
+ applyTextStyle(item)
247
+ contentInsets = UIEdgeInsets(
248
+ top: topInset, left: leftInset, bottom: -bottomInset, right: -rightInset)
249
+ root.alignment =
250
+ style.dictionary("container")?.string("contentVerticalAlignment") == "top"
251
+ ? .top
252
+ : style.dictionary("container")?.string("contentVerticalAlignment") == "bottom"
253
+ ? .bottom : .center
254
+ applySelectorTypography(item)
255
+ // Legacy measured the value after selector typography (tabular digits, kern 0).
256
+ if item.data.string("presentation") == "networkSelector", item.hasExplicitHeight,
257
+ item.data.dictionary("checkbox") != nil, !item.data.string("value").isEmpty
258
+ {
259
+ let width = trailingStack.widthAnchor.constraint(
260
+ equalToConstant: accessoryButtons[0].intrinsicContentSize.width + 12 + 20)
261
+ width.isActive = true
262
+ selectorConstraints.append(width)
263
+ }
264
+ }
265
+ private func applyTextStyle(_ item: NativeListItem) {
266
+ let style = item.data.dictionary("style") ?? [:]
267
+ if let title = style.dictionary("title") {
268
+ NativeListTextStyles.applyStyledText(titleLabel, title)
269
+ }
270
+ if let subtitle = style.dictionary("subtitle") {
271
+ NativeListTextStyles.applyStyledText(subtitleLabel, subtitle)
272
+ }
273
+ if let value = style.dictionary("value") {
274
+ NativeListTextStyles.applyStyledButton(accessoryButtons[0], value)
275
+ }
276
+ }
277
+ override func bindSelectionContent(
278
+ _ item: NativeListItem,
279
+ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
280
+ ) {
281
+ currentItem = item
282
+ checkboxControl.updateSelection(item, checkboxState: checkboxState)
283
+ if item.data.string("variant") == "summary" {
284
+ selectorTypographyRestorers.reversed().forEach { $0() }
285
+ selectorTypographyRestorers = []
286
+ updateSummaryText(item)
287
+ applyValueSegments(
288
+ item.data.dictionaries("valueSegments"), to: accessoryButtons[0], theme: currentTheme)
289
+ applyTextStyle(item)
290
+ applySelectorTypography(item)
291
+ }
292
+ }
293
+ private func bindCheckbox(
294
+ _ item: NativeListItem, _ descriptor: [String: Any],
295
+ _ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
296
+ ) {
297
+ checkboxControl.bind(
298
+ item, descriptors: [descriptor], theme: currentTheme, style: [:], checkboxState: checkboxState
299
+ )
300
+ }
301
+ override func recycleContent() {
302
+ checkboxControl.reset()
303
+ currentItem = nil
304
+ }
305
+ @objc private func titlePressed() {
306
+ if let item = currentItem {
307
+ emitAction(item.data.string("titleActionKey"), from: titleLabel, source: "leadingAction")
308
+ }
309
+ }
310
+ @objc private func valuePressed() {
311
+ if let action = accessoryActions.first {
312
+ emitAction(
313
+ action.0, from: accessoryButtons[0], source: "trailingAccessory", slot: 0, target: action.1)
314
+ }
315
+ }
316
+ private func show(_ label: UILabel, _ text: String, lines: Int) {
317
+ label.text = text
318
+ label.numberOfLines = lines
319
+ label.isHidden = text.isEmpty
320
+ }
321
+ private func bindSectionHeader(
322
+ _ item: NativeListItem,
323
+ theme: [String: Any]?,
324
+ layout: String,
325
+ _ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
326
+ ) {
327
+ root.addArrangedSubview(mainStack)
328
+ let variant = item.data.string("variant")
329
+ // OneKey patch: title help is a separate target from checkbox and value actions.
330
+ if !item.data.string("titleActionKey").isEmpty {
331
+ titleLabel.isUserInteractionEnabled = true
332
+ titleLabel.addGestureRecognizer(selectorTitleTap)
333
+ titleLabel.setContentHuggingPriority(.required, for: .horizontal)
334
+ }
335
+ let isSummary = variant == "summary"
336
+ let isGallery = variant == "gallery"
337
+ let isTable = layout == "table"
338
+ let isNetworkSelector = item.data.string("presentation") == "networkSelector"
339
+ let isExplicitNetworkHeader = isNetworkSelector && item.hasExplicitHeight
340
+ if isExplicitNetworkHeader {
341
+ // OneKey patch: the flexible title consumes spare space before trailing totals.
342
+ trailingStack.setContentHuggingPriority(.required, for: .horizontal)
343
+ trailingStack.setContentCompressionResistancePriority(.required, for: .horizontal)
344
+ }
345
+ titleLabel.reservesDottedUnderlineSpace =
346
+ isExplicitNetworkHeader && !item.data.string("titleActionKey").isEmpty
347
+ let isHistory = variant == "history"
348
+ let headerWeight: NativeListFontWeight =
349
+ isSummary
350
+ ? .medium
351
+ : isExplicitNetworkHeader
352
+ && (item.data.dictionary("checkbox") != nil || item.data.string("titleActionKey").isEmpty)
353
+ ? .semibold
354
+ : isNetworkSelector
355
+ ? .medium
356
+ : isGallery || layout == "sectioned" ? .semibold : .regular
357
+ titleLabel.font = nativeListFont(
358
+ ofSize: isGallery ? 18 : isSummary ? 16 : isTable ? 11 : isHistory ? 12 : 14,
359
+ weight: isHistory ? .semibold : headerWeight
360
+ )
361
+ titleLabel.textColor = nativeListColor(
362
+ theme,
363
+ isSummary || isGallery ? "primaryText" : "secondaryText",
364
+ isSummary || isGallery ? "#202020" : "#646464"
365
+ )
366
+ show(titleLabel, item.data.string("title"), lines: 1)
367
+ if isNetworkSelector {
368
+ titleLabel.showsDottedUnderline =
369
+ !isExplicitNetworkHeader || !item.data.string("titleActionKey").isEmpty
370
+ titleLabel.dottedUnderlineVerticalOffset = isExplicitNetworkHeader ? 1 : 2
371
+ titleLabel.dottedUnderlineColor = nativeListColor(
372
+ theme,
373
+ "secondaryText",
374
+ "#646464"
375
+ )
376
+ leftInset = 12
377
+ rightInset = -12
378
+ let isAlphabet = isExplicitNetworkHeader && item.data.string("titleActionKey").isEmpty
379
+ topInset = isAlphabet ? 8 : 12
380
+ bottomInset = isAlphabet ? -8 : isExplicitNetworkHeader ? -12 : -15
381
+ setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 20)
382
+ } else if isHistory {
383
+ leftInset = 0
384
+ rightInset = 0
385
+ topInset = 0
386
+ bottomInset = 0
387
+ setLineHeight(
388
+ titleLabel,
389
+ text: item.data.string("title").uppercased(),
390
+ lineHeight: 16,
391
+ letterSpacing: 0.8
392
+ )
393
+ } else if !isTable && !isGallery && !isSummary {
394
+ setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 20)
395
+ if layout == "linear" {
396
+ // TokenManager's section title is a plain body label with mt=10,
397
+ // unlike the reusable 36-point SectionHeader used by sectioned lists.
398
+ titleLabel.font = nativeListFont(ofSize: 14)
399
+ topInset = 10
400
+ bottomInset = 0
401
+ }
402
+ }
403
+ if isTable {
404
+ mainStack.alignment = .leading
405
+ titleRowStack.spacing = 4
406
+ titleLabel.setContentHuggingPriority(.required, for: .horizontal)
407
+ setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 14)
408
+ topInset = 12
409
+ bottomInset = -2
410
+ if let icon = item.data.dictionary("titleIcon") {
411
+ headerTitleIconImageView.image = nativeListIcon(named: icon.string("name"))
412
+ headerTitleIconImageView.tintColor = UIColor(
413
+ nativeListHex: icon.string("tintColor", default: "#646464"),
414
+ fallback: .darkGray
415
+ )
416
+ headerTitleIconImageView.isHidden = false
417
+ titleRowStack.insertArrangedSubview(headerTitleIconImageView, at: 1)
418
+ if icon.string("name") != "ChevronGrabberVerOutline" {
419
+ titleLabel.textColor = nativeListColor(theme, "primaryText", "#202020")
420
+ setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 14)
421
+ }
422
+ }
423
+ }
424
+ if isGallery {
425
+ setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 24)
426
+ topInset = 0
427
+ bottomInset = -8
428
+ }
429
+ show(subtitleLabel, item.data.string("subtitle"), lines: 1)
430
+ if isSummary {
431
+ titleLabel.showsDottedUnderline = true
432
+ titleLabel.dottedUnderlineColor = nativeListColor(
433
+ theme,
434
+ "secondaryText",
435
+ "#646464"
436
+ )
437
+ root.addArrangedSubview(trailingStack)
438
+ // The surrounding Stack contributes mt=16/pb=12 while its XStack uses
439
+ // px=20/py=8. The collection's 8-point inset supplies the outer 8 points
440
+ // of horizontal padding, so the cell supplies the remaining 12.
441
+ leftInset = 12
442
+ rightInset = -12
443
+ topInset = 24
444
+ bottomInset = -20
445
+ setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 24)
446
+ accessoryButtons[0].accessibilityIdentifier = item.data["valueActionTestID"] as? String
447
+ let valueActionKey = item.data.string("valueActionKey")
448
+ let action: (String, NativeSelectionTarget?)? =
449
+ valueActionKey.isEmpty
450
+ ? nil
451
+ : (valueActionKey, nil)
452
+ showAccessory(
453
+ 0,
454
+ item.data.string("value"),
455
+ action: action,
456
+ color: nativeListColor(theme, "secondaryText", "#646464")
457
+ )
458
+ accessoryButtons[0].titleLabel?.font = nativeListFont(
459
+ ofSize: 16, weight: isExplicitNetworkHeader ? .medium : .regular)
460
+ setButtonLine(
461
+ accessoryButtons[0],
462
+ text: item.data.string("value"),
463
+ font: nativeListFont(ofSize: 16, weight: isExplicitNetworkHeader ? .medium : .regular),
464
+ color: nativeListColor(theme, "secondaryText", "#646464"),
465
+ lineHeight: 24
466
+ )
467
+ } else if !isGallery && !isHistory {
468
+ root.addArrangedSubview(trailingStack)
469
+ showAccessory(0, item.data.string("value"))
470
+ if isTable {
471
+ trailingStack.axis = .horizontal
472
+ trailingStack.alignment = .center
473
+ trailingStack.spacing = 4
474
+ accessoryButtons[0].titleLabel?.font = nativeListFont(ofSize: 11)
475
+ accessoryButtons[0].setTitleColor(
476
+ nativeListColor(theme, "secondaryText", "#646464"),
477
+ for: .normal
478
+ )
479
+ if let icon = item.data.dictionary("valueIcon") {
480
+ headerValueIconImageView.image = nativeListIcon(named: icon.string("name"))
481
+ headerValueIconImageView.tintColor = UIColor(
482
+ nativeListHex: icon.string("tintColor", default: "#646464"),
483
+ fallback: .darkGray
484
+ )
485
+ headerValueIconImageView.isHidden = false
486
+ trailingStack.addArrangedSubview(headerValueIconImageView)
487
+ if icon.string("name") != "ChevronGrabberVerOutline" {
488
+ accessoryButtons[0].setTitleColor(
489
+ nativeListColor(theme, "primaryText", "#202020"),
490
+ for: .normal
491
+ )
492
+ }
493
+ }
494
+ }
495
+ if let checkbox = item.data.dictionary("checkbox") {
496
+ bindCheckbox(item, checkbox, checkboxState)
497
+ }
498
+ if item.data.dictionary("checkbox") != nil && !item.data.string("value").isEmpty {
499
+ titleLabel.showsDottedUnderline = true
500
+ titleLabel.dottedUnderlineColor = nativeListColor(
501
+ theme,
502
+ "secondaryText",
503
+ "#646464"
504
+ )
505
+ trailingStack.axis = .horizontal
506
+ trailingStack.alignment = .center
507
+ trailingStack.spacing = 12
508
+ topInset = 8
509
+ bottomInset = -8
510
+ accessoryButtons[0].titleLabel?.font = nativeListTabularFont(
511
+ ofSize: 16,
512
+ weight: .medium
513
+ )
514
+ setButtonLine(
515
+ accessoryButtons[0],
516
+ text: item.data.string("value"),
517
+ font: nativeListTabularFont(ofSize: 16, weight: .medium),
518
+ color: nativeListColor(theme, "primaryText", "#202020"),
519
+ lineHeight: 24
520
+ )
521
+ }
522
+ }
523
+ applyValueSegments(
524
+ item.data.dictionaries("valueSegments"), to: accessoryButtons[0], theme: theme)
525
+ }
526
+ private func setLineHeight(
527
+ _ label: UILabel,
528
+ text: String,
529
+ lineHeight: CGFloat,
530
+ letterSpacing: CGFloat = 0
531
+ ) {
532
+ let paragraphStyle = NSMutableParagraphStyle()
533
+ paragraphStyle.minimumLineHeight = lineHeight
534
+ paragraphStyle.maximumLineHeight = lineHeight
535
+ paragraphStyle.alignment = label.textAlignment
536
+ var attributes: [NSAttributedString.Key: Any] = [
537
+ .font: label.font as Any,
538
+ .foregroundColor: label.textColor as Any,
539
+ .paragraphStyle: paragraphStyle,
540
+ ]
541
+ if currentItem?.hasExplicitHeight == true
542
+ && currentItem?.data.string("presentation") == "networkSelector"
543
+ {
544
+ let baselineOffset = max(0, (lineHeight - label.font.lineHeight) / 2)
545
+ let scale = window?.screen.scale ?? traitCollection.displayScale
546
+ attributes[.baselineOffset] =
547
+ lineHeight == 20 && scale > 0 ? ceil(baselineOffset * scale) / scale : baselineOffset
548
+ }
549
+ if letterSpacing != 0 { attributes[.kern] = letterSpacing }
550
+ label.attributedText = NSAttributedString(string: text, attributes: attributes)
551
+ }
552
+ private func setButtonLine(
553
+ _ button: UIButton,
554
+ text: String,
555
+ font: UIFont,
556
+ color: UIColor,
557
+ lineHeight: CGFloat
558
+ ) {
559
+ guard !text.isEmpty else { return }
560
+ let paragraphStyle = NSMutableParagraphStyle()
561
+ paragraphStyle.minimumLineHeight = lineHeight
562
+ paragraphStyle.maximumLineHeight = lineHeight
563
+ let isSelectorValue =
564
+ currentItem?.data.string("presentation") == "networkSelector"
565
+ && currentItem?.hasExplicitHeight == true && currentItem?.data.string("variant") != "summary"
566
+ let isSelectorSummary =
567
+ currentItem?.data.string("presentation") == "networkSelector"
568
+ && currentItem?.hasExplicitHeight == true && currentItem?.data.string("variant") == "summary"
569
+ (button as? NativeListAccessoryButton)?.selectorSummaryLineHeight =
570
+ isSelectorSummary ? lineHeight : nil
571
+ // OneKey patch: summary text uses its source line box; currency retains trailing alignment.
572
+ paragraphStyle.alignment =
573
+ isSelectorValue ? .right : isSelectorSummary ? .natural : .center
574
+ if isSelectorSummary {
575
+ button.contentHorizontalAlignment = .leading
576
+ button.titleLabel?.textAlignment = .natural
577
+ }
578
+ if isSelectorValue {
579
+ button.contentHorizontalAlignment = .trailing
580
+ button.titleLabel?.textAlignment = .right
581
+ }
582
+ let baselineOffset: CGFloat =
583
+ isSelectorValue || isSelectorSummary
584
+ ? max(0, (lineHeight - font.lineHeight) / 2) : 0
585
+ let attributes: [NSAttributedString.Key: Any] = [
586
+ .font: font,
587
+ .foregroundColor: color,
588
+ .paragraphStyle: paragraphStyle,
589
+ .baselineOffset: baselineOffset,
590
+ ]
591
+ button.setAttributedTitle(
592
+ NSAttributedString(string: text, attributes: attributes),
593
+ for: .normal
594
+ )
595
+ }
596
+ private func applyValueSegments(
597
+ _ segments: [[String: Any]], to button: UIButton, theme: [String: Any]?
598
+ ) {
599
+ guard !segments.isEmpty else { return }
600
+ let value = NSMutableAttributedString(string: "")
601
+ let color = nativeListColor(theme, "primaryText", "#202020")
602
+ // OneKey patch: rich currency changes font runs without dropping the established line baseline.
603
+ let current = button.attributedTitle(for: .normal)
604
+ var attributes =
605
+ current.flatMap { $0.length > 0 ? $0.attributes(at: 0, effectiveRange: nil) : nil } ?? [:]
606
+ attributes[.foregroundColor] = color
607
+ if currentItem?.data.string("presentation") == "networkSelector"
608
+ && currentItem?.hasExplicitHeight == true
609
+ {
610
+ let paragraph =
611
+ (attributes[.paragraphStyle] as? NSParagraphStyle)?.mutableCopy()
612
+ as? NSMutableParagraphStyle ?? NSMutableParagraphStyle()
613
+ paragraph.alignment = .right
614
+ attributes[.paragraphStyle] = paragraph
615
+ }
616
+ for segment in segments {
617
+ attributes[.font] = nativeListTabularFont(
618
+ ofSize: segment.string("style") == "subscript" ? 10 : 16, weight: .medium)
619
+ value.append(NSAttributedString(string: segment.string("text"), attributes: attributes))
620
+ }
621
+ button.setAttributedTitle(value, for: .normal)
622
+ }
623
+ private func showAccessory(
624
+ _ index: Int,
625
+ _ value: String,
626
+ action: (String, NativeSelectionTarget?)? = nil,
627
+ color: UIColor? = nil
628
+ ) {
629
+ guard accessoryButtons.indices.contains(index), !value.isEmpty else { return }
630
+ let button = accessoryButtons[index]
631
+ button.isHidden = false
632
+ button.setTitle(value, for: .normal)
633
+ button.setContentHuggingPriority(.required, for: .horizontal)
634
+ button.setContentCompressionResistancePriority(.required, for: .horizontal)
635
+ if let color { button.setTitleColor(color, for: .normal) }
636
+ while accessoryActions.count <= index { accessoryActions.append(("", nil)) }
637
+ accessoryActions[index] = action ?? ("", nil)
638
+ }
639
+ private func updateSummaryText(_ item: NativeListItem) {
640
+ let title = item.data.string("title")
641
+ titleLabel.isHidden = title.isEmpty
642
+ setLineHeight(titleLabel, text: title, lineHeight: 24)
643
+
644
+ let value = item.data.string("value")
645
+ let isExplicitNetworkHeader =
646
+ item.data.string("presentation") == "networkSelector" && item.hasExplicitHeight
647
+ let valueButton = accessoryButtons[0]
648
+ valueButton.isHidden = value.isEmpty
649
+ if value.isEmpty {
650
+ valueButton.setTitle(nil, for: .normal)
651
+ valueButton.setAttributedTitle(nil, for: .normal)
652
+ } else {
653
+ setButtonLine(
654
+ valueButton,
655
+ text: value,
656
+ font: nativeListFont(ofSize: 16, weight: isExplicitNetworkHeader ? .medium : .regular),
657
+ color: nativeListColor(currentTheme, "secondaryText", "#646464"),
658
+ lineHeight: 24
659
+ )
660
+ }
661
+ }
662
+ private func selectorTabularFont(_ font: UIFont) -> UIFont {
663
+ var settings =
664
+ font.fontDescriptor.fontAttributes[.featureSettings] as? [[UIFontDescriptor.FeatureKey: Int]]
665
+ ?? []
666
+ settings.removeAll { $0[.type] == kNumberSpacingType }
667
+ settings.append([.type: kNumberSpacingType, .selector: kMonospacedNumbersSelector])
668
+ return UIFont(
669
+ descriptor: font.fontDescriptor.addingAttributes([.featureSettings: settings]),
670
+ size: font.pointSize)
671
+ }
672
+ private func selectorTabularText(_ original: NSAttributedString) -> NSAttributedString {
673
+ let result = NSMutableAttributedString(attributedString: original)
674
+ // OneKey patch: body typography explicitly supplies letterSpacing=0 in Tamagui.
675
+ result.addAttribute(.kern, value: 0, range: NSRange(location: 0, length: result.length))
676
+ original.enumerateAttribute(.font, in: NSRange(location: 0, length: original.length)) {
677
+ value, range, _ in
678
+ if let font = value as? UIFont {
679
+ result.addAttribute(.font, value: self.selectorTabularFont(font), range: range)
680
+ }
681
+ }
682
+ return result
683
+ }
684
+ private func applySelectorTypography(_ item: NativeListItem) {
685
+ guard
686
+ ["accountSelector", "networkSelector", "walletSidebar"].contains(
687
+ item.data.string("presentation"))
688
+ else { return }
689
+ func visit(_ view: UIView) {
690
+ if let button = view as? UIButton {
691
+ if let original = button.attributedTitle(for: .normal) {
692
+ selectorTypographyRestorers.append { button.setAttributedTitle(original, for: .normal) }
693
+ button.setAttributedTitle(selectorTabularText(original), for: .normal)
694
+ }
695
+ } else if let label = view as? UILabel, let font = label.font {
696
+ let original = label.attributedText
697
+ selectorTypographyRestorers.append {
698
+ label.font = font
699
+ label.attributedText = original
700
+ }
701
+ label.font = selectorTabularFont(font)
702
+ if let original { label.attributedText = selectorTabularText(original) }
703
+ }
704
+ for child in view.subviews { visit(child) }
705
+ }
706
+ visit(contentView)
707
+ }
708
+ }