@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,184 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
final class NativeListMediaTileCell: NativeListRendererCell {
|
|
4
|
+
override class func measure(
|
|
5
|
+
_ item: NativeListItem, width: CGFloat, theme: [String: Any]?, layout: String
|
|
6
|
+
) -> CGFloat? {
|
|
7
|
+
return 244
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
private let picture = UIView()
|
|
11
|
+
private let image = NativeListImageSlot()
|
|
12
|
+
private let network = NativeListImageSlot()
|
|
13
|
+
private let errorIcon = UIImageView()
|
|
14
|
+
private let title = NativeListTextLabel()
|
|
15
|
+
private let subtitle = NativeListTextLabel()
|
|
16
|
+
private let badge = NativeListTextLabel()
|
|
17
|
+
private let metadata = UIStackView()
|
|
18
|
+
private let subtitleLine = UIStackView()
|
|
19
|
+
private let close = UIButton(type: .system)
|
|
20
|
+
private let closeStack = UIStackView()
|
|
21
|
+
private let titleLine = UIStackView()
|
|
22
|
+
private var closeKey = ""
|
|
23
|
+
private var ownsImageFill = false
|
|
24
|
+
private var imageStyle: [String: Any] = [:]
|
|
25
|
+
private var imageWidth: NSLayoutConstraint!
|
|
26
|
+
private var imageHeight: NSLayoutConstraint!
|
|
27
|
+
override var defaultCornerRadius: CGFloat { 16 }
|
|
28
|
+
override var showsSelection: Bool { false }
|
|
29
|
+
override var pressChangesBackground: Bool { false }
|
|
30
|
+
override var assetFields: [String] { ["image", "networkImage"] }
|
|
31
|
+
override var isHighlighted: Bool { didSet { picture.alpha = isHighlighted ? 0.8 : 1 } }
|
|
32
|
+
|
|
33
|
+
override init(frame: CGRect) {
|
|
34
|
+
super.init(frame: frame)
|
|
35
|
+
root.axis = .vertical
|
|
36
|
+
root.alignment = .fill
|
|
37
|
+
metadata.axis = .vertical
|
|
38
|
+
metadata.alignment = .fill
|
|
39
|
+
subtitleLine.axis = .horizontal
|
|
40
|
+
subtitleLine.alignment = .center
|
|
41
|
+
subtitleLine.spacing = 8
|
|
42
|
+
[subtitle, network.view].forEach(subtitleLine.addArrangedSubview)
|
|
43
|
+
titleLine.axis = .horizontal
|
|
44
|
+
titleLine.alignment = .center
|
|
45
|
+
titleLine.addArrangedSubview(title)
|
|
46
|
+
[subtitleLine, titleLine].forEach(metadata.addArrangedSubview)
|
|
47
|
+
closeStack.axis = .vertical
|
|
48
|
+
closeStack.alignment = .trailing
|
|
49
|
+
closeStack.addArrangedSubview(close)
|
|
50
|
+
[picture, metadata, closeStack].forEach(root.addArrangedSubview)
|
|
51
|
+
picture.translatesAutoresizingMaskIntoConstraints = false
|
|
52
|
+
[image.view, errorIcon, badge].forEach {
|
|
53
|
+
picture.addSubview($0)
|
|
54
|
+
$0.translatesAutoresizingMaskIntoConstraints = false
|
|
55
|
+
}
|
|
56
|
+
imageWidth = image.view.widthAnchor.constraint(equalToConstant: 0)
|
|
57
|
+
imageHeight = picture.heightAnchor.constraint(equalToConstant: 160)
|
|
58
|
+
NSLayoutConstraint.activate([
|
|
59
|
+
image.view.leadingAnchor.constraint(equalTo: picture.leadingAnchor),
|
|
60
|
+
image.view.topAnchor.constraint(equalTo: picture.topAnchor),
|
|
61
|
+
image.view.heightAnchor.constraint(equalTo: picture.heightAnchor), imageWidth, imageHeight,
|
|
62
|
+
errorIcon.centerXAnchor.constraint(equalTo: image.view.centerXAnchor),
|
|
63
|
+
errorIcon.centerYAnchor.constraint(equalTo: image.view.centerYAnchor),
|
|
64
|
+
errorIcon.widthAnchor.constraint(equalToConstant: 24),
|
|
65
|
+
errorIcon.heightAnchor.constraint(equalToConstant: 24),
|
|
66
|
+
badge.trailingAnchor.constraint(equalTo: image.view.trailingAnchor),
|
|
67
|
+
badge.bottomAnchor.constraint(equalTo: image.view.bottomAnchor),
|
|
68
|
+
badge.heightAnchor.constraint(equalToConstant: 24),
|
|
69
|
+
network.view.widthAnchor.constraint(equalToConstant: 14),
|
|
70
|
+
network.view.heightAnchor.constraint(equalToConstant: 14),
|
|
71
|
+
])
|
|
72
|
+
errorIcon.contentMode = .scaleAspectFit
|
|
73
|
+
badge.textAlignment = .center
|
|
74
|
+
badge.layer.cornerRadius = 10
|
|
75
|
+
badge.layer.borderWidth = 2
|
|
76
|
+
badge.clipsToBounds = true
|
|
77
|
+
network.view.layer.cornerRadius = 7
|
|
78
|
+
network.view.clipsToBounds = true
|
|
79
|
+
subtitle.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
|
80
|
+
close.contentHorizontalAlignment = .trailing
|
|
81
|
+
close.addTarget(self, action: #selector(closePressed), for: .touchUpInside)
|
|
82
|
+
}
|
|
83
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
84
|
+
|
|
85
|
+
override func bindContent(
|
|
86
|
+
_ item: NativeListItem, theme: [String: Any]?, layout: String,
|
|
87
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
88
|
+
) {
|
|
89
|
+
let style = item.data.dictionary("style") ?? [:]
|
|
90
|
+
imageStyle = style.dictionary("image") ?? [:]
|
|
91
|
+
let hp = CGFloat(style.double("horizontalPadding", default: 10))
|
|
92
|
+
let vp = CGFloat(style.double("verticalPadding", default: 10))
|
|
93
|
+
contentInsets = UIEdgeInsets(top: vp, left: hp, bottom: vp, right: hp)
|
|
94
|
+
root.spacing = 8
|
|
95
|
+
root.setCustomSpacing(CGFloat(style.double("leadingGap", default: 8)), after: picture)
|
|
96
|
+
metadata.spacing = CGFloat(style.double("lineGap", default: 2))
|
|
97
|
+
NativeListResolvedText(
|
|
98
|
+
item.data.string("title"), style: style.dictionary("title"), size: 16,
|
|
99
|
+
weight: .medium, color: nativeListColor(theme, "primaryText", "#202020"),
|
|
100
|
+
lineHeight: nativeListFont(ofSize: 16, weight: .medium).lineHeight,
|
|
101
|
+
lines: 1, preservesBreakMode: true
|
|
102
|
+
).bind(title)
|
|
103
|
+
NativeListResolvedText(
|
|
104
|
+
item.data.string("subtitle"), style: style.dictionary("subtitle"), size: 12,
|
|
105
|
+
color: nativeListColor(theme, "secondaryText", "#646464"),
|
|
106
|
+
lineHeight: nativeListFont(ofSize: 12).lineHeight, lines: 1, preservesBreakMode: true
|
|
107
|
+
).bind(subtitle)
|
|
108
|
+
let badgeText = item.data.dictionary("badge")?.string("text") ?? ""
|
|
109
|
+
NativeListResolvedText(
|
|
110
|
+
badgeText.isEmpty ? "" : " \(badgeText) ", style: style.dictionary("badge"), size: 14,
|
|
111
|
+
weight: .medium, color: nativeListColor(theme, "inverseText", "#FCFCFC"),
|
|
112
|
+
lineHeight: nativeListFont(ofSize: 14, weight: .medium).lineHeight,
|
|
113
|
+
lines: 1, preservesBreakMode: true
|
|
114
|
+
).bind(badge)
|
|
115
|
+
if style.dictionary("badge")?["alignment"] == nil { badge.textAlignment = .center }
|
|
116
|
+
badge.backgroundColor = nativeListColor(theme, "inverseBackground", "#202020")
|
|
117
|
+
badge.layer.borderColor = nativeListColor(theme, "rowBackground", "#FFFFFF").cgColor
|
|
118
|
+
let state = item.data.string("imageState")
|
|
119
|
+
let placeholder = theme?.string("strongBackground", default: "#0000000F") ?? "#0000000F"
|
|
120
|
+
errorIcon.isHidden = state != "error"
|
|
121
|
+
errorIcon.image = nativeListIcon(named: "ImageSquareWavesOutline")
|
|
122
|
+
errorIcon.tintColor = UIColor(nativeListHex: "#00000044", fallback: .lightGray)
|
|
123
|
+
if state != "empty", state != "error", let source = item.data.dictionary("image") {
|
|
124
|
+
// Legacy source-backed media had a transparent container. Clear only a fill this
|
|
125
|
+
// renderer applied, so OneKeyImage keeps ownership of its own placeholder color.
|
|
126
|
+
if ownsImageFill {
|
|
127
|
+
image.view.backgroundColor = .clear
|
|
128
|
+
ownsImageFill = false
|
|
129
|
+
}
|
|
130
|
+
image.bind(
|
|
131
|
+
source, key: "\(item.key):media", fit: imageStyle["contentFit"] as? String,
|
|
132
|
+
placeholder: placeholder)
|
|
133
|
+
} else {
|
|
134
|
+
image.recycle()
|
|
135
|
+
image.view.backgroundColor =
|
|
136
|
+
state == "error" ? nativeListColor(theme, "strongBackground", "#0000000F") : .clear
|
|
137
|
+
ownsImageFill = true
|
|
138
|
+
}
|
|
139
|
+
if let source = item.data.dictionary("networkImage") {
|
|
140
|
+
// Legacy hid the network badge until it loaded, so it takes no layout space before.
|
|
141
|
+
network.view.isHidden = true
|
|
142
|
+
network.view.alpha = 1
|
|
143
|
+
network.bind(
|
|
144
|
+
source, key: "\(item.key):network", variant: "network", placeholder: placeholder
|
|
145
|
+
) { [weak self] loaded in
|
|
146
|
+
self?.network.view.isHidden = !loaded
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
network.recycle()
|
|
150
|
+
network.view.isHidden = true
|
|
151
|
+
}
|
|
152
|
+
closeKey = item.data.string("closeActionKey")
|
|
153
|
+
closeStack.isHidden = closeKey.isEmpty
|
|
154
|
+
close.setTitle("×", for: .normal)
|
|
155
|
+
close.titleLabel?.font = nativeListFont(ofSize: 16, weight: .medium)
|
|
156
|
+
close.setTitleColor(nativeListColor(theme, "primaryText", "#202020"), for: .normal)
|
|
157
|
+
}
|
|
158
|
+
override func layoutSubviews() {
|
|
159
|
+
let width = CGFloat(
|
|
160
|
+
imageStyle.double(
|
|
161
|
+
"width",
|
|
162
|
+
default: Double(max(0, contentView.bounds.width - contentInsets.left - contentInsets.right))
|
|
163
|
+
))
|
|
164
|
+
imageWidth.constant = width
|
|
165
|
+
imageHeight.constant = CGFloat(imageStyle.double("height", default: Double(width)))
|
|
166
|
+
let shape = imageStyle.string("shape", default: "rounded")
|
|
167
|
+
image.view.layer.cornerRadius = CGFloat(
|
|
168
|
+
imageStyle.double(
|
|
169
|
+
"cornerRadius",
|
|
170
|
+
default: shape == "circle"
|
|
171
|
+
? Double(min(width, imageHeight.constant) / 2) : shape == "square" ? 0 : 10))
|
|
172
|
+
image.view.clipsToBounds = true
|
|
173
|
+
super.layoutSubviews()
|
|
174
|
+
}
|
|
175
|
+
@objc private func closePressed() {
|
|
176
|
+
emitAction(closeKey, from: close, source: "mediaClose", slot: 0)
|
|
177
|
+
}
|
|
178
|
+
override func recycleContent() {
|
|
179
|
+
image.recycle()
|
|
180
|
+
network.recycle()
|
|
181
|
+
closeKey = ""
|
|
182
|
+
picture.alpha = 1
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import UIKit
|
|
2
|
+
|
|
3
|
+
enum NativeListMessageRenderer {
|
|
4
|
+
struct Resolved {
|
|
5
|
+
let title: NativeListResolvedText
|
|
6
|
+
let body: NativeListResolvedText
|
|
7
|
+
let time: NativeListResolvedText
|
|
8
|
+
let horizontalPadding: CGFloat
|
|
9
|
+
let verticalPadding: CGFloat
|
|
10
|
+
let gap: CGFloat
|
|
11
|
+
let timeInset: CGFloat
|
|
12
|
+
let leadingGap: CGFloat
|
|
13
|
+
let imageWidth: CGFloat
|
|
14
|
+
let imageHeight: CGFloat
|
|
15
|
+
let imageStyle: [String: Any]
|
|
16
|
+
let leading: [String: Any]?
|
|
17
|
+
let thumbnail: [String: Any]?
|
|
18
|
+
let unread: Bool
|
|
19
|
+
let alignment: UIStackView.Alignment
|
|
20
|
+
|
|
21
|
+
init(_ item: NativeListItem, theme: [String: Any]?, layout: String) {
|
|
22
|
+
let style = item.data.dictionary("style") ?? [:]
|
|
23
|
+
title = NativeListResolvedText(
|
|
24
|
+
item.data.string("title"), style: style.dictionary("title"), size: 14, weight: .semibold,
|
|
25
|
+
color: nativeListColor(theme, "primaryText", "#202020"), lineHeight: 20, lines: 2)
|
|
26
|
+
body = NativeListResolvedText(
|
|
27
|
+
item.data.string("body"), style: style.dictionary("body"), size: 14,
|
|
28
|
+
color: nativeListColor(theme, "secondaryText", "#646464"), lineHeight: 20,
|
|
29
|
+
lines: item.data.int("bodyLines", default: 3))
|
|
30
|
+
time = NativeListResolvedText(
|
|
31
|
+
item.data.string("time"), style: style.dictionary("time"), size: 12,
|
|
32
|
+
color: nativeListColor(theme, "disabledText", "#8D8D8D"), lineHeight: 16, lines: 1,
|
|
33
|
+
breakMode: .byWordWrapping)
|
|
34
|
+
horizontalPadding = CGFloat(
|
|
35
|
+
style.double("horizontalPadding", default: layout == "table" ? 16 : 12))
|
|
36
|
+
verticalPadding = CGFloat(style.double("verticalPadding", default: 16))
|
|
37
|
+
gap = CGFloat(style.double("lineGap", default: 2))
|
|
38
|
+
timeInset = style["lineGap"] == nil ? 2 : 0
|
|
39
|
+
leadingGap = CGFloat(style.double("leadingGap", default: 12))
|
|
40
|
+
imageStyle = style.dictionary("image") ?? [:]
|
|
41
|
+
imageWidth = CGFloat(imageStyle.double("width", default: 28))
|
|
42
|
+
imageHeight = CGFloat(imageStyle.double("height", default: 28))
|
|
43
|
+
leading = item.data.dictionary("leading")
|
|
44
|
+
thumbnail = item.data.dictionary("thumbnail")
|
|
45
|
+
unread = item.data.bool("unread")
|
|
46
|
+
switch style.dictionary("container")?.string("contentVerticalAlignment") {
|
|
47
|
+
case "center": alignment = .center
|
|
48
|
+
case "bottom": alignment = .bottom
|
|
49
|
+
default: alignment = .top
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func measure(width: CGFloat) -> CGFloat {
|
|
54
|
+
let textWidth = max(
|
|
55
|
+
1,
|
|
56
|
+
width - horizontalPadding * 2 - (leading == nil ? 0 : imageWidth + leadingGap)
|
|
57
|
+
- (thumbnail == nil ? 0 : 76))
|
|
58
|
+
let fields = [title, body, time].filter { !$0.text.isEmpty }
|
|
59
|
+
let textHeight =
|
|
60
|
+
fields.reduce(CGFloat(0)) { $0 + $1.measure(width: textWidth) }
|
|
61
|
+
+ CGFloat(max(0, fields.count - 1)) * gap + (time.text.isEmpty ? 0 : timeInset)
|
|
62
|
+
return verticalPadding * 2
|
|
63
|
+
+ max(textHeight, leading == nil ? 0 : imageHeight, thumbnail == nil ? 0 : 64)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
final class Views {
|
|
68
|
+
let root: UIStackView
|
|
69
|
+
let column = UIStackView()
|
|
70
|
+
let title = NativeListTextLabel()
|
|
71
|
+
let body = NativeListTextLabel()
|
|
72
|
+
let time = NativeListInsetLabel()
|
|
73
|
+
private var leading: NativeListLeadingVisual?
|
|
74
|
+
private var thumbnail: NativeListImageSlot?
|
|
75
|
+
private var sizeConstraints: [NSLayoutConstraint] = []
|
|
76
|
+
|
|
77
|
+
init(root: UIStackView = UIStackView()) {
|
|
78
|
+
self.root = root
|
|
79
|
+
root.axis = .horizontal
|
|
80
|
+
root.spacing = 12
|
|
81
|
+
column.axis = .vertical
|
|
82
|
+
column.alignment = .fill
|
|
83
|
+
column.setContentHuggingPriority(UILayoutPriority(1), for: .horizontal)
|
|
84
|
+
column.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal)
|
|
85
|
+
title.setContentCompressionResistancePriority(.required, for: .horizontal)
|
|
86
|
+
[title, body, time].forEach(column.addArrangedSubview)
|
|
87
|
+
root.addArrangedSubview(column)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
func bind(_ resolved: Resolved, item: NativeListItem, theme: [String: Any]?) {
|
|
91
|
+
resolved.title.bind(title)
|
|
92
|
+
resolved.body.bind(body)
|
|
93
|
+
resolved.time.bind(time)
|
|
94
|
+
time.topInset = resolved.timeInset
|
|
95
|
+
time.invalidateIntrinsicContentSize()
|
|
96
|
+
column.spacing = resolved.gap
|
|
97
|
+
root.alignment = resolved.alignment
|
|
98
|
+
NSLayoutConstraint.deactivate(sizeConstraints)
|
|
99
|
+
sizeConstraints.removeAll(keepingCapacity: true)
|
|
100
|
+
if let data = resolved.leading {
|
|
101
|
+
let visual = leading ?? NativeListLeadingVisual(frame: .zero)
|
|
102
|
+
leading = visual
|
|
103
|
+
if visual.superview == nil { root.insertArrangedSubview(visual, at: 0) }
|
|
104
|
+
visual.bind(
|
|
105
|
+
data, style: resolved.imageStyle, key: item.key, theme: theme, isUnread: resolved.unread)
|
|
106
|
+
root.setCustomSpacing(resolved.leadingGap, after: visual)
|
|
107
|
+
sizeConstraints += [
|
|
108
|
+
visual.widthAnchor.constraint(equalToConstant: resolved.imageWidth),
|
|
109
|
+
visual.heightAnchor.constraint(equalToConstant: resolved.imageHeight),
|
|
110
|
+
]
|
|
111
|
+
} else if let leading {
|
|
112
|
+
leading.recycle()
|
|
113
|
+
root.removeArrangedSubview(leading)
|
|
114
|
+
leading.removeFromSuperview()
|
|
115
|
+
}
|
|
116
|
+
if let source = resolved.thumbnail {
|
|
117
|
+
let slot = thumbnail ?? NativeListImageSlot()
|
|
118
|
+
thumbnail = slot
|
|
119
|
+
if slot.view.superview == nil { root.addArrangedSubview(slot.view) }
|
|
120
|
+
slot.view.layer.cornerRadius = 6
|
|
121
|
+
slot.view.layer.borderWidth = 1 / UIScreen.main.scale
|
|
122
|
+
slot.view.layer.borderColor = UIColor(nativeListHex: "#0000000F", fallback: .clear).cgColor
|
|
123
|
+
sizeConstraints += [
|
|
124
|
+
slot.view.widthAnchor.constraint(equalToConstant: 64),
|
|
125
|
+
slot.view.heightAnchor.constraint(equalToConstant: 64),
|
|
126
|
+
]
|
|
127
|
+
slot.bind(
|
|
128
|
+
source, key: "\(item.key):thumbnail",
|
|
129
|
+
placeholder: theme?.string("strongBackground", default: "#0000000F") ?? "#0000000F")
|
|
130
|
+
} else if let thumbnail {
|
|
131
|
+
thumbnail.recycle()
|
|
132
|
+
root.removeArrangedSubview(thumbnail.view)
|
|
133
|
+
thumbnail.view.removeFromSuperview()
|
|
134
|
+
}
|
|
135
|
+
NSLayoutConstraint.activate(sizeConstraints)
|
|
136
|
+
}
|
|
137
|
+
func recycle() {
|
|
138
|
+
leading?.recycle()
|
|
139
|
+
thumbnail?.recycle()
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
final class NativeListMessageCell: NativeListRendererCell {
|
|
146
|
+
override class func measure(
|
|
147
|
+
_ item: NativeListItem, width: CGFloat, theme: [String: Any]?, layout: String
|
|
148
|
+
) -> CGFloat? {
|
|
149
|
+
return NativeListMessageRenderer.Resolved(item, theme: theme, layout: layout).measure(
|
|
150
|
+
width: width)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
override var assetFields: [String] { ["leading", "thumbnail"] }
|
|
154
|
+
private lazy var views = NativeListMessageRenderer.Views(root: root)
|
|
155
|
+
override init(frame: CGRect) { super.init(frame: frame) }
|
|
156
|
+
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
|
157
|
+
override func bindContent(
|
|
158
|
+
_ item: NativeListItem, theme: [String: Any]?, layout: String,
|
|
159
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String
|
|
160
|
+
) {
|
|
161
|
+
let resolved = NativeListMessageRenderer.Resolved(item, theme: theme, layout: layout)
|
|
162
|
+
views.bind(resolved, item: item, theme: theme)
|
|
163
|
+
contentInsets = UIEdgeInsets(
|
|
164
|
+
top: resolved.verticalPadding, left: resolved.horizontalPadding,
|
|
165
|
+
bottom: resolved.verticalPadding, right: resolved.horizontalPadding)
|
|
166
|
+
}
|
|
167
|
+
override func recycleContent() { views.recycle() }
|
|
168
|
+
}
|