@onekeyfe/react-native-native-list 3.0.111 → 3.0.113
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +57 -3
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +17 -1
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +543 -26
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +26 -2
- package/ios/NativeListCell.swift +504 -20
- package/ios/NativeListDesignAssets.swift +8 -0
- package/ios/RNCNativeListView.swift +85 -7
- package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_badge_verified_solid.imageset/icon.svg +1 -0
- package/lib/module/validation.js +129 -1
- package/lib/module/web/NativeListWebEngine.js +357 -5
- package/lib/typescript/src/models.d.ts +82 -2
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +26 -1
- package/package.json +2 -2
- package/src/models.ts +121 -3
- package/src/validation.ts +206 -0
- package/src/web/NativeListWebEngine.ts +578 -4
package/ios/NativeListCell.swift
CHANGED
|
@@ -4,6 +4,73 @@ 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
|
+
|
|
7
74
|
final class NativeListActionOrigin {
|
|
8
75
|
weak var sourceView: UIView?
|
|
9
76
|
weak var ownerCell: NativeListCell?
|
|
@@ -36,13 +103,17 @@ private final class NativeListAccessoryButton: UIButton {
|
|
|
36
103
|
didSet { invalidateIntrinsicContentSize(); setNeedsLayout() }
|
|
37
104
|
}
|
|
38
105
|
|
|
106
|
+
var marketLineHeight: CGFloat? {
|
|
107
|
+
didSet { invalidateIntrinsicContentSize(); setNeedsLayout() }
|
|
108
|
+
}
|
|
109
|
+
|
|
39
110
|
private var sourcePixelScale: CGFloat {
|
|
40
111
|
max(1, window?.screen.scale ?? traitCollection.displayScale)
|
|
41
112
|
}
|
|
42
113
|
|
|
43
114
|
override var intrinsicContentSize: CGSize {
|
|
44
115
|
var size = super.intrinsicContentSize
|
|
45
|
-
guard selectorSummaryLineHeight != nil, let title = attributedTitle(for: .normal) else { return size }
|
|
116
|
+
guard selectorSummaryLineHeight != nil || marketLineHeight != nil, let title = attributedTitle(for: .normal) else { return size }
|
|
46
117
|
let width = title.boundingRect(
|
|
47
118
|
with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude),
|
|
48
119
|
options: [.usesLineFragmentOrigin, .usesFontLeading],
|
|
@@ -54,6 +125,21 @@ private final class NativeListAccessoryButton: UIButton {
|
|
|
54
125
|
|
|
55
126
|
override func layoutSubviews() {
|
|
56
127
|
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
|
+
}
|
|
57
143
|
guard let lineHeight = selectorSummaryLineHeight, let titleLabel else { return }
|
|
58
144
|
// OneKey patch: position the final source line box after UIKit has measured the button.
|
|
59
145
|
let top = ceil((bounds.height - lineHeight) / 2 * sourcePixelScale) / sourcePixelScale
|
|
@@ -362,6 +448,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
362
448
|
private let metricSubtitleLabel = UILabel()
|
|
363
449
|
private let metricCompositeStack = UIStackView()
|
|
364
450
|
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) }
|
|
365
453
|
private let actionStack = UIStackView()
|
|
366
454
|
private let actionButtons = (0..<3).map { _ in UIButton(type: .system) }
|
|
367
455
|
private let trailingStack = UIStackView()
|
|
@@ -412,6 +500,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
412
500
|
private var boundCheckboxData: [String: Any]?
|
|
413
501
|
private var boundCheckboxTarget: NativeSelectionTarget?
|
|
414
502
|
private var leadingActionKey: String?
|
|
503
|
+
private var marketBadgeActionKeys: [String?] = []
|
|
415
504
|
private var restingBackgroundColor: UIColor = .clear
|
|
416
505
|
private var pressedBackgroundColor = UIColor(
|
|
417
506
|
nativeListHex: "#E8E8E8",
|
|
@@ -612,6 +701,23 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
612
701
|
titleRowStack.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
613
702
|
titleRowStack.addArrangedSubview(titleLabel)
|
|
614
703
|
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
|
+
}
|
|
615
721
|
titleLabel.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
|
616
722
|
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
617
723
|
badgeLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
@@ -843,6 +949,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
843
949
|
case "activity": bindActivity(item, theme: theme)
|
|
844
950
|
case "message": bindMessage(item, theme: theme)
|
|
845
951
|
case "dataRow": bindDataRow(item, theme: theme, checkboxState)
|
|
952
|
+
case "market": bindMarket(item, theme: theme)
|
|
846
953
|
case "mediaTile": bindMediaTile(item, theme: theme)
|
|
847
954
|
case "metricCard": bindMetricCard(item, theme: theme)
|
|
848
955
|
case "sectionHeader": bindSectionHeader(item, theme: theme, layout: layout, checkboxState)
|
|
@@ -1041,6 +1148,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1041
1148
|
selectorConstraints.removeAll()
|
|
1042
1149
|
selectorImages.forEach { $0.prepareForReuse() }
|
|
1043
1150
|
selectorImages.removeAll()
|
|
1151
|
+
marketBadgeImages.forEach { $0.prepareForReuse(); $0.isHidden = true }
|
|
1044
1152
|
selectorFullWidthBackground.removeFromSuperlayer()
|
|
1045
1153
|
selectorBorder?.removeFromSuperlayer()
|
|
1046
1154
|
selectorBorder = nil
|
|
@@ -1123,6 +1231,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1123
1231
|
$0.isHidden = true
|
|
1124
1232
|
$0.alpha = 1
|
|
1125
1233
|
$0.layer.cornerRadius = 0
|
|
1234
|
+
$0.layer.mask = nil
|
|
1126
1235
|
}
|
|
1127
1236
|
leadingContainer.clipsToBounds = true
|
|
1128
1237
|
leadingContainer.layer.borderWidth = 0
|
|
@@ -1178,6 +1287,19 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1178
1287
|
badgeLabel.backgroundColor = .clear
|
|
1179
1288
|
badgeLabel.layer.cornerRadius = 0
|
|
1180
1289
|
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
|
+
}
|
|
1181
1303
|
[titleLabel, subtitleLabel, tertiaryLabel, statusLabel, metricSubtitleLabel, badgeLabel, actionStack]
|
|
1182
1304
|
.forEach { $0.isHidden = true }
|
|
1183
1305
|
separatorView.isHidden = true
|
|
@@ -1188,7 +1310,9 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1188
1310
|
$0.backgroundColor = .clear
|
|
1189
1311
|
}
|
|
1190
1312
|
accessoryButtons.enumerated().forEach { index, button in
|
|
1313
|
+
button.isUserInteractionEnabled = true
|
|
1191
1314
|
button.selectorSummaryLineHeight = nil
|
|
1315
|
+
button.marketLineHeight = nil
|
|
1192
1316
|
button.titleLabel?.font = nativeListFont(
|
|
1193
1317
|
ofSize: index == 0 ? 16 : 14,
|
|
1194
1318
|
weight: index == 0 ? .medium : .regular
|
|
@@ -1233,6 +1357,7 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
1233
1357
|
boundCheckboxData = nil
|
|
1234
1358
|
boundCheckboxTarget = nil
|
|
1235
1359
|
leadingActionKey = nil
|
|
1360
|
+
marketBadgeActionKeys = []
|
|
1236
1361
|
layer.maskedCorners = []
|
|
1237
1362
|
layer.cornerRadius = 0
|
|
1238
1363
|
layer.cornerCurve = .circular
|
|
@@ -2014,6 +2139,281 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2014
2139
|
}
|
|
2015
2140
|
}
|
|
2016
2141
|
|
|
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
|
+
|
|
2017
2417
|
private func bindMediaTile(_ item: NativeListItem, theme: [String: Any]?) {
|
|
2018
2418
|
rootStack.axis = .vertical
|
|
2019
2419
|
rootStack.alignment = .fill
|
|
@@ -2630,6 +3030,76 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2630
3030
|
rootStack.alignment = .center
|
|
2631
3031
|
rootStack.distribution = .fill
|
|
2632
3032
|
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
|
+
}
|
|
2633
3103
|
// OneKey patch: deprecated-wallet warnings stay inside the scrolling list.
|
|
2634
3104
|
if variant == "warning" {
|
|
2635
3105
|
rootStack.addArrangedSubview(mainStack)
|
|
@@ -2661,10 +3131,10 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
2661
3131
|
return
|
|
2662
3132
|
}
|
|
2663
3133
|
if variant == "loading" {
|
|
2664
|
-
leadingWidth.constant = 40
|
|
2665
|
-
leadingHeight.constant = 40
|
|
3134
|
+
leadingWidth.constant = isMarket ? 32 : 40
|
|
3135
|
+
leadingHeight.constant = isMarket ? 32 : 40
|
|
2666
3136
|
leadingContainer.backgroundColor = nativeListColor(theme, "strongBackground", "#F0F0F0")
|
|
2667
|
-
leadingContainer.layer.cornerRadius = 20
|
|
3137
|
+
leadingContainer.layer.cornerRadius = isMarket ? 16 : 20
|
|
2668
3138
|
rootStack.addArrangedSubview(leadingContainer)
|
|
2669
3139
|
configureSkeleton(skeletonPrimary, width: 120, height: 12, theme: theme)
|
|
2670
3140
|
configureSkeleton(skeletonSecondary, width: 80, height: 12, theme: theme)
|
|
@@ -3076,8 +3546,8 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3076
3546
|
let paragraphStyle = NSMutableParagraphStyle()
|
|
3077
3547
|
paragraphStyle.minimumLineHeight = lineHeight
|
|
3078
3548
|
paragraphStyle.maximumLineHeight = lineHeight
|
|
3079
|
-
if currentItem?.data.string("presentation") == "walletSidebar" {
|
|
3080
|
-
//
|
|
3549
|
+
if currentItem?.type == "market" || currentItem?.data.string("presentation") == "walletSidebar" {
|
|
3550
|
+
// Attributed paragraphs must preserve the label alignment and tail ellipsis.
|
|
3081
3551
|
paragraphStyle.alignment = label.textAlignment
|
|
3082
3552
|
paragraphStyle.lineBreakMode = label.lineBreakMode
|
|
3083
3553
|
}
|
|
@@ -3086,15 +3556,15 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3086
3556
|
.foregroundColor: label.textColor as Any,
|
|
3087
3557
|
.paragraphStyle: paragraphStyle,
|
|
3088
3558
|
]
|
|
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" {
|
|
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" {
|
|
3090
3560
|
// OneKey patch: React Native centers font metrics inside explicit line heights.
|
|
3091
3561
|
let baselineOffset = max(0, (lineHeight - label.font.lineHeight) / 2)
|
|
3092
3562
|
// OneKey patch: TextKit's 14/20 headings align their baseline to the upper physical pixel.
|
|
3093
|
-
let isSelectorHeading = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" &&
|
|
3563
|
+
let isSelectorHeading = lineHeight == 20 && (currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" || currentItem?.type == "market" && label.font.pointSize == 14)
|
|
3094
3564
|
let scale = window?.screen.scale ?? traitCollection.displayScale
|
|
3095
3565
|
attributes[.baselineOffset] = isSelectorHeading && scale > 0 ? ceil(baselineOffset * scale) / scale : baselineOffset
|
|
3096
3566
|
}
|
|
3097
|
-
if letterSpacing != 0 { attributes[.kern] = letterSpacing }
|
|
3567
|
+
if letterSpacing != 0 || currentItem?.type == "market" { attributes[.kern] = letterSpacing }
|
|
3098
3568
|
label.attributedText = NSAttributedString(string: text, attributes: attributes)
|
|
3099
3569
|
}
|
|
3100
3570
|
|
|
@@ -3112,8 +3582,10 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3112
3582
|
let isSelectorValue = currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") != "summary"
|
|
3113
3583
|
let isSelectorSummary = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil && currentItem?.data.string("variant") == "summary"
|
|
3114
3584
|
(button as? NativeListAccessoryButton)?.selectorSummaryLineHeight = isSelectorSummary ? lineHeight : nil
|
|
3585
|
+
(button as? NativeListAccessoryButton)?.marketLineHeight = currentItem?.type == "market" ? lineHeight : nil
|
|
3115
3586
|
// OneKey patch: summary text uses its source line box; currency retains trailing alignment.
|
|
3116
|
-
|
|
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
|
|
3117
3589
|
if isSelectorSummary {
|
|
3118
3590
|
button.contentHorizontalAlignment = .leading
|
|
3119
3591
|
button.titleLabel?.textAlignment = .natural
|
|
@@ -3122,17 +3594,16 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3122
3594
|
button.contentHorizontalAlignment = .trailing
|
|
3123
3595
|
button.titleLabel?.textAlignment = .right
|
|
3124
3596
|
}
|
|
3125
|
-
let baselineOffset: CGFloat = currentItem?.type == "sectionHeader" && currentItem?.data.string("presentation") == "networkSelector" && currentItem?.data["height"] != nil ? max(0, (lineHeight - font.lineHeight) / 2) : 0
|
|
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 }
|
|
3126
3605
|
button.setAttributedTitle(
|
|
3127
|
-
NSAttributedString(
|
|
3128
|
-
string: text,
|
|
3129
|
-
attributes: [
|
|
3130
|
-
.font: font,
|
|
3131
|
-
.foregroundColor: color,
|
|
3132
|
-
.paragraphStyle: paragraphStyle,
|
|
3133
|
-
.baselineOffset: baselineOffset,
|
|
3134
|
-
]
|
|
3135
|
-
),
|
|
3606
|
+
NSAttributedString(string: text, attributes: attributes),
|
|
3136
3607
|
for: .normal
|
|
3137
3608
|
)
|
|
3138
3609
|
}
|
|
@@ -3394,6 +3865,19 @@ final class NativeListCell: UICollectionViewCell {
|
|
|
3394
3865
|
)
|
|
3395
3866
|
}
|
|
3396
3867
|
|
|
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
|
+
|
|
3397
3881
|
@objc private func footerActionPressed(_ sender: UIButton) {
|
|
3398
3882
|
guard let item = currentItem, footerActionKeys.indices.contains(sender.tag) else { return }
|
|
3399
3883
|
onAction?(
|