@onekeyfe/react-native-native-list 3.0.157-alpha.249 → 3.0.157-alpha.251

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,568 @@
1
+ import UIKit
2
+
3
+ final class NativeListSystemCell: NativeListRendererCell {
4
+ override class func measure(
5
+ _ item: NativeListItem, width: CGFloat, theme: [String: Any]?, layout: String
6
+ ) -> CGFloat? {
7
+ return NativeListSystemRenderer.measure(item, width: width)
8
+ }
9
+ override class func appliesSizePreset(_ item: NativeListItem) -> Bool {
10
+ NativeListSystemRenderer.appliesSizePreset(item)
11
+ }
12
+
13
+ private let mainStack = UIStackView()
14
+ private let titleRowStack = UIStackView()
15
+ private let titleLabel = NativeListTextLabel()
16
+ private let subtitleLabel = NativeListTextLabel()
17
+ private let trailingStack = UIStackView()
18
+ private let accessoryButtons = [NativeListAccessoryButton(type: .system)]
19
+ private let leadingContainer = UIView()
20
+ private let skeletonPrimary = UIView()
21
+ private let skeletonSecondary = UIView()
22
+ private var leadingWidth: NSLayoutConstraint!
23
+ private var leadingHeight: NSLayoutConstraint!
24
+ private var selectorViews: [UIView] = []
25
+ private var selectorConstraints: [NSLayoutConstraint] = []
26
+ private var currentItem: NativeListItem?
27
+ private var actionKey = ""
28
+ private var leftInset: CGFloat = 12
29
+ private var rightInset: CGFloat = -12
30
+ private var topInset: CGFloat = 8
31
+ private var bottomInset: CGFloat = -8
32
+
33
+ override init(frame: CGRect) {
34
+ super.init(frame: frame)
35
+ mainStack.axis = .vertical
36
+ mainStack.alignment = .fill
37
+ mainStack.setContentHuggingPriority(UILayoutPriority(1), for: .horizontal)
38
+ titleRowStack.axis = .horizontal
39
+ titleRowStack.alignment = .center
40
+ titleRowStack.addArrangedSubview(titleLabel)
41
+ titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
42
+ titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
43
+ mainStack.addArrangedSubview(titleRowStack)
44
+ mainStack.addArrangedSubview(subtitleLabel)
45
+ trailingStack.axis = .vertical
46
+ trailingStack.alignment = .trailing
47
+ trailingStack.addArrangedSubview(accessoryButtons[0])
48
+ trailingStack.setContentHuggingPriority(.required, for: .horizontal)
49
+ trailingStack.setContentCompressionResistancePriority(.required, for: .horizontal)
50
+ accessoryButtons[0].addTarget(self, action: #selector(actionPressed), for: .touchUpInside)
51
+ leadingContainer.translatesAutoresizingMaskIntoConstraints = false
52
+ leadingWidth = leadingContainer.widthAnchor.constraint(equalToConstant: 40)
53
+ leadingHeight = leadingContainer.heightAnchor.constraint(equalToConstant: 40)
54
+ NSLayoutConstraint.activate([leadingWidth, leadingHeight])
55
+ }
56
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
57
+ override func bindContent(
58
+ _ item: NativeListItem, theme: [String: Any]?, layout: String,
59
+ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
60
+ ) {
61
+ recycleContent()
62
+ currentItem = item
63
+ root.axis = .horizontal
64
+ root.alignment = .center
65
+ root.spacing = 12
66
+ leftInset = layout == "table" ? 16 : 12
67
+ rightInset = -leftInset
68
+ topInset = 8
69
+ bottomInset = -8
70
+ mainStack.alignment = .fill
71
+ mainStack.spacing = 2
72
+ titleRowStack.isHidden = false
73
+ for label in [titleLabel, subtitleLabel] {
74
+ label.attributedText = nil
75
+ label.text = nil
76
+ label.isHidden = true
77
+ label.font = nativeListFont(
78
+ ofSize: label === titleLabel ? 16 : 14, weight: label === titleLabel ? .medium : .regular)
79
+ label.textColor = nativeListColor(
80
+ theme, label === titleLabel ? "primaryText" : "secondaryText",
81
+ label === titleLabel ? "#202020" : "#646464")
82
+ label.numberOfLines = 1
83
+ label.textAlignment = .natural
84
+ label.lineBreakMode = .byTruncatingTail
85
+ label.rowVerticalAlignment = nil
86
+ label.rowOffsetY = 0
87
+ label.transform = .identity
88
+ }
89
+ let button = accessoryButtons[0]
90
+ button.setAttributedTitle(nil, for: .normal)
91
+ button.setTitle(nil, for: .normal)
92
+ button.isHidden = true
93
+ button.titleLabel?.font = nativeListFont(ofSize: 16, weight: .medium)
94
+ button.titleLabel?.numberOfLines = 1
95
+ button.contentEdgeInsets = .zero
96
+ button.contentHorizontalAlignment = .center
97
+ button.contentVerticalAlignment = .center
98
+ button.rowTextOffsetY = 0
99
+ button.marketLineHeight = nil
100
+ button.layer.cornerRadius = 0
101
+ button.backgroundColor = .clear
102
+ bindSystem(item, theme: theme)
103
+ let style = item.data.dictionary("style") ?? [:]
104
+ if style["horizontalPadding"] != nil {
105
+ leftInset = CGFloat(style.double("horizontalPadding"))
106
+ rightInset = -leftInset
107
+ }
108
+ if style["verticalPadding"] != nil {
109
+ topInset = CGFloat(style.double("verticalPadding"))
110
+ bottomInset = -topInset
111
+ }
112
+ if style["lineGap"] != nil { mainStack.spacing = CGFloat(style.double("lineGap")) }
113
+ if style["leadingGap"] != nil && root.arrangedSubviews.contains(leadingContainer) {
114
+ root.setCustomSpacing(CGFloat(style.double("leadingGap")), after: leadingContainer)
115
+ }
116
+ if style["trailingGap"] != nil { trailingStack.spacing = CGFloat(style.double("trailingGap")) }
117
+ let warning = item.data.string("variant") == "warning"
118
+ if warning, let text = style.dictionary("title") {
119
+ NativeListTextStyles.applyStyledText(titleLabel, text)
120
+ }
121
+ if let text = style.dictionary("message") {
122
+ NativeListTextStyles.applyStyledText(warning ? subtitleLabel : titleLabel, text)
123
+ }
124
+ if let text = style.dictionary("actionText") {
125
+ NativeListTextStyles.applyStyledButton(button, text)
126
+ }
127
+ contentInsets = UIEdgeInsets(
128
+ top: topInset, left: leftInset, bottom: -bottomInset, right: -rightInset)
129
+ if root.axis == .horizontal,
130
+ let alignment = style.dictionary("container")?["contentVerticalAlignment"] as? String
131
+ {
132
+ root.alignment = alignment == "top" ? .top : alignment == "bottom" ? .bottom : .center
133
+ }
134
+ }
135
+ override func recycleContent() {
136
+ NSLayoutConstraint.deactivate(selectorConstraints)
137
+ selectorConstraints.removeAll()
138
+ selectorViews.forEach { view in
139
+ if let stack = view.superview as? UIStackView { stack.removeArrangedSubview(view) }
140
+ view.removeFromSuperview()
141
+ }
142
+ selectorViews.removeAll()
143
+ for view in root.arrangedSubviews {
144
+ root.removeArrangedSubview(view)
145
+ view.removeFromSuperview()
146
+ }
147
+ for view in [skeletonPrimary, skeletonSecondary] {
148
+ mainStack.removeArrangedSubview(view)
149
+ view.removeFromSuperview()
150
+ }
151
+ currentItem = nil
152
+ actionKey = ""
153
+ }
154
+ @objc private func actionPressed() {
155
+ emitAction(actionKey, from: accessoryButtons[0], source: "trailingAccessory", slot: 0)
156
+ }
157
+ private func showAccessory(
158
+ _ index: Int, _ text: String, action: (String, NativeSelectionTarget?)?
159
+ ) {
160
+ accessoryButtons[0].setTitle(text, for: .normal)
161
+ accessoryButtons[0].isHidden = text.isEmpty
162
+ actionKey = action?.0 ?? ""
163
+ accessoryButtons[0].setContentHuggingPriority(.required, for: .horizontal)
164
+ accessoryButtons[0].setContentCompressionResistancePriority(.required, for: .horizontal)
165
+ }
166
+ private func show(_ label: UILabel, _ text: String, lines: Int) {
167
+ label.text = text
168
+ label.numberOfLines = lines
169
+ label.isHidden = text.isEmpty
170
+ }
171
+ private func setLineHeight(_ label: UILabel, text: String, lineHeight: CGFloat) {
172
+ let paragraph = NSMutableParagraphStyle()
173
+ paragraph.minimumLineHeight = lineHeight
174
+ paragraph.maximumLineHeight = lineHeight
175
+ paragraph.alignment = label.textAlignment
176
+ var attrs: [NSAttributedString.Key: Any] = [
177
+ .font: label.font as Any, .foregroundColor: label.textColor as Any,
178
+ .paragraphStyle: paragraph,
179
+ ]
180
+ if currentItem?.data.string("variant") == "warning"
181
+ || currentItem?.data.string("presentation") == "market"
182
+ && currentItem?.data.string("variant") == "noMatch"
183
+ {
184
+ attrs[.baselineOffset] = max(0, (lineHeight - label.font.lineHeight) / 2)
185
+ }
186
+ if currentItem?.data.string("presentation") == "market" { attrs[.kern] = 0 }
187
+ label.attributedText = NSAttributedString(string: text, attributes: attrs)
188
+ }
189
+ private func setButtonLine(
190
+ _ button: UIButton, text: String, font: UIFont, color: UIColor, lineHeight: CGFloat
191
+ ) {
192
+ let market = currentItem?.data.string("presentation") == "market"
193
+ let paragraph = NSMutableParagraphStyle()
194
+ paragraph.minimumLineHeight = lineHeight
195
+ paragraph.maximumLineHeight = lineHeight
196
+ paragraph.alignment = market ? .natural : .center
197
+ let scale = max(1, traitCollection.displayScale)
198
+ let offset = market ? ceil(max(0, (lineHeight - font.lineHeight) / 2) * scale) / scale : 0
199
+ (button as? NativeListAccessoryButton)?.marketLineHeight = market ? lineHeight : nil
200
+ button.setAttributedTitle(
201
+ NSAttributedString(
202
+ string: text,
203
+ attributes: [
204
+ .font: font, .foregroundColor: color, .paragraphStyle: paragraph, .baselineOffset: offset,
205
+ ]), for: .normal)
206
+ }
207
+ private func bindSystem(_ item: NativeListItem, theme: [String: Any]?) {
208
+ root.alignment = .center
209
+ root.distribution = .fill
210
+ let variant = item.data.string("variant")
211
+ let isMarket = item.data.string("presentation") == "market"
212
+ if isMarket {
213
+ leftInset = 20
214
+ rightInset = -20
215
+ topInset = 12
216
+ bottomInset = -12
217
+ }
218
+ if isMarket && variant == "retry" {
219
+ let message = item.data.string("message")
220
+ let text = item.data.string("actionText", default: "Retry")
221
+ root.axis = .vertical
222
+ // The source tertiary Button has -5 vertical margins around its 30pt frame.
223
+ root.spacing = 7
224
+ leftInset = 32
225
+ rightInset = -32
226
+ let height = CGFloat(item.data.double("height", default: message.isEmpty ? 52 : 120))
227
+ let top = message.isEmpty ? 11 : max(32, (height - 56) / 2)
228
+ topInset = top
229
+ bottomInset = -(height - top - (message.isEmpty ? 30 : 61))
230
+ if !message.isEmpty {
231
+ show(titleLabel, message, lines: 2)
232
+ titleLabel.font = nativeListTabularFont(ofSize: 16)
233
+ titleLabel.textColor = nativeListColor(theme, "secondaryText", "#646464")
234
+ titleLabel.textAlignment = .center
235
+ setLineHeight(titleLabel, text: message, lineHeight: 24)
236
+ root.addArrangedSubview(mainStack)
237
+ }
238
+ showAccessory(0, text, action: (item.data.string("actionKey"), nil))
239
+ let button = accessoryButtons[0]
240
+ button.backgroundColor = .clear
241
+ button.layer.cornerRadius = 15
242
+ setButtonLine(
243
+ button, text: text, font: nativeListTabularFont(ofSize: 14, weight: .medium),
244
+ color: nativeListColor(theme, "secondaryText", "#646464"), lineHeight: 20)
245
+ let textWidth = button.intrinsicContentSize.width
246
+ selectorConstraints.append(contentsOf: [
247
+ button.widthAnchor.constraint(equalToConstant: textWidth + 18),
248
+ button.heightAnchor.constraint(equalToConstant: 30),
249
+ ])
250
+ NSLayoutConstraint.activate(selectorConstraints)
251
+ root.addArrangedSubview(trailingStack)
252
+ return
253
+ }
254
+ if variant == "loading" && item.data.string("loadingStyle") == "skeleton" {
255
+ leftInset = 20
256
+ rightInset = -20
257
+ topInset = 12
258
+ bottomInset = -12
259
+ let skeleton = NativeListMarketSkeleton(
260
+ background: nativeListColor(theme, "background", "#FFFFFF"))
261
+ skeleton.translatesAutoresizingMaskIntoConstraints = false
262
+ skeleton.heightAnchor.constraint(equalToConstant: 32).isActive = true
263
+ root.addArrangedSubview(skeleton)
264
+ selectorViews.append(skeleton)
265
+ return
266
+ }
267
+ if variant == "loading" && item.data.string("loadingStyle") == "spinner" {
268
+ topInset = 16
269
+ bottomInset = -16
270
+ root.addArrangedSubview(mainStack)
271
+ mainStack.alignment = .center
272
+ mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
273
+ let indicator = UIActivityIndicatorView(style: .medium)
274
+ indicator.color = nativeListColor(theme, "icon", "#0000009B")
275
+ indicator.startAnimating()
276
+ mainStack.addArrangedSubview(indicator)
277
+ selectorViews.append(indicator)
278
+ return
279
+ }
280
+ if isMarket && variant == "noMatch" {
281
+ let padding = max(32, (CGFloat(item.data.double("height", default: 88)) - 24) / 2)
282
+ topInset = padding
283
+ bottomInset = -padding
284
+ root.addArrangedSubview(mainStack)
285
+ mainStack.alignment = .center
286
+ mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
287
+ titleLabel.font = nativeListTabularFont(ofSize: 16)
288
+ titleLabel.textColor = nativeListColor(theme, "secondaryText", "#646464")
289
+ titleLabel.textAlignment = .center
290
+ show(titleLabel, item.data.string("message"), lines: 1)
291
+ setLineHeight(titleLabel, text: item.data.string("message"), lineHeight: 24)
292
+ return
293
+ }
294
+ if isMarket && variant == "end" {
295
+ topInset = 16
296
+ bottomInset = -16
297
+ // OneKey patch: an empty title stack must not consume the dot's line height.
298
+ titleRowStack.isHidden = true
299
+ root.addArrangedSubview(mainStack)
300
+ mainStack.alignment = .center
301
+ mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
302
+ let indicator = UIStackView()
303
+ indicator.axis = .horizontal
304
+ indicator.alignment = .center
305
+ indicator.spacing = 8
306
+ for width in [CGFloat(80), 4, 80] {
307
+ let mark = UIView()
308
+ mark.translatesAutoresizingMaskIntoConstraints = false
309
+ mark.backgroundColor = nativeListColor(theme, "separator", "#0000001F")
310
+ mark.layer.cornerRadius = width == 4 ? 2 : 0
311
+ NSLayoutConstraint.activate([
312
+ mark.widthAnchor.constraint(equalToConstant: width),
313
+ mark.heightAnchor.constraint(equalToConstant: width == 4 ? 4 : 1),
314
+ ])
315
+ indicator.addArrangedSubview(mark)
316
+ }
317
+ mainStack.addArrangedSubview(indicator)
318
+ selectorViews.append(indicator)
319
+ return
320
+ }
321
+ // OneKey patch: deprecated-wallet warnings stay inside the scrolling list.
322
+ if variant == "warning" {
323
+ root.addArrangedSubview(mainStack)
324
+ topInset = 14
325
+ bottomInset = -14
326
+ mainStack.spacing = 4
327
+ titleLabel.font = nativeListFont(ofSize: 14, weight: .medium)
328
+ titleLabel.numberOfLines = 0
329
+ subtitleLabel.font = nativeListFont(ofSize: 14)
330
+ subtitleLabel.numberOfLines = 0
331
+ show(titleLabel, item.data.string("title"), lines: 0)
332
+ show(subtitleLabel, item.data.string("message"), lines: 0)
333
+ setLineHeight(titleLabel, text: item.data.string("title"), lineHeight: 20)
334
+ setLineHeight(subtitleLabel, text: item.data.string("message"), lineHeight: 20)
335
+ // Legacy applied selector typography (tabular digits, kern 0) to warnings.
336
+ [titleLabel, subtitleLabel].forEach(NativeListTextStyles.applyTabularDigits)
337
+ let borderColor = UIColor(
338
+ nativeListHex: item.data.string("borderColor", default: "#E0E0E0"), fallback: .lightGray)
339
+ for top in [true, false] {
340
+ let border = UIView()
341
+ border.translatesAutoresizingMaskIntoConstraints = false
342
+ border.backgroundColor = borderColor
343
+ contentView.addSubview(border)
344
+ NSLayoutConstraint.activate([
345
+ border.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: -8),
346
+ border.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 8),
347
+ border.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale),
348
+ top
349
+ ? border.topAnchor.constraint(equalTo: contentView.topAnchor)
350
+ : border.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
351
+ ])
352
+ selectorViews.append(border)
353
+ }
354
+ return
355
+ }
356
+ if variant == "loading" {
357
+ leadingWidth.constant = isMarket ? 32 : 40
358
+ leadingHeight.constant = isMarket ? 32 : 40
359
+ leadingContainer.backgroundColor = nativeListColor(theme, "strongBackground", "#F0F0F0")
360
+ leadingContainer.layer.cornerRadius = isMarket ? 16 : 20
361
+ root.addArrangedSubview(leadingContainer)
362
+ configureSkeleton(skeletonPrimary, width: 120, height: 12, theme: theme)
363
+ configureSkeleton(skeletonSecondary, width: 80, height: 12, theme: theme)
364
+ mainStack.spacing = 8
365
+ mainStack.addArrangedSubview(skeletonPrimary)
366
+ mainStack.addArrangedSubview(skeletonSecondary)
367
+ }
368
+ show(
369
+ titleLabel,
370
+ item.data.string("message", default: variant == "end" ? "End" : ""),
371
+ lines: 2
372
+ )
373
+ let alignsToStart = variant == "retry" || variant == "noMatch" || variant == "end"
374
+ titleLabel.textAlignment = alignsToStart ? .natural : .center
375
+ mainStack.alignment = variant == "loading" || alignsToStart ? .leading : .center
376
+ if alignsToStart {
377
+ mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
378
+ titleLabel.font = nativeListFont(ofSize: 14)
379
+ titleLabel.textColor = nativeListColor(theme, "secondaryText", "#646464")
380
+ setLineHeight(titleLabel, text: titleLabel.text ?? "", lineHeight: 20)
381
+ }
382
+ root.addArrangedSubview(mainStack)
383
+ if variant == "retry" {
384
+ showAccessory(0, "Retry", action: (item.data.string("actionKey"), nil))
385
+ accessoryButtons[0].backgroundColor = nativeListColor(theme, "strongBackground", "#F0F0F0")
386
+ accessoryButtons[0].layer.cornerRadius = 14
387
+ accessoryButtons[0].contentEdgeInsets = UIEdgeInsets(top: 4, left: 10, bottom: 4, right: 10)
388
+ setButtonLine(
389
+ accessoryButtons[0],
390
+ text: "Retry",
391
+ font: nativeListFont(ofSize: 14, weight: .medium),
392
+ color: nativeListColor(theme, "primaryText", "#202020"),
393
+ lineHeight: 20
394
+ )
395
+ root.addArrangedSubview(trailingStack)
396
+ }
397
+ }
398
+ private func configureSkeleton(
399
+ _ view: UIView,
400
+ width: CGFloat,
401
+ height: CGFloat,
402
+ theme: [String: Any]?
403
+ ) {
404
+ view.backgroundColor = nativeListColor(theme, "rowPressedBackground", "#E8E8E8")
405
+ view.layer.cornerRadius = 4
406
+ view.translatesAutoresizingMaskIntoConstraints = false
407
+ if view.constraints.isEmpty {
408
+ NSLayoutConstraint.activate([
409
+ view.widthAnchor.constraint(equalToConstant: width),
410
+ view.heightAnchor.constraint(equalToConstant: height),
411
+ ])
412
+ }
413
+ }
414
+ override func layoutSubviews() {
415
+ super.layoutSubviews()
416
+ if let item = currentItem, item.type == "system", item.data.string("presentation") == "market",
417
+ ["noMatch", "retry"].contains(item.data.string("variant")), !titleLabel.isHidden
418
+ {
419
+ titleLabel.transform = .identity
420
+ // Settle each nested stack before measuring; the content view alone can leave stale descendant frames.
421
+ contentView.layoutIfNeeded()
422
+ root.layoutIfNeeded()
423
+ mainStack.layoutIfNeeded()
424
+ titleRowStack.layoutIfNeeded()
425
+ // React Native floors text origins to physical pixels after centering the line box.
426
+ let scale = max(1, window?.screen.scale ?? traitCollection.displayScale)
427
+ let origin = titleLabel.convert(titleLabel.bounds, to: contentView).origin
428
+ let x = floor((contentView.bounds.width - titleLabel.bounds.width) / 2 * scale) / scale
429
+ let contentHeight: CGFloat = item.data.string("variant") == "retry" ? 56 : 24
430
+ let y =
431
+ item.data.dictionary("style")?.dictionary("container")?["contentVerticalAlignment"] != nil
432
+ ? origin.y : floor(max(32, (contentView.bounds.height - contentHeight) / 2) * scale) / scale
433
+ titleLabel.transform = CGAffineTransform(translationX: x - origin.x, y: y - origin.y)
434
+ }
435
+ }
436
+ }
437
+
438
+ // Market/TokenListSkeleton: source geometry and the native Skeleton's 3s shimmer.
439
+ private final class NativeListMarketSkeleton: UIView {
440
+ private let marks = (0..<5).map { _ in UIView() }
441
+ private let gradients = (0..<5).map { _ in CAGradientLayer() }
442
+
443
+ init(background: UIColor) {
444
+ super.init(frame: .zero)
445
+ var white: CGFloat = 1
446
+ background.getWhite(&white, alpha: nil)
447
+ let base = UIColor(nativeListHex: white < 0.5 ? "#111111" : "#FAFAFA", fallback: .white)
448
+ let highlight = UIColor(
449
+ nativeListHex: white < 0.5 ? "#333333" : "#CDCDCD", fallback: .lightGray)
450
+ for (index, mark) in marks.enumerated() {
451
+ mark.backgroundColor = base
452
+ mark.clipsToBounds = true
453
+ mark.layer.cornerRadius = index == 0 ? 16 : 8
454
+ let gradient = gradients[index]
455
+ gradient.cornerRadius = mark.layer.cornerRadius
456
+ gradient.colors = [base.cgColor, highlight.cgColor, base.cgColor]
457
+ gradient.locations = [0, 0.5, 1]
458
+ gradient.startPoint = CGPoint(x: 0, y: 0.5)
459
+ gradient.endPoint = CGPoint(x: 1, y: 0.5)
460
+ mark.layer.addSublayer(gradient)
461
+ addSubview(mark)
462
+ }
463
+ }
464
+
465
+ required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
466
+
467
+ override func layoutSubviews() {
468
+ super.layoutSubviews()
469
+ let frames = [
470
+ CGRect(x: 0, y: 0, width: 32, height: 32),
471
+ CGRect(x: 44, y: 0, width: 80, height: 16),
472
+ CGRect(x: 44, y: 20, width: 60, height: 12),
473
+ CGRect(x: bounds.width - 168, y: 7, width: 80, height: 18),
474
+ CGRect(x: bounds.width - 80, y: 7, width: 80, height: 18),
475
+ ]
476
+ CATransaction.begin()
477
+ CATransaction.setDisableActions(true)
478
+ for (index, frame) in frames.enumerated() {
479
+ marks[index].frame = frame
480
+ gradients[index].frame = marks[index].bounds
481
+ }
482
+ CATransaction.commit()
483
+ updateAnimation()
484
+ }
485
+
486
+ override func didMoveToWindow() {
487
+ super.didMoveToWindow()
488
+ updateAnimation()
489
+ }
490
+
491
+ private func updateAnimation() {
492
+ for gradient in gradients {
493
+ guard window != nil else {
494
+ gradient.removeAllAnimations()
495
+ continue
496
+ }
497
+ guard gradient.bounds.width > 0, gradient.animation(forKey: "shimmer") == nil else {
498
+ continue
499
+ }
500
+ let animation = CABasicAnimation(keyPath: "transform.translation.x")
501
+ animation.fromValue = -gradient.bounds.width
502
+ animation.toValue = gradient.bounds.width
503
+ animation.duration = 3
504
+ animation.repeatCount = .infinity
505
+ animation.timingFunction = CAMediaTimingFunction(name: .linear)
506
+ gradient.add(animation, forKey: "shimmer")
507
+ }
508
+ }
509
+ }
510
+
511
+ enum NativeListSystemRenderer {
512
+ static func appliesSizePreset(_ item: NativeListItem) -> Bool {
513
+ !["warning", "spacer"].contains(item.data.string("variant"))
514
+ }
515
+ static func measure(_ item: NativeListItem, width: CGFloat) -> CGFloat {
516
+ if item.type == "system", item.data.string("variant") == "spacer" {
517
+ return CGFloat(item.data.int("height"))
518
+ }
519
+ // OneKey patch: warning height follows the current native font and available width.
520
+ if item.type == "system", item.data.string("variant") == "warning" {
521
+ let style = item.data.dictionary("style")
522
+ let textWidth = max(
523
+ 1, width - CGFloat(style?.double("horizontalPadding", default: 12) ?? 12) * 2)
524
+ func textHeight(_ key: String, weight: NativeListFontWeight) -> CGFloat {
525
+ let textStyle = style?.dictionary(key)
526
+ let lineHeight = CGFloat(textStyle?.double("lineHeight", default: 20) ?? 20)
527
+ let fontSize = CGFloat(textStyle?.double("fontSize", default: 14) ?? 14)
528
+ let paragraph = NSMutableParagraphStyle()
529
+ paragraph.minimumLineHeight = lineHeight
530
+ paragraph.maximumLineHeight = lineHeight
531
+ let measured = ceil(
532
+ (item.data.string(key) as NSString).boundingRect(
533
+ with: CGSize(width: textWidth, height: .greatestFiniteMagnitude),
534
+ options: [.usesLineFragmentOrigin, .usesFontLeading],
535
+ attributes: [
536
+ .font: nativeListFont(
537
+ ofSize: fontSize,
538
+ weight: NativeListTextStyles.marketFontWeight(
539
+ textStyle?.string("fontWeight") ?? "", fallback: weight)),
540
+ .paragraphStyle: paragraph,
541
+ ], context: nil
542
+ ).height / lineHeight)
543
+ // Legacy: an empty title/message contributes no line.
544
+ guard !item.data.string(key).isEmpty else { return 0 }
545
+ return min(CGFloat(textStyle?.int("lines", default: Int.max) ?? Int.max), max(1, measured))
546
+ * lineHeight
547
+ }
548
+ return CGFloat(style?.double("verticalPadding", default: 14) ?? 14) * 2
549
+ + CGFloat(style?.double("lineGap", default: 4) ?? 4) + textHeight("title", weight: .medium)
550
+ + textHeight("message", weight: .regular)
551
+ }
552
+ if item.data.string("variant") == "loading" && item.data.string("loadingStyle") == "skeleton" {
553
+ return 56
554
+ } else if item.data.string("variant") == "loading"
555
+ && item.data.string("loadingStyle") == "spinner"
556
+ {
557
+ return 52
558
+ } else if item.data.string("presentation") == "market" {
559
+ return item.data.string("variant") == "loading" ? 68 : 44
560
+ } else {
561
+ switch item.data.string("variant") {
562
+ case "noMatch", "end": return 36
563
+ case "retry": return 44
564
+ default: return 56
565
+ }
566
+ }
567
+ }
568
+ }
@@ -0,0 +1,42 @@
1
+ import UIKit
2
+
3
+ class NativeListTextLabel: UILabel {
4
+ var rowVerticalAlignment: String? { didSet { setNeedsDisplay() } }
5
+ var rowOffsetY: CGFloat = 0 { didSet { setNeedsDisplay() } }
6
+
7
+ override func drawText(in rect: CGRect) {
8
+ var target = rect
9
+ if let alignment = rowVerticalAlignment {
10
+ let height = super.textRect(forBounds: rect, limitedToNumberOfLines: numberOfLines).height
11
+ target.origin.y += alignment == "top" ? 0 : alignment == "bottom" ? rect.height - height : (rect.height - height) / 2
12
+ target.size.height = height
13
+ }
14
+ target.origin.y += rowOffsetY
15
+ super.drawText(in: target)
16
+ }
17
+ }
18
+
19
+ final class NativeListInsetLabel: NativeListTextLabel {
20
+ var horizontalInset: CGFloat = 0
21
+ var topInset: CGFloat = 0
22
+ var bottomInset: CGFloat = 0
23
+
24
+ override var intrinsicContentSize: CGSize {
25
+ let size = super.intrinsicContentSize
26
+ return CGSize(
27
+ width: size.width + horizontalInset * 2,
28
+ height: size.height + topInset + bottomInset
29
+ )
30
+ }
31
+
32
+ override func drawText(in rect: CGRect) {
33
+ super.drawText(in: rect.inset(
34
+ by: UIEdgeInsets(
35
+ top: topInset,
36
+ left: horizontalInset,
37
+ bottom: bottomInset,
38
+ right: horizontalInset
39
+ )
40
+ ))
41
+ }
42
+ }