@onekeyfe/react-native-native-list 3.0.113 → 3.0.114

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.
@@ -4,73 +4,6 @@ import CoreText
4
4
  import OneKeyImage
5
5
  import UIKit
6
6
 
7
- // Market/TokenListSkeleton: source geometry and the native Skeleton's 3s shimmer.
8
- private final class NativeListMarketSkeleton: UIView {
9
- private let marks = (0..<5).map { _ in UIView() }
10
- private let gradients = (0..<5).map { _ in CAGradientLayer() }
11
-
12
- init(background: UIColor) {
13
- super.init(frame: .zero)
14
- var white: CGFloat = 1
15
- background.getWhite(&white, alpha: nil)
16
- let base = UIColor(nativeListHex: white < 0.5 ? "#111111" : "#FAFAFA", fallback: .white)
17
- let highlight = UIColor(nativeListHex: white < 0.5 ? "#333333" : "#CDCDCD", fallback: .lightGray)
18
- for (index, mark) in marks.enumerated() {
19
- mark.backgroundColor = base
20
- mark.clipsToBounds = true
21
- mark.layer.cornerRadius = index == 0 ? 16 : 8
22
- let gradient = gradients[index]
23
- gradient.cornerRadius = mark.layer.cornerRadius
24
- gradient.colors = [base.cgColor, highlight.cgColor, base.cgColor]
25
- gradient.locations = [0, 0.5, 1]
26
- gradient.startPoint = CGPoint(x: 0, y: 0.5)
27
- gradient.endPoint = CGPoint(x: 1, y: 0.5)
28
- mark.layer.addSublayer(gradient)
29
- addSubview(mark)
30
- }
31
- }
32
-
33
- required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
34
-
35
- override func layoutSubviews() {
36
- super.layoutSubviews()
37
- let frames = [
38
- CGRect(x: 0, y: 0, width: 32, height: 32),
39
- CGRect(x: 44, y: 0, width: 80, height: 16),
40
- CGRect(x: 44, y: 20, width: 60, height: 12),
41
- CGRect(x: bounds.width - 168, y: 7, width: 80, height: 18),
42
- CGRect(x: bounds.width - 80, y: 7, width: 80, height: 18),
43
- ]
44
- CATransaction.begin()
45
- CATransaction.setDisableActions(true)
46
- for (index, frame) in frames.enumerated() {
47
- marks[index].frame = frame
48
- gradients[index].frame = marks[index].bounds
49
- }
50
- CATransaction.commit()
51
- updateAnimation()
52
- }
53
-
54
- override func didMoveToWindow() {
55
- super.didMoveToWindow()
56
- updateAnimation()
57
- }
58
-
59
- private func updateAnimation() {
60
- for gradient in gradients {
61
- guard window != nil else { gradient.removeAllAnimations(); continue }
62
- guard gradient.bounds.width > 0, gradient.animation(forKey: "shimmer") == nil else { continue }
63
- let animation = CABasicAnimation(keyPath: "transform.translation.x")
64
- animation.fromValue = -gradient.bounds.width
65
- animation.toValue = gradient.bounds.width
66
- animation.duration = 3
67
- animation.repeatCount = .infinity
68
- animation.timingFunction = CAMediaTimingFunction(name: .linear)
69
- gradient.add(animation, forKey: "shimmer")
70
- }
71
- }
72
- }
73
-
74
7
  final class NativeListActionOrigin {
75
8
  weak var sourceView: UIView?
76
9
  weak var ownerCell: NativeListCell?
@@ -103,17 +36,13 @@ private final class NativeListAccessoryButton: UIButton {
103
36
  didSet { invalidateIntrinsicContentSize(); setNeedsLayout() }
104
37
  }
105
38
 
106
- var marketLineHeight: CGFloat? {
107
- didSet { invalidateIntrinsicContentSize(); setNeedsLayout() }
108
- }
109
-
110
39
  private var sourcePixelScale: CGFloat {
111
40
  max(1, window?.screen.scale ?? traitCollection.displayScale)
112
41
  }
113
42
 
114
43
  override var intrinsicContentSize: CGSize {
115
44
  var size = super.intrinsicContentSize
116
- guard selectorSummaryLineHeight != nil || marketLineHeight != nil, let title = attributedTitle(for: .normal) else { return size }
45
+ guard selectorSummaryLineHeight != nil, let title = attributedTitle(for: .normal) else { return size }
117
46
  let width = title.boundingRect(
118
47
  with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude),
119
48
  options: [.usesLineFragmentOrigin, .usesFontLeading],
@@ -125,21 +54,6 @@ private final class NativeListAccessoryButton: UIButton {
125
54
 
126
55
  override func layoutSubviews() {
127
56
  super.layoutSubviews()
128
- if let lineHeight = marketLineHeight, let titleLabel, let title = attributedTitle(for: .normal) {
129
- let measured = title.boundingRect(
130
- with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude),
131
- options: [.usesLineFragmentOrigin, .usesFontLeading], context: nil
132
- ).width
133
- let width = min(bounds.width, ceil(measured * sourcePixelScale) / sourcePixelScale)
134
- let x = contentHorizontalAlignment == .trailing ? bounds.width - width
135
- : contentHorizontalAlignment == .leading ? 0 : (bounds.width - width) / 2
136
- titleLabel.frame = CGRect(
137
- x: floor(x * sourcePixelScale) / sourcePixelScale,
138
- y: (bounds.height - lineHeight) / 2,
139
- width: width, height: lineHeight
140
- )
141
- return
142
- }
143
57
  guard let lineHeight = selectorSummaryLineHeight, let titleLabel else { return }
144
58
  // OneKey patch: position the final source line box after UIKit has measured the button.
145
59
  let top = ceil((bounds.height - lineHeight) / 2 * sourcePixelScale) / sourcePixelScale
@@ -448,8 +362,6 @@ final class NativeListCell: UICollectionViewCell {
448
362
  private let metricSubtitleLabel = UILabel()
449
363
  private let metricCompositeStack = UIStackView()
450
364
  private let badgeLabel = NativeListInsetLabel()
451
- private let marketBadgeButtons = (0..<3).map { _ in UIButton(type: .system) }
452
- private let marketBadgeImages = (0..<3).map { _ in OneKeyImageReusableView(frame: .zero) }
453
365
  private let actionStack = UIStackView()
454
366
  private let actionButtons = (0..<3).map { _ in UIButton(type: .system) }
455
367
  private let trailingStack = UIStackView()
@@ -500,7 +412,6 @@ final class NativeListCell: UICollectionViewCell {
500
412
  private var boundCheckboxData: [String: Any]?
501
413
  private var boundCheckboxTarget: NativeSelectionTarget?
502
414
  private var leadingActionKey: String?
503
- private var marketBadgeActionKeys: [String?] = []
504
415
  private var restingBackgroundColor: UIColor = .clear
505
416
  private var pressedBackgroundColor = UIColor(
506
417
  nativeListHex: "#E8E8E8",
@@ -701,23 +612,6 @@ final class NativeListCell: UICollectionViewCell {
701
612
  titleRowStack.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
702
613
  titleRowStack.addArrangedSubview(titleLabel)
703
614
  titleRowStack.addArrangedSubview(badgeLabel)
704
- marketBadgeButtons.enumerated().forEach { index, button in
705
- button.tag = index
706
- button.addTarget(self, action: #selector(marketBadgePressed(_:)), for: .touchUpInside)
707
- button.titleLabel?.font = nativeListFont(ofSize: 11, weight: .medium)
708
- button.layer.cornerRadius = 4
709
- button.clipsToBounds = true
710
- let image = marketBadgeImages[index]
711
- image.isUserInteractionEnabled = false
712
- image.translatesAutoresizingMaskIntoConstraints = false
713
- button.addSubview(image)
714
- NSLayoutConstraint.activate([
715
- image.centerYAnchor.constraint(equalTo: button.centerYAnchor),
716
- image.widthAnchor.constraint(equalToConstant: 14),
717
- image.heightAnchor.constraint(equalToConstant: 14),
718
- ])
719
- titleRowStack.addArrangedSubview(button)
720
- }
721
615
  titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
722
616
  titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
723
617
  badgeLabel.setContentHuggingPriority(.required, for: .horizontal)
@@ -949,7 +843,6 @@ final class NativeListCell: UICollectionViewCell {
949
843
  case "activity": bindActivity(item, theme: theme)
950
844
  case "message": bindMessage(item, theme: theme)
951
845
  case "dataRow": bindDataRow(item, theme: theme, checkboxState)
952
- case "market": bindMarket(item, theme: theme)
953
846
  case "mediaTile": bindMediaTile(item, theme: theme)
954
847
  case "metricCard": bindMetricCard(item, theme: theme)
955
848
  case "sectionHeader": bindSectionHeader(item, theme: theme, layout: layout, checkboxState)
@@ -1148,7 +1041,6 @@ final class NativeListCell: UICollectionViewCell {
1148
1041
  selectorConstraints.removeAll()
1149
1042
  selectorImages.forEach { $0.prepareForReuse() }
1150
1043
  selectorImages.removeAll()
1151
- marketBadgeImages.forEach { $0.prepareForReuse(); $0.isHidden = true }
1152
1044
  selectorFullWidthBackground.removeFromSuperlayer()
1153
1045
  selectorBorder?.removeFromSuperlayer()
1154
1046
  selectorBorder = nil
@@ -1231,7 +1123,6 @@ final class NativeListCell: UICollectionViewCell {
1231
1123
  $0.isHidden = true
1232
1124
  $0.alpha = 1
1233
1125
  $0.layer.cornerRadius = 0
1234
- $0.layer.mask = nil
1235
1126
  }
1236
1127
  leadingContainer.clipsToBounds = true
1237
1128
  leadingContainer.layer.borderWidth = 0
@@ -1287,19 +1178,6 @@ final class NativeListCell: UICollectionViewCell {
1287
1178
  badgeLabel.backgroundColor = .clear
1288
1179
  badgeLabel.layer.cornerRadius = 0
1289
1180
  badgeLabel.clipsToBounds = false
1290
- marketBadgeButtons.forEach {
1291
- $0.isHidden = true
1292
- $0.isEnabled = true
1293
- $0.isUserInteractionEnabled = false
1294
- $0.setTitle(nil, for: .normal)
1295
- $0.setImage(nil, for: .normal)
1296
- $0.setTitleColor(nil, for: .normal)
1297
- $0.tintColor = nil
1298
- $0.backgroundColor = .clear
1299
- $0.contentEdgeInsets = .zero
1300
- $0.imageEdgeInsets = .zero
1301
- $0.titleEdgeInsets = .zero
1302
- }
1303
1181
  [titleLabel, subtitleLabel, tertiaryLabel, statusLabel, metricSubtitleLabel, badgeLabel, actionStack]
1304
1182
  .forEach { $0.isHidden = true }
1305
1183
  separatorView.isHidden = true
@@ -1310,9 +1188,7 @@ final class NativeListCell: UICollectionViewCell {
1310
1188
  $0.backgroundColor = .clear
1311
1189
  }
1312
1190
  accessoryButtons.enumerated().forEach { index, button in
1313
- button.isUserInteractionEnabled = true
1314
1191
  button.selectorSummaryLineHeight = nil
1315
- button.marketLineHeight = nil
1316
1192
  button.titleLabel?.font = nativeListFont(
1317
1193
  ofSize: index == 0 ? 16 : 14,
1318
1194
  weight: index == 0 ? .medium : .regular
@@ -1357,7 +1233,6 @@ final class NativeListCell: UICollectionViewCell {
1357
1233
  boundCheckboxData = nil
1358
1234
  boundCheckboxTarget = nil
1359
1235
  leadingActionKey = nil
1360
- marketBadgeActionKeys = []
1361
1236
  layer.maskedCorners = []
1362
1237
  layer.cornerRadius = 0
1363
1238
  layer.cornerCurve = .circular
@@ -2139,281 +2014,6 @@ final class NativeListCell: UICollectionViewCell {
2139
2014
  }
2140
2015
  }
2141
2016
 
2142
- private func marketFontWeight(_ value: String, fallback: NativeListFontWeight) -> NativeListFontWeight {
2143
- switch value {
2144
- case "regular": return .regular
2145
- case "semibold": return .semibold
2146
- case "bold": return .bold
2147
- case "medium": return .medium
2148
- default: return fallback
2149
- }
2150
- }
2151
-
2152
- private func marketTextAlignment(_ value: String) -> NSTextAlignment {
2153
- switch value {
2154
- case "center": return .center
2155
- case "start": return effectiveUserInterfaceLayoutDirection == .rightToLeft ? .right : .left
2156
- case "end": return effectiveUserInterfaceLayoutDirection == .rightToLeft ? .left : .right
2157
- default: return .natural
2158
- }
2159
- }
2160
-
2161
- private func applyMarketTextStyle(
2162
- _ label: UILabel,
2163
- data: [String: Any]?,
2164
- theme: [String: Any]?,
2165
- defaultSize: CGFloat,
2166
- defaultLineHeight: CGFloat,
2167
- defaultWeight: NativeListFontWeight,
2168
- defaultColor: UIColor,
2169
- defaultAlignment: NSTextAlignment = .natural
2170
- ) {
2171
- let size = CGFloat(data?.double("fontSize", default: Double(defaultSize)) ?? Double(defaultSize))
2172
- let lineHeight = CGFloat(data?.double("lineHeight", default: Double(defaultLineHeight)) ?? Double(defaultLineHeight))
2173
- label.font = nativeListTabularFont(ofSize: size, weight: marketFontWeight(data?.string("fontWeight") ?? "", fallback: defaultWeight))
2174
- label.textColor = data?["color"].flatMap { $0 as? String }.map { UIColor(nativeListHex: $0, fallback: defaultColor) } ?? defaultColor
2175
- label.textAlignment = data?["alignment"] == nil
2176
- ? defaultAlignment
2177
- : marketTextAlignment(data?.string("alignment") ?? "")
2178
- label.numberOfLines = min(2, max(1, data?.int("lines", default: 1) ?? 1))
2179
- setLineHeight(label, text: label.text ?? "", lineHeight: lineHeight)
2180
- }
2181
-
2182
- private func applyMarketButtonStyle(
2183
- _ button: UIButton,
2184
- data: [String: Any]?,
2185
- defaultSize: CGFloat,
2186
- defaultLineHeight: CGFloat,
2187
- defaultWeight: NativeListFontWeight,
2188
- color: UIColor,
2189
- defaultAlignment: UIControl.ContentHorizontalAlignment
2190
- ) {
2191
- let size = CGFloat(data?.double("fontSize", default: Double(defaultSize)) ?? Double(defaultSize))
2192
- let lineHeight = CGFloat(data?.double("lineHeight", default: Double(defaultLineHeight)) ?? Double(defaultLineHeight))
2193
- setButtonLine(
2194
- button,
2195
- text: button.title(for: .normal) ?? "",
2196
- font: nativeListTabularFont(ofSize: size, weight: marketFontWeight(data?.string("fontWeight") ?? "", fallback: defaultWeight)),
2197
- color: data?["color"].flatMap { $0 as? String }.map { UIColor(nativeListHex: $0, fallback: color) } ?? color,
2198
- lineHeight: lineHeight
2199
- )
2200
- if data?["alignment"] == nil {
2201
- button.contentHorizontalAlignment = defaultAlignment
2202
- } else {
2203
- button.contentHorizontalAlignment = data?.string("alignment") == "start"
2204
- ? .leading
2205
- : data?.string("alignment") == "end" ? .trailing : .center
2206
- }
2207
- button.titleLabel?.numberOfLines = min(2, max(1, data?.int("lines", default: 1) ?? 1))
2208
- }
2209
-
2210
- private func marketAttributedText(
2211
- _ text: String,
2212
- segments: [[String: Any]],
2213
- style: [String: Any]?,
2214
- defaultSize: CGFloat,
2215
- defaultLineHeight: CGFloat,
2216
- defaultWeight: NativeListFontWeight,
2217
- color: UIColor,
2218
- defaultAlignment: NSTextAlignment
2219
- ) -> NSAttributedString {
2220
- let size = CGFloat(style?.double("fontSize", default: Double(defaultSize)) ?? Double(defaultSize))
2221
- let lineHeight = CGFloat(style?.double("lineHeight", default: Double(defaultLineHeight)) ?? Double(defaultLineHeight))
2222
- let paragraph = NSMutableParagraphStyle()
2223
- paragraph.minimumLineHeight = lineHeight
2224
- paragraph.maximumLineHeight = lineHeight
2225
- paragraph.alignment = style?["alignment"] == nil
2226
- ? defaultAlignment
2227
- : marketTextAlignment(style?.string("alignment") ?? "")
2228
- let weight = marketFontWeight(style?.string("fontWeight") ?? "", fallback: defaultWeight)
2229
- let resolvedColor = style?["color"].flatMap { $0 as? String }.map { UIColor(nativeListHex: $0, fallback: color) } ?? color
2230
- let result = NSMutableAttributedString(string: "")
2231
- let source = segments.isEmpty ? [["text": text]] : segments
2232
- for segment in source {
2233
- let segmentSize = segment.string("style") == "subscript" ? ceil(size * 0.6) : size
2234
- result.append(NSAttributedString(string: segment.string("text"), attributes: [
2235
- .font: nativeListTabularFont(ofSize: segmentSize, weight: weight),
2236
- .foregroundColor: resolvedColor,
2237
- .kern: 0,
2238
- .paragraphStyle: paragraph,
2239
- .baselineOffset: max(0, (lineHeight - nativeListTabularFont(ofSize: size, weight: weight).lineHeight) / 2),
2240
- ]))
2241
- }
2242
- return result
2243
- }
2244
-
2245
- private func marketLeading(_ item: NativeListItem, style: [String: Any]?) -> [String: Any]? {
2246
- guard var visual = item.data.dictionary("leading") else { return nil }
2247
- guard let imageStyle = style?.dictionary("image") else { return visual }
2248
- if let shape = imageStyle["shape"] as? String { visual["shape"] = shape }
2249
- if let contentFit = imageStyle["contentFit"] as? String, var image = visual.dictionary("image") {
2250
- image["contentFit"] = contentFit
2251
- visual["image"] = image
2252
- }
2253
- return visual
2254
- }
2255
-
2256
- private func bindMarket(_ item: NativeListItem, theme: [String: Any]?) {
2257
- let variant = item.data.string("variant")
2258
- let style = item.data.dictionary("style")
2259
- let imageStyle = style?.dictionary("image")
2260
- let imageWidth = CGFloat(imageStyle?.double("width", default: variant == "stock" ? 40 : 32) ?? (variant == "stock" ? 40 : 32))
2261
- let imageHeight = CGFloat(imageStyle?.double("height", default: variant == "stock" ? 40 : 32) ?? (variant == "stock" ? 40 : 32))
2262
- let horizontalPadding = CGFloat(style?.double("horizontalPadding", default: variant == "perp" ? 16 : 20) ?? (variant == "perp" ? 16 : 20))
2263
- let verticalPadding = CGFloat(style?.double("verticalPadding", default: 12) ?? 12)
2264
- rootLeadingConstraint.constant = horizontalPadding
2265
- rootTrailingConstraint.constant = -horizontalPadding
2266
- rootTopConstraint.constant = verticalPadding
2267
- rootBottomConstraint.constant = -verticalPadding
2268
- rootStack.spacing = CGFloat(style?.double("leadingGap", default: variant == "perp" ? 8 : 14) ?? (variant == "perp" ? 8 : 14))
2269
- leadingWidth.constant = imageWidth
2270
- leadingHeight.constant = imageHeight
2271
- if let visual = marketLeading(item, style: style) {
2272
- addLeading(visual, key: item.key)
2273
- let radius = CGFloat(imageStyle?.double("cornerRadius", default: imageStyle?.string("shape") == "square" ? 0 : imageStyle?.string("shape") == "rounded" ? 8 : Double(min(imageWidth, imageHeight) / 2)) ?? Double(min(imageWidth, imageHeight) / 2))
2274
- leadingContainer.layer.cornerRadius = radius
2275
- leadingImages.first?.layer.cornerRadius = radius
2276
- if let image = leadingImages.first, !visual.string("borderColor").isEmpty {
2277
- // Match Token's border box: the bitmap occupies the one-point inset.
2278
- leadingContainer.layer.borderWidth = 1
2279
- leadingContainer.layer.borderColor = UIColor(nativeListHex: visual.string("borderColor"), fallback: .clear).cgColor
2280
- for constraint in leadingSlotConstraints where constraint.firstItem === image {
2281
- switch constraint.firstAttribute {
2282
- case .leading, .top: constraint.constant = 1
2283
- case .trailing, .bottom: constraint.constant = -1
2284
- default: break
2285
- }
2286
- }
2287
- image.layer.cornerRadius = 0
2288
- let mask = CAShapeLayer()
2289
- mask.path = UIBezierPath(roundedRect: CGRect(x: -1, y: -1, width: imageWidth, height: imageHeight), cornerRadius: radius).cgPath
2290
- image.layer.mask = mask
2291
- }
2292
- if let diagnostic = item.data.dictionary("diagnostics")?.string("imageBindActionKey"), !diagnostic.isEmpty,
2293
- visual.dictionary("image") != nil || visual.dictionary("networkImage") != nil {
2294
- onAction?(item, diagnostic, nil, nil)
2295
- }
2296
- }
2297
- rootStack.addArrangedSubview(mainStack)
2298
- rootStack.setCustomSpacing(0, after: mainStack)
2299
- mainStack.spacing = CGFloat(style?.double("lineGap", default: 0) ?? 0)
2300
- titleRowStack.spacing = CGFloat(style?.double("titleBadgeGap", default: 4) ?? 4)
2301
- show(titleLabel, item.data.string("title"), lines: style?.dictionary("title")?.int("lines", default: 1) ?? 1)
2302
- applyMarketTextStyle(titleLabel, data: style?.dictionary("title"), theme: theme, defaultSize: 16, defaultLineHeight: 24, defaultWeight: .medium, defaultColor: nativeListColor(theme, "primaryText", "#202020"))
2303
- let badges = Array(item.data.dictionaries("badges").prefix(marketBadgeButtons.count))
2304
- marketBadgeActionKeys = badges.map { $0["actionKey"] as? String }
2305
- for (index, badge) in badges.enumerated() {
2306
- let button = marketBadgeButtons[index]
2307
- let hasBuiltInIcon = badge.string("iconName") == "verified"
2308
- let hasRemoteIcon = badge.dictionary("icon") != nil
2309
- let hasIcon = hasBuiltInIcon || hasRemoteIcon
2310
- let text = badge.string("text")
2311
- let toneColor: UIColor
2312
- switch badge.string("tone") {
2313
- case "success": toneColor = nativeListColor(theme, "positive", "#218358")
2314
- case "danger": toneColor = nativeListColor(theme, "negative", "#CE2C31")
2315
- case "info": toneColor = nativeListColor(theme, "info", "#0D74CE")
2316
- case "warning": toneColor = nativeListColor(theme, "primaryText", "#202020")
2317
- default: toneColor = nativeListColor(theme, "secondaryText", "#646464")
2318
- }
2319
- let foreground = UIColor(
2320
- nativeListHex: badge.string("textColor", default: ""),
2321
- fallback: toneColor
2322
- )
2323
- button.isHidden = false
2324
- button.isEnabled = true
2325
- button.isUserInteractionEnabled = !badge.string("actionKey").isEmpty
2326
- button.accessibilityTraits = button.isUserInteractionEnabled ? .button : .staticText
2327
- button.setTitle(text, for: .normal)
2328
- button.setTitleColor(foreground, for: .normal)
2329
- button.tintColor = foreground
2330
- button.backgroundColor = UIColor(
2331
- nativeListHex: badge.string("backgroundColor", default: ""),
2332
- fallback: hasIcon && text.isEmpty
2333
- ? .clear
2334
- : nativeListColor(theme, "strongBackground", "#0000000F")
2335
- )
2336
- let iconOnly = hasIcon && text.isEmpty
2337
- let iconSize: CGFloat = hasBuiltInIcon ? 16 : 14
2338
- button.contentEdgeInsets = UIEdgeInsets(top: 0, left: iconOnly ? 0 : hasRemoteIcon ? 20 : hasIcon ? 3 : 5, bottom: 0, right: iconOnly ? 0 : 5)
2339
- button.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: text.isEmpty ? 0 : 3)
2340
- button.titleEdgeInsets = .zero
2341
- button.imageView?.contentMode = .scaleAspectFit
2342
- if hasBuiltInIcon {
2343
- button.setImage(
2344
- nativeListIcon(named: "BadgeVerifiedSolid", size: CGSize(width: iconSize, height: iconSize)),
2345
- for: .normal
2346
- )
2347
- }
2348
- let height = button.heightAnchor.constraint(equalToConstant: 18)
2349
- let textWidth = (text as NSString).size(
2350
- withAttributes: [.font: nativeListFont(ofSize: 11, weight: .medium)]
2351
- ).width
2352
- let width = button.widthAnchor.constraint(equalToConstant: iconOnly ? iconSize : ceil(textWidth) + (hasIcon ? iconSize + 11 : 10))
2353
- NSLayoutConstraint.activate([width, height])
2354
- selectorConstraints.append(contentsOf: [width, height])
2355
- if let icon = badge.dictionary("icon") {
2356
- marketBadgeImages[index].isHidden = false
2357
- marketBadgeImages[index].layer.cornerRadius = 7
2358
- marketBadgeImages[index].clipsToBounds = true
2359
- let imageLeading = marketBadgeImages[index].leadingAnchor.constraint(equalTo: button.leadingAnchor, constant: text.isEmpty ? 0 : 4)
2360
- imageLeading.isActive = true
2361
- selectorConstraints.append(imageLeading)
2362
- bindImage(icon, into: marketBadgeImages[index], token: item.key, slot: 20 + index, variant: "generic")
2363
- }
2364
- button.accessibilityLabel = badge.string("accessibilityLabel", default: badge.string("text"))
2365
- }
2366
- if !item.data.string("subtitle").isEmpty || !item.data.dictionaries("subtitleSegments").isEmpty {
2367
- show(subtitleLabel, item.data.string("subtitle"), lines: style?.dictionary("subtitle")?.int("lines", default: 1) ?? 1)
2368
- applyMarketTextStyle(subtitleLabel, data: style?.dictionary("subtitle"), theme: theme, defaultSize: 14, defaultLineHeight: 20, defaultWeight: .regular, defaultColor: nativeListColor(theme, "secondaryText", "#646464"))
2369
- if !item.data.dictionaries("subtitleSegments").isEmpty {
2370
- subtitleLabel.attributedText = marketAttributedText(item.data.string("subtitle"), segments: item.data.dictionaries("subtitleSegments"), style: style?.dictionary("subtitle"), defaultSize: 14, defaultLineHeight: 20, defaultWeight: .regular, color: nativeListColor(theme, "secondaryText", "#646464"), defaultAlignment: .natural)
2371
- }
2372
- }
2373
- rootStack.addArrangedSubview(trailingStack)
2374
- trailingStack.axis = .horizontal
2375
- trailingStack.alignment = .center
2376
- trailingStack.spacing = CGFloat(style?.double("trailingGap", default: 8) ?? 8)
2377
- updateMarketQuote(item, theme: theme)
2378
- }
2379
-
2380
- func updateMarketQuote(_ item: NativeListItem, theme: [String: Any]?) {
2381
- guard currentItem?.key == item.key, item.type == "market" else { return }
2382
- currentItem = item
2383
- NSLayoutConstraint.deactivate(accessorySizeConstraints)
2384
- accessorySizeConstraints.removeAll()
2385
- let style = item.data.dictionary("style")
2386
- let price = accessoryButtons[0]
2387
- price.isUserInteractionEnabled = false
2388
- price.isHidden = false
2389
- price.setTitle(item.data.string("price"), for: .normal)
2390
- applyMarketButtonStyle(price, data: style?.dictionary("price"), defaultSize: 16, defaultLineHeight: 24, defaultWeight: .medium, color: nativeListColor(theme, "primaryText", "#202020"), defaultAlignment: .trailing)
2391
- if !item.data.dictionaries("priceSegments").isEmpty {
2392
- price.setAttributedTitle(marketAttributedText(item.data.string("price"), segments: item.data.dictionaries("priceSegments"), style: style?.dictionary("price"), defaultSize: 16, defaultLineHeight: 24, defaultWeight: .medium, color: nativeListColor(theme, "primaryText", "#202020"), defaultAlignment: marketTextAlignment("end")), for: .normal)
2393
- }
2394
- let changeData = item.data.dictionary("change") ?? [:]
2395
- let change = accessoryButtons[1]
2396
- change.isUserInteractionEnabled = false
2397
- change.isHidden = false
2398
- change.setTitle(changeData.string("text"), for: .normal)
2399
- let defaultChangeColor = nativeListColor(theme, "inverseText", "#FFFFFF")
2400
- applyMarketButtonStyle(change, data: style?.dictionary("change"), defaultSize: 14, defaultLineHeight: 20, defaultWeight: .medium, color: UIColor(nativeListHex: changeData.string("textColor", default: "#FFFFFF"), fallback: defaultChangeColor), defaultAlignment: .center)
2401
- if !changeData.dictionaries("textSegments").isEmpty {
2402
- let changeTextColor = UIColor(nativeListHex: changeData.string("textColor", default: ""), fallback: defaultChangeColor)
2403
- change.setAttributedTitle(marketAttributedText(changeData.string("text"), segments: changeData.dictionaries("textSegments"), style: style?.dictionary("change"), defaultSize: 14, defaultLineHeight: 20, defaultWeight: .medium, color: changeTextColor, defaultAlignment: .center), for: .normal)
2404
- }
2405
- let toneKey = changeData.string("tone") == "positive" ? "positive" : changeData.string("tone") == "negative" ? "negative" : "secondaryText"
2406
- change.backgroundColor = UIColor(nativeListHex: changeData.string("backgroundColor", default: ""), fallback: nativeListColor(theme, toneKey, changeData.string("tone") == "positive" ? "#218358" : changeData.string("tone") == "negative" ? "#CE2C31" : "#8D8D8D"))
2407
- change.layer.cornerRadius = CGFloat(style?.double("changeCornerRadius", default: 8) ?? 8)
2408
- change.clipsToBounds = true
2409
- let width = change.widthAnchor.constraint(equalToConstant: CGFloat(style?.double("changeWidth", default: 80) ?? 80))
2410
- let height = change.heightAnchor.constraint(equalToConstant: CGFloat(style?.double("changeHeight", default: 32) ?? 32))
2411
- width.isActive = true
2412
- height.isActive = true
2413
- accessorySizeConstraints.append(contentsOf: [width, height])
2414
- accessibilityLabel = item.data.string("accessibilityLabel", default: [item.data.string("title"), item.data.string("subtitle"), item.data.string("price"), changeData.string("text")].filter { !$0.isEmpty }.joined(separator: ", "))
2415
- }
2416
-
2417
2017
  private func bindMediaTile(_ item: NativeListItem, theme: [String: Any]?) {
2418
2018
  rootStack.axis = .vertical
2419
2019
  rootStack.alignment = .fill
@@ -3030,76 +2630,6 @@ final class NativeListCell: UICollectionViewCell {
3030
2630
  rootStack.alignment = .center
3031
2631
  rootStack.distribution = .fill
3032
2632
  let variant = item.data.string("variant")
3033
- let isMarket = item.data.string("presentation") == "market"
3034
- if isMarket {
3035
- rootLeadingConstraint.constant = 20
3036
- rootTrailingConstraint.constant = -20
3037
- rootTopConstraint.constant = 12
3038
- rootBottomConstraint.constant = -12
3039
- }
3040
- if variant == "loading" && item.data.string("loadingStyle") == "skeleton" {
3041
- rootLeadingConstraint.constant = 20
3042
- rootTrailingConstraint.constant = -20
3043
- rootTopConstraint.constant = 12
3044
- rootBottomConstraint.constant = -12
3045
- let skeleton = NativeListMarketSkeleton(background: nativeListColor(theme, "background", "#FFFFFF"))
3046
- skeleton.translatesAutoresizingMaskIntoConstraints = false
3047
- skeleton.heightAnchor.constraint(equalToConstant: 32).isActive = true
3048
- rootStack.addArrangedSubview(skeleton)
3049
- selectorViews.append(skeleton)
3050
- return
3051
- }
3052
- if variant == "loading" && item.data.string("loadingStyle") == "spinner" {
3053
- rootTopConstraint.constant = 16
3054
- rootBottomConstraint.constant = -16
3055
- rootStack.addArrangedSubview(mainStack)
3056
- mainStack.alignment = .center
3057
- mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
3058
- let indicator = UIActivityIndicatorView(style: .medium)
3059
- indicator.color = nativeListColor(theme, "icon", "#0000009B")
3060
- indicator.startAnimating()
3061
- mainStack.addArrangedSubview(indicator)
3062
- selectorViews.append(indicator)
3063
- return
3064
- }
3065
- if isMarket && variant == "noMatch" {
3066
- rootTopConstraint.constant = 32
3067
- rootBottomConstraint.constant = -32
3068
- rootStack.addArrangedSubview(mainStack)
3069
- mainStack.alignment = .center
3070
- mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
3071
- titleLabel.font = nativeListFont(ofSize: 16)
3072
- titleLabel.textColor = nativeListColor(theme, "secondaryText", "#646464")
3073
- titleLabel.textAlignment = .center
3074
- show(titleLabel, item.data.string("message"), lines: 1)
3075
- setLineHeight(titleLabel, text: item.data.string("message"), lineHeight: 24)
3076
- return
3077
- }
3078
- if isMarket && variant == "end" {
3079
- rootTopConstraint.constant = 16
3080
- rootBottomConstraint.constant = -16
3081
- rootStack.addArrangedSubview(mainStack)
3082
- mainStack.alignment = .center
3083
- mainStack.setContentHuggingPriority(.defaultLow, for: .horizontal)
3084
- let indicator = UIStackView()
3085
- indicator.axis = .horizontal
3086
- indicator.alignment = .center
3087
- indicator.spacing = 8
3088
- for width in [CGFloat(80), 4, 80] {
3089
- let mark = UIView()
3090
- mark.translatesAutoresizingMaskIntoConstraints = false
3091
- mark.backgroundColor = nativeListColor(theme, "separator", "#0000001F")
3092
- mark.layer.cornerRadius = width == 4 ? 2 : 0
3093
- NSLayoutConstraint.activate([
3094
- mark.widthAnchor.constraint(equalToConstant: width),
3095
- mark.heightAnchor.constraint(equalToConstant: width == 4 ? 4 : 1),
3096
- ])
3097
- indicator.addArrangedSubview(mark)
3098
- }
3099
- mainStack.addArrangedSubview(indicator)
3100
- selectorViews.append(indicator)
3101
- return
3102
- }
3103
2633
  // OneKey patch: deprecated-wallet warnings stay inside the scrolling list.
3104
2634
  if variant == "warning" {
3105
2635
  rootStack.addArrangedSubview(mainStack)
@@ -3131,10 +2661,10 @@ final class NativeListCell: UICollectionViewCell {
3131
2661
  return
3132
2662
  }
3133
2663
  if variant == "loading" {
3134
- leadingWidth.constant = isMarket ? 32 : 40
3135
- leadingHeight.constant = isMarket ? 32 : 40
2664
+ leadingWidth.constant = 40
2665
+ leadingHeight.constant = 40
3136
2666
  leadingContainer.backgroundColor = nativeListColor(theme, "strongBackground", "#F0F0F0")
3137
- leadingContainer.layer.cornerRadius = isMarket ? 16 : 20
2667
+ leadingContainer.layer.cornerRadius = 20
3138
2668
  rootStack.addArrangedSubview(leadingContainer)
3139
2669
  configureSkeleton(skeletonPrimary, width: 120, height: 12, theme: theme)
3140
2670
  configureSkeleton(skeletonSecondary, width: 80, height: 12, theme: theme)
@@ -3546,8 +3076,8 @@ final class NativeListCell: UICollectionViewCell {
3546
3076
  let paragraphStyle = NSMutableParagraphStyle()
3547
3077
  paragraphStyle.minimumLineHeight = lineHeight
3548
3078
  paragraphStyle.maximumLineHeight = lineHeight
3549
- if currentItem?.type == "market" || currentItem?.data.string("presentation") == "walletSidebar" {
3550
- // Attributed paragraphs must preserve the label alignment and tail ellipsis.
3079
+ if currentItem?.data.string("presentation") == "walletSidebar" {
3080
+ // OneKey patch: attributed paragraphs must preserve wallet name alignment and tail ellipsis.
3551
3081
  paragraphStyle.alignment = label.textAlignment
3552
3082
  paragraphStyle.lineBreakMode = label.lineBreakMode
3553
3083
  }
@@ -3556,15 +3086,15 @@ final class NativeListCell: UICollectionViewCell {
3556
3086
  .foregroundColor: label.textColor as Any,
3557
3087
  .paragraphStyle: paragraphStyle,
3558
3088
  ]
3559
- if currentItem?.type == "market" || (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" {
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" {
3560
3090
  // OneKey patch: React Native centers font metrics inside explicit line heights.
3561
3091
  let baselineOffset = max(0, (lineHeight - label.font.lineHeight) / 2)
3562
3092
  // OneKey patch: TextKit's 14/20 headings align their baseline to the upper physical pixel.
3563
- let isSelectorHeading = lineHeight == 20 && (currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" || currentItem?.type == "market" && label.font.pointSize == 14)
3093
+ let isSelectorHeading = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && lineHeight == 20
3564
3094
  let scale = window?.screen.scale ?? traitCollection.displayScale
3565
3095
  attributes[.baselineOffset] = isSelectorHeading && scale > 0 ? ceil(baselineOffset * scale) / scale : baselineOffset
3566
3096
  }
3567
- if letterSpacing != 0 || currentItem?.type == "market" { attributes[.kern] = letterSpacing }
3097
+ if letterSpacing != 0 { attributes[.kern] = letterSpacing }
3568
3098
  label.attributedText = NSAttributedString(string: text, attributes: attributes)
3569
3099
  }
3570
3100
 
@@ -3582,10 +3112,8 @@ final class NativeListCell: UICollectionViewCell {
3582
3112
  let isSelectorValue = currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") != "summary"
3583
3113
  let isSelectorSummary = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") == "summary"
3584
3114
  (button as? NativeListAccessoryButton)?.selectorSummaryLineHeight = isSelectorSummary ? lineHeight : nil
3585
- (button as? NativeListAccessoryButton)?.marketLineHeight = currentItem?.type == "market" ? lineHeight : nil
3586
3115
  // OneKey patch: summary text uses its source line box; currency retains trailing alignment.
3587
- // Market's line box already handles alignment; source text starts at its origin.
3588
- paragraphStyle.alignment = isSelectorValue ? .right : isSelectorSummary || currentItem?.type == "market" ? .natural : .center
3116
+ paragraphStyle.alignment = isSelectorValue ? .right : isSelectorSummary ? .natural : .center
3589
3117
  if isSelectorSummary {
3590
3118
  button.contentHorizontalAlignment = .leading
3591
3119
  button.titleLabel?.textAlignment = .natural
@@ -3594,16 +3122,17 @@ final class NativeListCell: UICollectionViewCell {
3594
3122
  button.contentHorizontalAlignment = .trailing
3595
3123
  button.titleLabel?.textAlignment = .right
3596
3124
  }
3597
- let baselineOffset: CGFloat = currentItem?.type == "market" || currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil ? max(0, (lineHeight - font.lineHeight) / 2) : 0
3598
- var attributes: [NSAttributedString.Key: Any] = [
3599
- .font: font,
3600
- .foregroundColor: color,
3601
- .paragraphStyle: paragraphStyle,
3602
- .baselineOffset: currentItem?.type == "market" && lineHeight == 20 && font.pointSize == 14 ? ceil(baselineOffset * max(1, traitCollection.displayScale)) / max(1, traitCollection.displayScale) : baselineOffset,
3603
- ]
3604
- if currentItem?.type == "market" { attributes[.kern] = 0 }
3125
+ let baselineOffset: CGFloat = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil ? max(0, (lineHeight - font.lineHeight) / 2) : 0
3605
3126
  button.setAttributedTitle(
3606
- NSAttributedString(string: text, attributes: attributes),
3127
+ NSAttributedString(
3128
+ string: text,
3129
+ attributes: [
3130
+ .font: font,
3131
+ .foregroundColor: color,
3132
+ .paragraphStyle: paragraphStyle,
3133
+ .baselineOffset: baselineOffset,
3134
+ ]
3135
+ ),
3607
3136
  for: .normal
3608
3137
  )
3609
3138
  }
@@ -3865,19 +3394,6 @@ final class NativeListCell: UICollectionViewCell {
3865
3394
  )
3866
3395
  }
3867
3396
 
3868
- @objc private func marketBadgePressed(_ sender: UIButton) {
3869
- guard let item = currentItem,
3870
- marketBadgeActionKeys.indices.contains(sender.tag),
3871
- let actionKey = marketBadgeActionKeys[sender.tag],
3872
- !actionKey.isEmpty else { return }
3873
- onAction?(
3874
- item,
3875
- actionKey,
3876
- nil,
3877
- actionOrigin(sourceView: sender, source: "marketBadge", slot: sender.tag)
3878
- )
3879
- }
3880
-
3881
3397
  @objc private func footerActionPressed(_ sender: UIButton) {
3882
3398
  guard let item = currentItem, footerActionKeys.indices.contains(sender.tag) else { return }
3883
3399
  onAction?(