@onekeyfe/react-native-native-list 3.0.157-alpha.248 → 3.0.157-alpha.250
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -5
- package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
- package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
- package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
- package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
- package/docs/DESIGN.md +518 -9
- package/docs/MIGRATION_BASELINE.md +117 -0
- package/docs/ROW_TEMPLATES.md +359 -0
- package/docs/SPEC.md +358 -0
- package/docs/STYLE_SPEC.md +978 -0
- package/docs/images/list-capabilities.svg +39 -0
- package/docs/images/row-action.svg +17 -0
- package/docs/images/row-activity.svg +24 -0
- package/docs/images/row-data.svg +24 -0
- package/docs/images/row-identity.svg +25 -0
- package/docs/images/row-market.svg +23 -0
- package/docs/images/row-media-tile.svg +26 -0
- package/docs/images/row-message.svg +20 -0
- package/docs/images/row-metric-card.svg +33 -0
- package/docs/images/row-rail.svg +18 -0
- package/docs/images/row-section-header.svg +19 -0
- package/docs/images/row-style-regions.svg +30 -0
- package/docs/images/row-system.svg +30 -0
- package/docs/images/row-wallet-group.svg +26 -0
- package/ios/NativeListAccessoryStack.swift +680 -0
- package/ios/NativeListActionRenderer.swift +108 -0
- package/ios/NativeListActivityRenderer.swift +179 -0
- package/ios/NativeListDataRowRenderer.swift +444 -0
- package/ios/NativeListIdentityRenderer.swift +685 -0
- package/ios/NativeListImageSlot.swift +140 -0
- package/ios/NativeListLeadingVisual.swift +372 -0
- package/ios/NativeListMarketRenderer.swift +698 -0
- package/ios/NativeListMediaTileRenderer.swift +184 -0
- package/ios/NativeListMessageRenderer.swift +168 -0
- package/ios/NativeListMetricCardRenderer.swift +420 -0
- package/ios/NativeListModels.swift +32 -0
- package/ios/NativeListRailRenderer.swift +152 -0
- package/ios/NativeListRendererCell.swift +311 -0
- package/ios/NativeListRendererRegistry.swift +50 -0
- package/ios/NativeListResolvedText.swift +122 -0
- package/ios/NativeListRowHost.swift +63 -0
- package/ios/NativeListSectionHeaderRenderer.swift +708 -0
- package/ios/NativeListSystemRenderer.swift +568 -0
- package/ios/NativeListTextLabel.swift +42 -0
- package/ios/NativeListTextStyles.swift +236 -0
- package/ios/NativeListWalletGroupRenderer.swift +255 -0
- package/ios/RNCNativeListView.swift +111 -180
- package/lib/module/models.js +54 -0
- package/lib/module/validation.js +324 -26
- package/lib/module/web/NativeListWebEngine.js +148 -936
- package/lib/module/web/templates/ActionRowRenderer.js +97 -0
- package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
- package/lib/module/web/templates/DataRowRenderer.js +145 -0
- package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
- package/lib/module/web/templates/MarketRowRenderer.js +310 -0
- package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
- package/lib/module/web/templates/MessageRowRenderer.js +165 -0
- package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
- package/lib/module/web/templates/RailRowRenderer.js +98 -0
- package/lib/module/web/templates/RowContainerStyle.js +100 -0
- package/lib/module/web/templates/RowElements.js +93 -0
- package/lib/module/web/templates/RowRendererRegistry.js +70 -0
- package/lib/module/web/templates/RowText.js +121 -0
- package/lib/module/web/templates/RowVisual.js +43 -0
- package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
- package/lib/module/web/templates/SystemRowRenderer.js +161 -0
- package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
- package/lib/typescript/src/models.d.ts +166 -13
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
- package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
- package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
- package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
- package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
- package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
- package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
- package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
- package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
- package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
- package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
- package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
- package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
- package/package.json +3 -3
- package/src/models.ts +281 -19
- package/src/validation.ts +501 -46
- package/src/web/NativeListWebEngine.ts +361 -1932
- package/src/web/templates/ActionRowRenderer.ts +123 -0
- package/src/web/templates/ActivityRowRenderer.ts +159 -0
- package/src/web/templates/DataRowRenderer.ts +212 -0
- package/src/web/templates/IdentityRowRenderer.ts +527 -0
- package/src/web/templates/MarketRowRenderer.ts +467 -0
- package/src/web/templates/MediaTileRowRenderer.ts +182 -0
- package/src/web/templates/MessageRowRenderer.ts +306 -0
- package/src/web/templates/MetricCardRowRenderer.ts +337 -0
- package/src/web/templates/RailRowRenderer.ts +136 -0
- package/src/web/templates/RowContainerStyle.ts +131 -0
- package/src/web/templates/RowElements.ts +160 -0
- package/src/web/templates/RowRendererRegistry.ts +111 -0
- package/src/web/templates/RowText.ts +181 -0
- package/src/web/templates/RowVisual.ts +115 -0
- package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
- package/src/web/templates/SystemRowRenderer.ts +282 -0
- package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
- package/ios/NativeListCell.swift +0 -4300
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
final class NativeListMetricCardCell: NativeListRendererCell {
|
|
4
|
+
override class func measure(
|
|
5
|
+
_ item: NativeListItem, width: CGFloat, theme: [String: Any]?, layout: String
|
|
6
|
+
) -> CGFloat? {
|
|
7
|
+
return item.data.string("variant") == "activity"
|
|
8
|
+
? 160 + 1 / UIScreen.main.scale : item.data.string("variant") == "performance" ? 178 : 132
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
private let visual = NativeListLeadingVisual()
|
|
12
|
+
private let mainStack = UIStackView()
|
|
13
|
+
private let titleLine = UIStackView()
|
|
14
|
+
private let titleLabel = NativeListTextLabel()
|
|
15
|
+
private let subtitleLabel = NativeListTextLabel()
|
|
16
|
+
private let statusLabel = NativeListTextLabel()
|
|
17
|
+
private let detailLabel = NativeListTextLabel()
|
|
18
|
+
private let badgeLabel = NativeListTextLabel()
|
|
19
|
+
private let metricCompositeStack = UIStackView()
|
|
20
|
+
private var dimensions: [NSLayoutConstraint] = []
|
|
21
|
+
private var metricVisuals: [String: NativeListMetricVisual] = [:]
|
|
22
|
+
private var usedVisuals = Set<String>()
|
|
23
|
+
private var rowKey = ""
|
|
24
|
+
private var boundTheme: [String: Any]?
|
|
25
|
+
override var assetFields: [String] { ["visual", "metrics"] }
|
|
26
|
+
override var defaultCornerRadius: CGFloat { 12 }
|
|
27
|
+
override func unselectedBackground(
|
|
28
|
+
_ item: NativeListItem, theme: [String: Any]?, layout: String, itemIndex: Int?
|
|
29
|
+
) -> UIColor {
|
|
30
|
+
// Legacy: a selected card in a sectioned list without its own `selected` flag falls back
|
|
31
|
+
// to rowBackground (the checkbox carries selection), not the subdued card fill.
|
|
32
|
+
selectedState && layout == "sectioned"
|
|
33
|
+
? nativeListColor(theme, "rowBackground", "#FFFFFF")
|
|
34
|
+
: nativeListColor(theme, "subduedBackground", "#F9F9F9")
|
|
35
|
+
}
|
|
36
|
+
override init(frame: CGRect) {
|
|
37
|
+
super.init(frame: frame)
|
|
38
|
+
mainStack.axis = .vertical
|
|
39
|
+
mainStack.alignment = .fill
|
|
40
|
+
titleLine.axis = .horizontal
|
|
41
|
+
titleLine.alignment = .center
|
|
42
|
+
titleLine.spacing = 8
|
|
43
|
+
titleLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
44
|
+
badgeLabel.setContentHuggingPriority(.required, for: .horizontal)
|
|
45
|
+
titleLine.addArrangedSubview(titleLabel)
|
|
46
|
+
titleLine.addArrangedSubview(badgeLabel)
|
|
47
|
+
[titleLine, subtitleLabel, statusLabel, detailLabel].forEach(mainStack.addArrangedSubview)
|
|
48
|
+
metricCompositeStack.axis = .vertical
|
|
49
|
+
metricCompositeStack.alignment = .fill
|
|
50
|
+
// Legacy clipped the standard visual container to its shape even with overlays.
|
|
51
|
+
visual.alwaysClips = true
|
|
52
|
+
}
|
|
53
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
54
|
+
override func bindContent(
|
|
55
|
+
_ item: NativeListItem, theme: [String: Any]?, layout: String,
|
|
56
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
57
|
+
) {
|
|
58
|
+
rowKey = item.key
|
|
59
|
+
boundTheme = theme
|
|
60
|
+
usedVisuals = []
|
|
61
|
+
contentView.clipsToBounds = true
|
|
62
|
+
root.arrangedSubviews.forEach {
|
|
63
|
+
root.removeArrangedSubview($0)
|
|
64
|
+
$0.removeFromSuperview()
|
|
65
|
+
}
|
|
66
|
+
mainStack.removeArrangedSubview(metricCompositeStack)
|
|
67
|
+
metricCompositeStack.removeFromSuperview()
|
|
68
|
+
metricCompositeStack.arrangedSubviews.forEach {
|
|
69
|
+
metricCompositeStack.removeArrangedSubview($0)
|
|
70
|
+
$0.removeFromSuperview()
|
|
71
|
+
}
|
|
72
|
+
let data = item.data
|
|
73
|
+
let style = data.dictionary("style") ?? [:]
|
|
74
|
+
let image = style.dictionary("image") ?? [:]
|
|
75
|
+
root.axis = .vertical
|
|
76
|
+
root.alignment = .leading
|
|
77
|
+
root.spacing = CGFloat(style.double("lineGap", default: 4))
|
|
78
|
+
mainStack.spacing = CGFloat(style.double("lineGap", default: 2))
|
|
79
|
+
let hp = CGFloat(style.double("horizontalPadding", default: 14))
|
|
80
|
+
let vp = CGFloat(style.double("verticalPadding", default: 14))
|
|
81
|
+
contentInsets = UIEdgeInsets(top: vp, left: hp, bottom: vp, right: hp)
|
|
82
|
+
for label in [titleLabel, subtitleLabel, statusLabel, detailLabel, badgeLabel] {
|
|
83
|
+
label.attributedText = nil
|
|
84
|
+
label.text = nil
|
|
85
|
+
label.isHidden = true
|
|
86
|
+
label.numberOfLines = 1
|
|
87
|
+
label.lineBreakMode = .byTruncatingTail
|
|
88
|
+
label.textAlignment = .natural
|
|
89
|
+
label.rowVerticalAlignment = nil
|
|
90
|
+
label.rowOffsetY = 0
|
|
91
|
+
label.transform = .identity
|
|
92
|
+
}
|
|
93
|
+
titleLabel.font = nativeListFont(
|
|
94
|
+
ofSize: data.string("size") == "large" ? 24 : 18, weight: .semibold)
|
|
95
|
+
subtitleLabel.font = nativeListFont(ofSize: 11)
|
|
96
|
+
statusLabel.font = nativeListFont(ofSize: 12)
|
|
97
|
+
detailLabel.font = nativeListFont(ofSize: 12)
|
|
98
|
+
badgeLabel.font = nativeListFont(ofSize: 12, weight: .medium)
|
|
99
|
+
titleLabel.textColor = nativeListColor(theme, "primaryText", "#202020")
|
|
100
|
+
subtitleLabel.textColor = nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
101
|
+
[statusLabel, detailLabel].forEach {
|
|
102
|
+
$0.textColor = nativeListColor(theme, "secondaryText", "#646464")
|
|
103
|
+
}
|
|
104
|
+
badgeLabel.textColor = nativeListColor(theme, "accent", "#108303")
|
|
105
|
+
let variant = data.string("variant", default: "standard")
|
|
106
|
+
NSLayoutConstraint.deactivate(dimensions)
|
|
107
|
+
dimensions = []
|
|
108
|
+
if variant == "activity" || variant == "performance" {
|
|
109
|
+
visual.recycle()
|
|
110
|
+
bindCompositeMetricCard(item, theme: theme, variant: variant)
|
|
111
|
+
if style["lineGap"] != nil {
|
|
112
|
+
mainStack.spacing = CGFloat(style.double("lineGap"))
|
|
113
|
+
metricCompositeStack.spacing = CGFloat(style.double("lineGap"))
|
|
114
|
+
if variant == "performance",
|
|
115
|
+
let summary = metricCompositeStack.arrangedSubviews.first as? UIStackView
|
|
116
|
+
{
|
|
117
|
+
summary.spacing = CGFloat(style.double("lineGap"))
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if let text = style.dictionary("title") {
|
|
121
|
+
NativeListTextStyles.applyStyledText(titleLabel, text)
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
if let descriptor = data.dictionary("visual") {
|
|
125
|
+
visual.bind(descriptor, style: image, key: item.key, theme: theme, isUnread: false)
|
|
126
|
+
root.addArrangedSubview(visual)
|
|
127
|
+
dimensions = [
|
|
128
|
+
visual.widthAnchor.constraint(
|
|
129
|
+
equalToConstant: CGFloat(image.double("width", default: 32))),
|
|
130
|
+
visual.heightAnchor.constraint(
|
|
131
|
+
equalToConstant: CGFloat(image.double("height", default: 32))),
|
|
132
|
+
]
|
|
133
|
+
NSLayoutConstraint.activate(dimensions)
|
|
134
|
+
root.setCustomSpacing(
|
|
135
|
+
CGFloat(style.double("leadingGap", default: Double(root.spacing))), after: visual)
|
|
136
|
+
} else {
|
|
137
|
+
visual.recycle()
|
|
138
|
+
}
|
|
139
|
+
show(titleLabel, data.string("value"), lines: 1)
|
|
140
|
+
show(subtitleLabel, data.string("title"), lines: 1)
|
|
141
|
+
show(statusLabel, data.string("trend"), lines: 1)
|
|
142
|
+
show(detailLabel, data.string("subtitle"), lines: 1)
|
|
143
|
+
show(badgeLabel, data.dictionary("badge")?.string("text") ?? "", lines: 1)
|
|
144
|
+
let tone = data.string("trendTone")
|
|
145
|
+
if tone == "positive" || tone == "negative" {
|
|
146
|
+
statusLabel.textColor = nativeListColor(
|
|
147
|
+
theme, tone, tone == "positive" ? "#218358" : "#CE2C31")
|
|
148
|
+
}
|
|
149
|
+
for (slot, label) in [
|
|
150
|
+
("title", subtitleLabel), ("value", titleLabel), ("subtitle", detailLabel),
|
|
151
|
+
("trend", statusLabel),
|
|
152
|
+
] {
|
|
153
|
+
if let text = style.dictionary(slot) { NativeListTextStyles.applyStyledText(label, text) }
|
|
154
|
+
}
|
|
155
|
+
root.addArrangedSubview(mainStack)
|
|
156
|
+
}
|
|
157
|
+
for key in Array(metricVisuals.keys) where !usedVisuals.contains(key) {
|
|
158
|
+
metricVisuals.removeValue(forKey: key)?.recycle()
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
override func recycleContent() {
|
|
162
|
+
visual.recycle()
|
|
163
|
+
metricVisuals.values.forEach { $0.recycle() }
|
|
164
|
+
metricVisuals = [:]
|
|
165
|
+
usedVisuals = []
|
|
166
|
+
}
|
|
167
|
+
private func show(_ view: UILabel, _ text: String, lines: Int) {
|
|
168
|
+
view.text = text
|
|
169
|
+
view.numberOfLines = lines
|
|
170
|
+
view.isHidden = text.isEmpty
|
|
171
|
+
}
|
|
172
|
+
private func setLineHeight(
|
|
173
|
+
_ view: UILabel, text: String, lineHeight: CGFloat, letterSpacing: CGFloat? = nil
|
|
174
|
+
) {
|
|
175
|
+
let paragraph = NSMutableParagraphStyle()
|
|
176
|
+
paragraph.minimumLineHeight = lineHeight
|
|
177
|
+
paragraph.maximumLineHeight = lineHeight
|
|
178
|
+
paragraph.alignment = view.textAlignment
|
|
179
|
+
var attrs: [NSAttributedString.Key: Any] = [
|
|
180
|
+
.font: view.font as Any, .foregroundColor: view.textColor as Any, .paragraphStyle: paragraph,
|
|
181
|
+
]
|
|
182
|
+
if let letterSpacing { attrs[.kern] = letterSpacing }
|
|
183
|
+
view.attributedText = NSAttributedString(string: text, attributes: attrs)
|
|
184
|
+
}
|
|
185
|
+
private func makeMetricVisual(_ visual: [String: Any], key: String, slot: Int, role: String)
|
|
186
|
+
-> UIView
|
|
187
|
+
{
|
|
188
|
+
let identity = "\(rowKey):\(role):\(slot):\(key)"
|
|
189
|
+
usedVisuals.insert(identity)
|
|
190
|
+
let view = metricVisuals[identity] ?? NativeListMetricVisual()
|
|
191
|
+
metricVisuals[identity] = view
|
|
192
|
+
// Legacy metric images used the themed strongBackground placeholder.
|
|
193
|
+
view.bind(
|
|
194
|
+
visual, key: identity,
|
|
195
|
+
placeholder: boundTheme?.string("strongBackground", default: "#0000000F") ?? "#0000000F")
|
|
196
|
+
return view
|
|
197
|
+
}
|
|
198
|
+
private func bindCompositeMetricCard(
|
|
199
|
+
_ item: NativeListItem,
|
|
200
|
+
theme: [String: Any]?,
|
|
201
|
+
variant: String
|
|
202
|
+
) {
|
|
203
|
+
root.alignment = .fill
|
|
204
|
+
show(titleLabel, item.data.string("title"), lines: 1)
|
|
205
|
+
titleLabel.font = nativeListFont(ofSize: 11)
|
|
206
|
+
titleLabel.textColor = nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
207
|
+
setLineHeight(
|
|
208
|
+
titleLabel,
|
|
209
|
+
text: item.data.string("title").uppercased(),
|
|
210
|
+
lineHeight: 14,
|
|
211
|
+
letterSpacing: 1.2
|
|
212
|
+
)
|
|
213
|
+
mainStack.spacing = 14
|
|
214
|
+
metricCompositeStack.spacing = 14
|
|
215
|
+
mainStack.addArrangedSubview(metricCompositeStack)
|
|
216
|
+
root.addArrangedSubview(mainStack)
|
|
217
|
+
|
|
218
|
+
let metrics = item.data.dictionaries("metrics")
|
|
219
|
+
let topCount = min(2, metrics.count)
|
|
220
|
+
if variant == "activity" {
|
|
221
|
+
metricCompositeStack.addArrangedSubview(
|
|
222
|
+
makeMetricRow(
|
|
223
|
+
Array(metrics.prefix(topCount)),
|
|
224
|
+
theme: theme,
|
|
225
|
+
style: "activityHero"
|
|
226
|
+
)
|
|
227
|
+
)
|
|
228
|
+
let divider = UIView()
|
|
229
|
+
divider.backgroundColor = nativeListColor(theme, "separator", "#E0E0E0")
|
|
230
|
+
divider.heightAnchor.constraint(equalToConstant: 1 / UIScreen.main.scale).isActive = true
|
|
231
|
+
metricCompositeStack.addArrangedSubview(divider)
|
|
232
|
+
metricCompositeStack.addArrangedSubview(
|
|
233
|
+
makeMetricRow(
|
|
234
|
+
Array(metrics.dropFirst(topCount)),
|
|
235
|
+
theme: theme,
|
|
236
|
+
style: "compact"
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
} else {
|
|
240
|
+
let performanceSummary = UIStackView()
|
|
241
|
+
performanceSummary.axis = .vertical
|
|
242
|
+
performanceSummary.alignment = .fill
|
|
243
|
+
performanceSummary.spacing = 8
|
|
244
|
+
performanceSummary.addArrangedSubview(
|
|
245
|
+
makeMetricRow(
|
|
246
|
+
Array(metrics.prefix(topCount)),
|
|
247
|
+
theme: theme,
|
|
248
|
+
style: "performanceHero"
|
|
249
|
+
)
|
|
250
|
+
)
|
|
251
|
+
let progress = min(1, max(0, item.data.double("progress")))
|
|
252
|
+
let progressRow = UIStackView()
|
|
253
|
+
progressRow.axis = .horizontal
|
|
254
|
+
progressRow.spacing = 0
|
|
255
|
+
progressRow.layer.cornerRadius = 2
|
|
256
|
+
progressRow.clipsToBounds = true
|
|
257
|
+
let wins = UIView()
|
|
258
|
+
wins.backgroundColor = nativeListColor(theme, "positive", "#218358")
|
|
259
|
+
let losses = UIView()
|
|
260
|
+
losses.backgroundColor = nativeListColor(theme, "negative", "#CE2C31")
|
|
261
|
+
progressRow.addArrangedSubview(wins)
|
|
262
|
+
progressRow.addArrangedSubview(losses)
|
|
263
|
+
progressRow.heightAnchor.constraint(equalToConstant: 4).isActive = true
|
|
264
|
+
if progress <= 0 {
|
|
265
|
+
wins.isHidden = true
|
|
266
|
+
} else if progress >= 1 {
|
|
267
|
+
losses.isHidden = true
|
|
268
|
+
} else {
|
|
269
|
+
wins.widthAnchor.constraint(
|
|
270
|
+
equalTo: progressRow.widthAnchor,
|
|
271
|
+
multiplier: CGFloat(progress)
|
|
272
|
+
).isActive = true
|
|
273
|
+
}
|
|
274
|
+
performanceSummary.addArrangedSubview(progressRow)
|
|
275
|
+
metricCompositeStack.addArrangedSubview(performanceSummary)
|
|
276
|
+
metricCompositeStack.addArrangedSubview(
|
|
277
|
+
makeMetricRow(
|
|
278
|
+
Array(metrics.dropFirst(topCount)),
|
|
279
|
+
theme: theme,
|
|
280
|
+
style: "compactShaded"
|
|
281
|
+
)
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
contentView.layer.cornerRadius = 12
|
|
285
|
+
contentView.clipsToBounds = true
|
|
286
|
+
}
|
|
287
|
+
private func makeMetricRow(
|
|
288
|
+
_ metrics: [[String: Any]],
|
|
289
|
+
theme: [String: Any]?,
|
|
290
|
+
style: String
|
|
291
|
+
) -> UIStackView {
|
|
292
|
+
let shaded = style == "compactShaded"
|
|
293
|
+
let row = UIStackView()
|
|
294
|
+
row.axis = .horizontal
|
|
295
|
+
row.alignment = style.hasSuffix("Hero") ? .bottom : .fill
|
|
296
|
+
row.distribution = .fillEqually
|
|
297
|
+
row.spacing = shaded ? 8 : 12
|
|
298
|
+
metrics.enumerated().forEach { index, metric in
|
|
299
|
+
let column = UIStackView()
|
|
300
|
+
column.axis = .vertical
|
|
301
|
+
column.alignment =
|
|
302
|
+
shaded
|
|
303
|
+
? .leading
|
|
304
|
+
: index == 0 ? .leading : index == metrics.count - 1 ? .trailing : .center
|
|
305
|
+
column.spacing = 2
|
|
306
|
+
if shaded {
|
|
307
|
+
column.isLayoutMarginsRelativeArrangement = true
|
|
308
|
+
column.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)
|
|
309
|
+
column.backgroundColor = UIColor(nativeListHex: "#0000000F", fallback: .lightGray)
|
|
310
|
+
column.layer.cornerRadius = 8
|
|
311
|
+
}
|
|
312
|
+
let label = UILabel()
|
|
313
|
+
label.font = nativeListFont(ofSize: 11)
|
|
314
|
+
label.textColor = nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
315
|
+
setLineHeight(label, text: metric.string("label"), lineHeight: 14)
|
|
316
|
+
let value = UILabel()
|
|
317
|
+
let valueSize: CGFloat
|
|
318
|
+
let valueLineHeight: CGFloat
|
|
319
|
+
let valueWeight: NativeListFontWeight
|
|
320
|
+
switch style {
|
|
321
|
+
case "activityHero" where index == 0:
|
|
322
|
+
valueSize = 16
|
|
323
|
+
valueLineHeight = 24
|
|
324
|
+
valueWeight = .semibold
|
|
325
|
+
case "performanceHero" where index == 0:
|
|
326
|
+
valueSize = 18
|
|
327
|
+
valueLineHeight = 24
|
|
328
|
+
valueWeight = .semibold
|
|
329
|
+
case "activityHero", "performanceHero":
|
|
330
|
+
valueSize = 14
|
|
331
|
+
valueLineHeight = 20
|
|
332
|
+
valueWeight = .semibold
|
|
333
|
+
default:
|
|
334
|
+
valueSize = 14
|
|
335
|
+
valueLineHeight = 20
|
|
336
|
+
valueWeight = .medium
|
|
337
|
+
}
|
|
338
|
+
value.font = nativeListTabularFont(ofSize: valueSize, weight: valueWeight)
|
|
339
|
+
value.textColor = dataTextColor(metric.string("tone"), theme: theme)
|
|
340
|
+
setLineHeight(value, text: metric.string("value"), lineHeight: valueLineHeight)
|
|
341
|
+
column.addArrangedSubview(label)
|
|
342
|
+
if let visual = metric.dictionary("visual") {
|
|
343
|
+
let valueRow = UIStackView()
|
|
344
|
+
valueRow.axis = .horizontal
|
|
345
|
+
valueRow.alignment = .center
|
|
346
|
+
valueRow.spacing = 6
|
|
347
|
+
valueRow.addArrangedSubview(
|
|
348
|
+
makeMetricVisual(visual, key: metric.string("key"), slot: index, role: style)
|
|
349
|
+
)
|
|
350
|
+
valueRow.addArrangedSubview(value)
|
|
351
|
+
column.addArrangedSubview(valueRow)
|
|
352
|
+
} else {
|
|
353
|
+
column.addArrangedSubview(value)
|
|
354
|
+
}
|
|
355
|
+
row.addArrangedSubview(column)
|
|
356
|
+
}
|
|
357
|
+
return row
|
|
358
|
+
}
|
|
359
|
+
private func dataTextColor(_ tone: String, theme: [String: Any]?) -> UIColor {
|
|
360
|
+
switch tone.isEmpty ? "primary" : tone {
|
|
361
|
+
// OneKey patch: account warnings and hidden balances use existing theme tokens.
|
|
362
|
+
case "disabled": return nativeListColor(theme, "disabledText", "#8D8D8D")
|
|
363
|
+
case "caution": return nativeListColor(theme, "caution", "#AB6400")
|
|
364
|
+
case "secondary": return nativeListColor(theme, "secondaryText", "#646464")
|
|
365
|
+
case "positive": return nativeListColor(theme, "positive", "#218358")
|
|
366
|
+
case "negative": return nativeListColor(theme, "negative", "#CE2C31")
|
|
367
|
+
default: return nativeListColor(theme, "primaryText", "#202020")
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
private final class NativeListMetricVisual: UIView {
|
|
373
|
+
private let icon = UIImageView()
|
|
374
|
+
private let image = NativeListImageSlot()
|
|
375
|
+
init() {
|
|
376
|
+
super.init(frame: .zero)
|
|
377
|
+
translatesAutoresizingMaskIntoConstraints = false
|
|
378
|
+
icon.contentMode = .scaleAspectFit
|
|
379
|
+
addSubview(icon)
|
|
380
|
+
addSubview(image.view)
|
|
381
|
+
NSLayoutConstraint.activate([
|
|
382
|
+
widthAnchor.constraint(equalToConstant: 16), heightAnchor.constraint(equalToConstant: 16),
|
|
383
|
+
])
|
|
384
|
+
clipsToBounds = true
|
|
385
|
+
}
|
|
386
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
387
|
+
func bind(_ visual: [String: Any], key: String, placeholder: String) {
|
|
388
|
+
backgroundColor = UIColor(
|
|
389
|
+
nativeListHex: visual.string("backgroundColor", default: "#0000000F"), fallback: .lightGray)
|
|
390
|
+
layer.cornerRadius = visual.string("shape") == "square" ? 0 : 8
|
|
391
|
+
let kind = visual.string("kind")
|
|
392
|
+
icon.isHidden = kind != "icon"
|
|
393
|
+
image.view.isHidden = true
|
|
394
|
+
if kind == "icon" {
|
|
395
|
+
image.recycle()
|
|
396
|
+
icon.image = nativeListIcon(named: visual.string("name"))
|
|
397
|
+
icon.tintColor = UIColor(
|
|
398
|
+
nativeListHex: visual.string("tintColor", default: "#00000072"), fallback: .darkGray)
|
|
399
|
+
} else if let source = kind == "stackedImages"
|
|
400
|
+
? visual.dictionaries("images").first : visual.dictionary("image")
|
|
401
|
+
{
|
|
402
|
+
image.view.isHidden = false
|
|
403
|
+
image.bind(
|
|
404
|
+
source, key: key,
|
|
405
|
+
variant: kind == "token"
|
|
406
|
+
? "token"
|
|
407
|
+
: kind == "network"
|
|
408
|
+
? "network" : ["wallet", "account"].contains(kind) ? "avatar" : "generic", fit: nil,
|
|
409
|
+
placeholder: placeholder)
|
|
410
|
+
} else {
|
|
411
|
+
image.recycle()
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
override func layoutSubviews() {
|
|
415
|
+
super.layoutSubviews()
|
|
416
|
+
icon.frame = bounds
|
|
417
|
+
image.view.frame = bounds
|
|
418
|
+
}
|
|
419
|
+
func recycle() { image.recycle() }
|
|
420
|
+
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import Foundation
|
|
2
2
|
import UIKit
|
|
3
3
|
|
|
4
|
+
// Template type alone partitions reuse; data keys and styles never select a renderer.
|
|
5
|
+
enum NativeListRendererKey: String, CaseIterable {
|
|
6
|
+
case message, rail, mediaTile, action, system, activity, dataRow, metricCard, market, identity, sectionHeader, walletGroup
|
|
7
|
+
/// Not a template: legacy accepted any non-empty type and drew an empty row with the
|
|
8
|
+
/// shared chrome. Unknown types keep that placeholder instead of failing the snapshot.
|
|
9
|
+
case unsupported = "__nativeListUnsupported"
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
struct NativeListItem {
|
|
5
13
|
let key: String
|
|
6
14
|
let type: String
|
|
@@ -9,6 +17,18 @@ struct NativeListItem {
|
|
|
9
17
|
let data: [String: Any]
|
|
10
18
|
let content: String
|
|
11
19
|
|
|
20
|
+
let rendererKey: NativeListRendererKey
|
|
21
|
+
|
|
22
|
+
var styledHeight: CGFloat? {
|
|
23
|
+
(data.dictionary("style")?.dictionary("container")?["height"] as? Double).map { CGFloat($0) }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/// Selector presentations use their explicit-height geometry when either
|
|
27
|
+
/// `style.container.height` or `row.height` is set (Web `hasExplicitRowHeight`).
|
|
28
|
+
var hasExplicitHeight: Bool {
|
|
29
|
+
data.dictionary("style")?.dictionary("container")?["height"] != nil || data["height"] != nil
|
|
30
|
+
}
|
|
31
|
+
|
|
12
32
|
var isSelectable: Bool {
|
|
13
33
|
let disabled = data["disabled"] as? Bool ?? false
|
|
14
34
|
return !disabled && Self.selectableTypes.contains(type)
|
|
@@ -33,8 +53,12 @@ struct NativeListItem {
|
|
|
33
53
|
let type = data["type"] as? String, !type.isEmpty else {
|
|
34
54
|
throw NativeListModelError.invalidRow
|
|
35
55
|
}
|
|
56
|
+
let rendererKey = NativeListRendererKey(rawValue: type)
|
|
57
|
+
.flatMap { $0 == .unsupported ? nil : $0 } ?? .unsupported
|
|
58
|
+
if rendererKey == .unsupported { Self.logUnsupported(type) }
|
|
36
59
|
self.key = key
|
|
37
60
|
self.type = type
|
|
61
|
+
self.rendererKey = rendererKey
|
|
38
62
|
self.sectionKey = data["sectionKey"] as? String
|
|
39
63
|
self.revision = data["revision"] as? Int ?? 0
|
|
40
64
|
self.data = data
|
|
@@ -42,6 +66,12 @@ struct NativeListItem {
|
|
|
42
66
|
self.content = String(data: jsonData, encoding: .utf8) ?? ""
|
|
43
67
|
}
|
|
44
68
|
|
|
69
|
+
private static var loggedUnsupportedTypes = Set<String>()
|
|
70
|
+
private static func logUnsupported(_ type: String) {
|
|
71
|
+
guard loggedUnsupportedTypes.insert(type).inserted else { return }
|
|
72
|
+
NSLog("[NativeList] Unsupported row type '%@' rendered as an empty placeholder row", type)
|
|
73
|
+
}
|
|
74
|
+
|
|
45
75
|
private static let selectableTypes: Set<String> = [
|
|
46
76
|
"identity", "rail", "activity", "message", "dataRow", "mediaTile", "metricCard",
|
|
47
77
|
]
|
|
@@ -70,6 +100,7 @@ struct NativeListConfig {
|
|
|
70
100
|
let sectionIndexHapticsEnabled: Bool
|
|
71
101
|
let sectionIndexCenteredInWindow: Bool
|
|
72
102
|
let theme: [String: Any]?
|
|
103
|
+
let listStyle: [String: Any]?
|
|
73
104
|
let fixedFooter: NativeListItem?
|
|
74
105
|
var items: [NativeListItem]
|
|
75
106
|
|
|
@@ -132,6 +163,7 @@ struct NativeListConfig {
|
|
|
132
163
|
sectionIndexHapticsEnabled: sectionIndex?["hapticsEnabled"] as? Bool ?? true,
|
|
133
164
|
sectionIndexCenteredInWindow: sectionIndex?["centeredInWindow"] as? Bool ?? false,
|
|
134
165
|
theme: root["theme"] as? [String: Any],
|
|
166
|
+
listStyle: root["listStyle"] as? [String: Any],
|
|
135
167
|
fixedFooter: footer,
|
|
136
168
|
items: items
|
|
137
169
|
)
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
enum NativeListRailRenderer {
|
|
4
|
+
struct Resolved {
|
|
5
|
+
let title: NativeListResolvedText
|
|
6
|
+
let badge: NativeListResolvedText
|
|
7
|
+
let status: NativeListResolvedText
|
|
8
|
+
let horizontalPadding: CGFloat
|
|
9
|
+
let verticalPadding: CGFloat
|
|
10
|
+
let leadingGap: CGFloat
|
|
11
|
+
let badgeGap: CGFloat
|
|
12
|
+
let trailingGap: CGFloat
|
|
13
|
+
let imageWidth: CGFloat
|
|
14
|
+
let imageHeight: CGFloat
|
|
15
|
+
let imageStyle: [String: Any]
|
|
16
|
+
let visual: [String: Any]?
|
|
17
|
+
let alignment: UIStackView.Alignment
|
|
18
|
+
|
|
19
|
+
init(_ item: NativeListItem, theme: [String: Any]?) {
|
|
20
|
+
let style = item.data.dictionary("style") ?? [:]
|
|
21
|
+
let badge = item.data.dictionary("badge") ?? [:]
|
|
22
|
+
title = NativeListResolvedText(
|
|
23
|
+
item.data.string("title"), style: style.dictionary("title"),
|
|
24
|
+
size: 12, weight: .medium, color: nativeListColor(theme, "primaryText", "#202020"),
|
|
25
|
+
lineHeight: 16, lines: 1)
|
|
26
|
+
self.badge = NativeListResolvedText(
|
|
27
|
+
badge.string("text"), style: style.dictionary("badge"),
|
|
28
|
+
size: 12, weight: .medium,
|
|
29
|
+
color: nativeListColor(
|
|
30
|
+
theme,
|
|
31
|
+
badge.string("tone") == "success"
|
|
32
|
+
? "positive" : badge.string("tone") == "danger" ? "negative" : "secondaryText",
|
|
33
|
+
badge.string("tone") == "success"
|
|
34
|
+
? "#218358" : badge.string("tone") == "danger" ? "#CE2C31" : "#646464"), lineHeight: 16,
|
|
35
|
+
lines: 1, tabular: true)
|
|
36
|
+
let status = item.data.string("status")
|
|
37
|
+
self.status = NativeListResolvedText(
|
|
38
|
+
status == "none" ? "" : status, style: style.dictionary("status"),
|
|
39
|
+
size: 12, color: nativeListColor(theme, "secondaryText", "#646464"), lineHeight: 16,
|
|
40
|
+
lines: 1, tabular: true)
|
|
41
|
+
horizontalPadding = CGFloat(style.double("horizontalPadding", default: 4))
|
|
42
|
+
verticalPadding = CGFloat(style.double("verticalPadding", default: 4))
|
|
43
|
+
leadingGap = CGFloat(style.double("leadingGap", default: 6))
|
|
44
|
+
// The legacy iOS title/badge stack uses 8, independently of the rail's 6 gap.
|
|
45
|
+
badgeGap = CGFloat(style.double("titleBadgeGap", default: 8))
|
|
46
|
+
trailingGap = CGFloat(style.double("trailingGap", default: 6))
|
|
47
|
+
imageStyle = style.dictionary("image") ?? [:]
|
|
48
|
+
imageWidth = CGFloat(imageStyle.double("width", default: 20))
|
|
49
|
+
imageHeight = CGFloat(imageStyle.double("height", default: 20))
|
|
50
|
+
visual = item.data.dictionary("visual")
|
|
51
|
+
switch style.dictionary("container")?.string("contentVerticalAlignment") {
|
|
52
|
+
case "top": alignment = .top
|
|
53
|
+
case "bottom": alignment = .bottom
|
|
54
|
+
default: alignment = .center
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
var horizontalWidth: CGFloat {
|
|
58
|
+
func width(_ text: NativeListResolvedText) -> CGFloat {
|
|
59
|
+
(text.text as NSString).size(withAttributes: [.font: text.font]).width
|
|
60
|
+
}
|
|
61
|
+
// Preserve the legacy viewport bounds and trailing allowance. The old
|
|
62
|
+
// estimator budgeted 6 points for the actual 8-point title/badge gap.
|
|
63
|
+
return min(
|
|
64
|
+
288,
|
|
65
|
+
max(
|
|
66
|
+
72,
|
|
67
|
+
ceil(
|
|
68
|
+
horizontalPadding * 2 + imageWidth + leadingGap
|
|
69
|
+
+ width(title) + (badge.text.isEmpty ? 0 : badgeGap + width(badge))
|
|
70
|
+
+ (status.text.isEmpty ? 0 : trailingGap + width(status))
|
|
71
|
+
+ (badge.text.isEmpty ? 8 : 6))))
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
final class NativeListRailCell: NativeListRendererCell {
|
|
77
|
+
override class func measure(
|
|
78
|
+
_ item: NativeListItem, width: CGFloat, theme: [String: Any]?, layout: String
|
|
79
|
+
) -> CGFloat? {
|
|
80
|
+
return 40
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private let title = NativeListTextLabel()
|
|
84
|
+
private let badge = NativeListTextLabel()
|
|
85
|
+
private let status = NativeListTextLabel()
|
|
86
|
+
private let titleLine = UIStackView()
|
|
87
|
+
private let textLine = UIStackView()
|
|
88
|
+
private var visual: NativeListLeadingVisual?
|
|
89
|
+
private var dimensions: [NSLayoutConstraint] = []
|
|
90
|
+
override var defaultCornerRadius: CGFloat { 8 }
|
|
91
|
+
override var pressedCornerRadius: CGFloat? { 8 }
|
|
92
|
+
override var assetFields: [String] { ["visual"] }
|
|
93
|
+
override var showsSelection: Bool { false }
|
|
94
|
+
// Legacy Rail used a fixed pressed tint, independent of theme.rowPressedBackground.
|
|
95
|
+
override func pressedBackground(_ theme: [String: Any]?) -> UIColor {
|
|
96
|
+
UIColor(nativeListHex: "#F0F0F0", fallback: .lightGray)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
override init(frame: CGRect) {
|
|
100
|
+
super.init(frame: frame)
|
|
101
|
+
root.axis = .horizontal
|
|
102
|
+
titleLine.axis = .horizontal
|
|
103
|
+
textLine.axis = .horizontal
|
|
104
|
+
titleLine.alignment = .center
|
|
105
|
+
textLine.alignment = .center
|
|
106
|
+
[title, badge].forEach(titleLine.addArrangedSubview)
|
|
107
|
+
[titleLine, status].forEach(textLine.addArrangedSubview)
|
|
108
|
+
root.addArrangedSubview(textLine)
|
|
109
|
+
[title, titleLine, textLine].forEach {
|
|
110
|
+
$0.setContentHuggingPriority(.required, for: .horizontal)
|
|
111
|
+
$0.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
115
|
+
override func bindContent(
|
|
116
|
+
_ item: NativeListItem, theme: [String: Any]?, layout: String,
|
|
117
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
118
|
+
) {
|
|
119
|
+
let r = NativeListRailRenderer.Resolved(item, theme: theme)
|
|
120
|
+
r.title.bind(title)
|
|
121
|
+
r.badge.bind(badge)
|
|
122
|
+
r.status.bind(status)
|
|
123
|
+
badge.setContentHuggingPriority(.required, for: .horizontal)
|
|
124
|
+
title.setContentHuggingPriority(r.title.lines > 1 ? .defaultLow : .required, for: .horizontal)
|
|
125
|
+
status.setContentHuggingPriority(r.title.lines > 1 ? .required : .defaultLow, for: .horizontal)
|
|
126
|
+
root.alignment = r.alignment
|
|
127
|
+
root.spacing = r.leadingGap
|
|
128
|
+
titleLine.spacing = r.badgeGap
|
|
129
|
+
textLine.spacing = r.trailingGap
|
|
130
|
+
contentInsets = UIEdgeInsets(
|
|
131
|
+
top: r.verticalPadding, left: r.horizontalPadding,
|
|
132
|
+
bottom: r.verticalPadding, right: r.horizontalPadding)
|
|
133
|
+
NSLayoutConstraint.deactivate(dimensions)
|
|
134
|
+
dimensions.removeAll(keepingCapacity: true)
|
|
135
|
+
if let source = r.visual {
|
|
136
|
+
let view = visual ?? NativeListLeadingVisual(frame: .zero)
|
|
137
|
+
visual = view
|
|
138
|
+
if view.superview == nil { root.insertArrangedSubview(view, at: 0) }
|
|
139
|
+
view.bind(source, style: r.imageStyle, key: item.key, theme: theme, isUnread: false)
|
|
140
|
+
dimensions = [
|
|
141
|
+
view.widthAnchor.constraint(equalToConstant: r.imageWidth),
|
|
142
|
+
view.heightAnchor.constraint(equalToConstant: r.imageHeight),
|
|
143
|
+
]
|
|
144
|
+
NSLayoutConstraint.activate(dimensions)
|
|
145
|
+
} else if let visual {
|
|
146
|
+
visual.recycle()
|
|
147
|
+
root.removeArrangedSubview(visual)
|
|
148
|
+
visual.removeFromSuperview()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
override func recycleContent() { visual?.recycle() }
|
|
152
|
+
}
|