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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +21 -5
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
  5. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
  6. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
  12. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
  16. package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
  18. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
  19. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
  20. package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
  21. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
  22. package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
  23. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
  24. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
  25. package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
  26. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
  27. package/docs/DESIGN.md +518 -9
  28. package/docs/MIGRATION_BASELINE.md +117 -0
  29. package/docs/ROW_TEMPLATES.md +359 -0
  30. package/docs/SPEC.md +358 -0
  31. package/docs/STYLE_SPEC.md +978 -0
  32. package/docs/images/list-capabilities.svg +39 -0
  33. package/docs/images/row-action.svg +17 -0
  34. package/docs/images/row-activity.svg +24 -0
  35. package/docs/images/row-data.svg +24 -0
  36. package/docs/images/row-identity.svg +25 -0
  37. package/docs/images/row-market.svg +23 -0
  38. package/docs/images/row-media-tile.svg +26 -0
  39. package/docs/images/row-message.svg +20 -0
  40. package/docs/images/row-metric-card.svg +33 -0
  41. package/docs/images/row-rail.svg +18 -0
  42. package/docs/images/row-section-header.svg +19 -0
  43. package/docs/images/row-style-regions.svg +30 -0
  44. package/docs/images/row-system.svg +30 -0
  45. package/docs/images/row-wallet-group.svg +26 -0
  46. package/ios/NativeListAccessoryStack.swift +680 -0
  47. package/ios/NativeListActionRenderer.swift +108 -0
  48. package/ios/NativeListActivityRenderer.swift +179 -0
  49. package/ios/NativeListDataRowRenderer.swift +444 -0
  50. package/ios/NativeListIdentityRenderer.swift +685 -0
  51. package/ios/NativeListImageSlot.swift +140 -0
  52. package/ios/NativeListLeadingVisual.swift +372 -0
  53. package/ios/NativeListMarketRenderer.swift +698 -0
  54. package/ios/NativeListMediaTileRenderer.swift +184 -0
  55. package/ios/NativeListMessageRenderer.swift +168 -0
  56. package/ios/NativeListMetricCardRenderer.swift +420 -0
  57. package/ios/NativeListModels.swift +32 -0
  58. package/ios/NativeListRailRenderer.swift +152 -0
  59. package/ios/NativeListRendererCell.swift +311 -0
  60. package/ios/NativeListRendererRegistry.swift +50 -0
  61. package/ios/NativeListResolvedText.swift +122 -0
  62. package/ios/NativeListRowHost.swift +63 -0
  63. package/ios/NativeListSectionHeaderRenderer.swift +708 -0
  64. package/ios/NativeListSystemRenderer.swift +568 -0
  65. package/ios/NativeListTextLabel.swift +42 -0
  66. package/ios/NativeListTextStyles.swift +236 -0
  67. package/ios/NativeListWalletGroupRenderer.swift +255 -0
  68. package/ios/RNCNativeListView.swift +111 -180
  69. package/lib/module/models.js +54 -0
  70. package/lib/module/validation.js +324 -26
  71. package/lib/module/web/NativeListWebEngine.js +148 -936
  72. package/lib/module/web/templates/ActionRowRenderer.js +97 -0
  73. package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
  74. package/lib/module/web/templates/DataRowRenderer.js +145 -0
  75. package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
  76. package/lib/module/web/templates/MarketRowRenderer.js +310 -0
  77. package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
  78. package/lib/module/web/templates/MessageRowRenderer.js +165 -0
  79. package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
  80. package/lib/module/web/templates/RailRowRenderer.js +98 -0
  81. package/lib/module/web/templates/RowContainerStyle.js +100 -0
  82. package/lib/module/web/templates/RowElements.js +93 -0
  83. package/lib/module/web/templates/RowRendererRegistry.js +70 -0
  84. package/lib/module/web/templates/RowText.js +121 -0
  85. package/lib/module/web/templates/RowVisual.js +43 -0
  86. package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
  87. package/lib/module/web/templates/SystemRowRenderer.js +161 -0
  88. package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
  89. package/lib/typescript/src/models.d.ts +166 -13
  90. package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
  91. package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
  92. package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
  93. package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
  94. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
  95. package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
  96. package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
  97. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
  98. package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
  99. package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
  100. package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
  101. package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
  102. package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
  103. package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
  104. package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
  105. package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
  106. package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
  107. package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
  108. package/package.json +3 -3
  109. package/src/models.ts +281 -19
  110. package/src/validation.ts +501 -46
  111. package/src/web/NativeListWebEngine.ts +361 -1932
  112. package/src/web/templates/ActionRowRenderer.ts +123 -0
  113. package/src/web/templates/ActivityRowRenderer.ts +159 -0
  114. package/src/web/templates/DataRowRenderer.ts +212 -0
  115. package/src/web/templates/IdentityRowRenderer.ts +527 -0
  116. package/src/web/templates/MarketRowRenderer.ts +467 -0
  117. package/src/web/templates/MediaTileRowRenderer.ts +182 -0
  118. package/src/web/templates/MessageRowRenderer.ts +306 -0
  119. package/src/web/templates/MetricCardRowRenderer.ts +337 -0
  120. package/src/web/templates/RailRowRenderer.ts +136 -0
  121. package/src/web/templates/RowContainerStyle.ts +131 -0
  122. package/src/web/templates/RowElements.ts +160 -0
  123. package/src/web/templates/RowRendererRegistry.ts +111 -0
  124. package/src/web/templates/RowText.ts +181 -0
  125. package/src/web/templates/RowVisual.ts +115 -0
  126. package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
  127. package/src/web/templates/SystemRowRenderer.ts +282 -0
  128. package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
  129. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
  130. package/ios/NativeListCell.swift +0 -4300
@@ -0,0 +1,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
+ }