@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,487 @@
1
+ package com.margelo.nitro.nativelist
2
+
3
+ import android.graphics.drawable.GradientDrawable
4
+ import android.text.TextUtils
5
+ import android.view.Gravity
6
+ import android.widget.LinearLayout
7
+ import android.widget.TextView
8
+ import androidx.core.widget.TextViewCompat
9
+ import com.facebook.react.uimanager.ThemedReactContext
10
+ import kotlin.math.roundToInt
11
+ import org.json.JSONArray
12
+ import org.json.JSONObject
13
+
14
+ internal class NativeListDataRowView(context: ThemedReactContext) :
15
+ NativeListRendererRowView(context) {
16
+ private val visual = NativeListLeadingVisual(context)
17
+ private val favorite = OneKeyIconView(context)
18
+ private val accessories = NativeListAccessoryStack(context)
19
+ private val columns =
20
+ LinearLayout(context).apply {
21
+ orientation = HORIZONTAL
22
+ gravity = Gravity.CENTER_VERTICAL
23
+ }
24
+ private val cells = List(4) { NativeListTableColumnView(context) }
25
+
26
+ override fun unselectedBackground(
27
+ item: NativeListItem,
28
+ theme: JSONObject?,
29
+ layout: String,
30
+ itemIndex: Int?,
31
+ ): Int {
32
+ val index = if (item.json.has("index")) item.json.optInt("index") else itemIndex
33
+ return if (layout == "table" && index?.rem(2) == 0)
34
+ parseNativeListColor(theme?.optString("subduedBackground", "#F9F9F9") ?: "#F9F9F9")
35
+ else super.unselectedBackground(item, theme, layout, itemIndex)
36
+ }
37
+
38
+ override fun backgroundGroupPosition(
39
+ item: NativeListItem,
40
+ layout: String,
41
+ itemIndex: Int?,
42
+ selected: Boolean,
43
+ ): String {
44
+ val index = if (item.json.has("index")) item.json.optInt("index") else itemIndex
45
+ return if (layout == "table" && index?.rem(2) == 0 && !selected) ""
46
+ else super.backgroundGroupPosition(item, layout, itemIndex, selected)
47
+ }
48
+
49
+ override val assetFields = listOf("leading")
50
+
51
+ init {
52
+ cells.forEach(columns::addView)
53
+ addView(favorite)
54
+ addView(visual)
55
+ addView(accessories)
56
+ addView(columns, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
57
+ accessories.onAction = { key, view, slot, target ->
58
+ emitAction(key, view, "trailingAccessory", slot, target)
59
+ }
60
+ }
61
+
62
+ override fun defaultHeight(item: NativeListItem, layout: String): Int {
63
+ val values = item.json.optJSONArray("columns") ?: JSONArray()
64
+ val secondary =
65
+ (0 until values.length()).any {
66
+ values.getJSONObject(it).optString("secondaryText").isNotEmpty()
67
+ }
68
+ return if (layout == "table") if (secondary) 60 else 52 else if (secondary) 64 else 56
69
+ }
70
+
71
+ override fun bindContent(
72
+ item: NativeListItem,
73
+ theme: JSONObject?,
74
+ layout: String,
75
+ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
76
+ ) {
77
+ val data = item.json
78
+ val style = data.optJSONObject("style") ?: JSONObject()
79
+ val image = style.optJSONObject("image") ?: JSONObject()
80
+ orientation = HORIZONTAL
81
+ gravity =
82
+ when (style.optJSONObject("container")?.optString("contentVerticalAlignment")) {
83
+ "top" -> Gravity.TOP
84
+ "bottom" -> Gravity.BOTTOM
85
+ else -> Gravity.CENTER_VERTICAL
86
+ }
87
+ val hp =
88
+ if (style.has("horizontalPadding")) stylePx(style.optDouble("horizontalPadding"))
89
+ else dp(if (layout == "table") 20 else 12)
90
+ val vp =
91
+ if (style.has("verticalPadding")) stylePx(style.optDouble("verticalPadding"))
92
+ else dp(if (layout == "table") 10 else 8)
93
+ setPadding(hp, vp, hp, vp)
94
+ val active = data.optBoolean("favoriteActive")
95
+ favorite.visibility = if (active || data.optBoolean("favorite")) VISIBLE else GONE
96
+ favorite.iconName = if (active) "StarSolid" else "StarOutline"
97
+ favorite.useSourceScale = sourceScale
98
+ favorite.tintColor =
99
+ parseNativeListColor(
100
+ theme?.optString(
101
+ if (active) "icon" else "iconSubdued",
102
+ if (active) "#0000009B" else "#00000072",
103
+ ) ?: if (active) "#0000009B" else "#00000072"
104
+ )
105
+ favorite.layoutParams =
106
+ LayoutParams(dp(20), dp(20)).apply { marginEnd = dp(if (layout == "table") 8 else 12) }
107
+ val leading = data.optJSONObject("leading")
108
+ visual.visibility = if (leading == null) GONE else VISIBLE
109
+ visual.layoutParams =
110
+ LayoutParams(
111
+ if (image.has("width")) stylePx(image.optDouble("width")) else dp(40),
112
+ if (image.has("height")) stylePx(image.optDouble("height")) else dp(40),
113
+ )
114
+ .apply {
115
+ marginEnd =
116
+ if (style.has("leadingGap")) stylePx(style.optDouble("leadingGap"))
117
+ else dp(if (layout == "table") 10 else 12)
118
+ }
119
+ if (leading != null) visual.bind(leading, image, item.key, theme, false, sourceScale)
120
+ else visual.recycle()
121
+ val descriptors = JSONArray()
122
+ if (data.has("index"))
123
+ descriptors.put(
124
+ JSONObject().put("kind", "value").put("text", data.optInt("index").toString())
125
+ )
126
+ data.optJSONObject("checkbox")?.let(descriptors::put)
127
+ val accessoryStyle = JSONObject().put("value", style.optJSONObject("index"))
128
+ accessories.bind(item, descriptors, theme, accessoryStyle, sourceScale, checkboxState)
129
+ accessories.visibility = if (descriptors.length() == 0) GONE else VISIBLE
130
+ accessories.layoutParams =
131
+ LayoutParams(dp(32), LayoutParams.WRAP_CONTENT).apply { marginEnd = dp(10) }
132
+ val values = data.getJSONArray("columns")
133
+ // Only the keys NativeListTableColumnView consumes switch a row off the legacy
134
+ // single-label / fixed-geometry text layout; container, padding, image and
135
+ // index styles keep legacy text rendering.
136
+ val unstyled = TEXT_STYLE_KEYS.none(style::has)
137
+ cells.forEachIndexed { index, cell ->
138
+ cell.visibility = if (index < values.length()) VISIBLE else GONE
139
+ if (index < values.length()) {
140
+ val value = values.getJSONObject(index)
141
+ val infoColor = parseNativeListColor(theme?.optString("info", "#006DCBF2") ?: "#006DCBF2")
142
+ if (unstyled && layout != "table")
143
+ // Legacy linear default: one label per column with appended spans.
144
+ cell.bindLegacyLinear(
145
+ value,
146
+ if (index == 0) data.optJSONArray("badges") else null,
147
+ dataColor(value.optString("tone"), theme),
148
+ dataColor(value.optString("secondaryTone", "secondary"), theme),
149
+ infoColor,
150
+ )
151
+ else
152
+ cell.bind(
153
+ value,
154
+ if (index == 0) data.optJSONArray("badges") else null,
155
+ dataColor(value.optString("tone"), theme),
156
+ dataColor(value.optString("secondaryTone", "secondary"), theme),
157
+ infoColor,
158
+ if (layout == "table") 14f else 16f,
159
+ style,
160
+ fixedTableGeometry = unstyled,
161
+ )
162
+ // Legacy table default: every column is a fixed 40dp, top-aligned block.
163
+ cell.layoutParams =
164
+ LayoutParams(
165
+ 0,
166
+ if (unstyled && layout == "table") dp(40) else LayoutParams.WRAP_CONTENT,
167
+ value.optInt("weight", 1).toFloat(),
168
+ )
169
+ } else cell.reset()
170
+ }
171
+ }
172
+
173
+ private fun dataColor(tone: String, theme: JSONObject?): Int {
174
+ // Legacy tone mapping: any other tone renders as primaryText.
175
+ val token =
176
+ when (tone) {
177
+ "positive" -> "positive"
178
+ "negative" -> "negative"
179
+ "secondary" -> "secondaryText"
180
+ else -> "primaryText"
181
+ }
182
+ val fallback =
183
+ when (token) {
184
+ "positive" -> "#00713FDE"
185
+ "negative" -> "#C40006D3"
186
+ "secondaryText" -> "#0000009B"
187
+ else -> "#000000DF"
188
+ }
189
+ return parseNativeListColor(theme?.optString(token, fallback) ?: fallback)
190
+ }
191
+
192
+ override fun bindSelectionContent(
193
+ item: NativeListItem,
194
+ checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
195
+ ) {
196
+ accessories.updateSelection(item, checkboxState)
197
+ }
198
+
199
+ override fun recycleContent() {
200
+ visual.recycle()
201
+ accessories.reset()
202
+ cells.forEach { it.reset() }
203
+ }
204
+
205
+ override fun disposeContent() {
206
+ visual.dispose()
207
+ accessories.reset()
208
+ }
209
+
210
+ private companion object {
211
+ val TEXT_STYLE_KEYS = listOf("columns", "columnSecondary", "lineGap", "titleBadgeGap")
212
+ }
213
+ }
214
+
215
+ private class NativeListTableColumnView(context: android.content.Context) : LinearLayout(context) {
216
+ private val primaryLine = LinearLayout(context)
217
+ private val primary = NativeListTextView(context)
218
+ private val badges = LinearLayout(context)
219
+ private val secondaryLine = LinearLayout(context)
220
+ private val secondaryLeading = NativeListTextView(context)
221
+ private val secondary = NativeListTextView(context)
222
+
223
+ init {
224
+ orientation = VERTICAL
225
+ primaryLine.orientation = HORIZONTAL
226
+ primaryLine.gravity = Gravity.CENTER_VERTICAL
227
+ badges.orientation = HORIZONTAL
228
+ badges.gravity = Gravity.CENTER_VERTICAL
229
+ primary.includeFontPadding = false
230
+ primary.fontFeatureSettings = "tnum"
231
+ primary.maxLines = 1
232
+ primary.ellipsize = TextUtils.TruncateAt.END
233
+ primary.textSize = sp(14f)
234
+ primary.typeface = NativeListFonts.medium(context)
235
+ TextViewCompat.setLineHeight(primary, dp(20))
236
+ secondaryLine.orientation = HORIZONTAL
237
+ secondaryLine.gravity = Gravity.CENTER_VERTICAL
238
+ listOf(secondaryLeading, secondary).forEach { label ->
239
+ label.includeFontPadding = false
240
+ label.fontFeatureSettings = "tnum"
241
+ label.maxLines = 1
242
+ label.ellipsize = TextUtils.TruncateAt.END
243
+ label.textSize = sp(12f)
244
+ label.typeface = NativeListFonts.regular(context)
245
+ TextViewCompat.setLineHeight(label, dp(16))
246
+ }
247
+ secondaryLeading.maxWidth = dp(120)
248
+ secondaryLine.addView(
249
+ secondaryLeading,
250
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT),
251
+ )
252
+ secondaryLine.addView(
253
+ secondary,
254
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
255
+ marginStart = dp(4)
256
+ },
257
+ )
258
+ primaryLine.addView(primary, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
259
+ primaryLine.addView(
260
+ badges,
261
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
262
+ marginStart = dp(6)
263
+ },
264
+ )
265
+ addView(primaryLine, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT))
266
+ addView(
267
+ secondaryLine,
268
+ LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply { topMargin = dp(4) },
269
+ )
270
+ }
271
+
272
+ fun reset() {
273
+ primaryLine.layoutParams.width = LayoutParams.WRAP_CONTENT
274
+ primaryLine.layoutParams.height = LayoutParams.WRAP_CONTENT
275
+ secondaryLine.layoutParams.height = LayoutParams.WRAP_CONTENT
276
+ badges.layoutParams.height = LayoutParams.WRAP_CONTENT
277
+ secondaryLeading.layoutParams.height = LayoutParams.WRAP_CONTENT
278
+ primary.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
279
+ secondary.layoutParams =
280
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
281
+ marginStart = dp(4)
282
+ }
283
+ (secondaryLine.layoutParams as MarginLayoutParams).topMargin = dp(4)
284
+ (badges.layoutParams as MarginLayoutParams).marginStart = dp(6)
285
+ primary.text = ""
286
+ secondaryLeading.text = ""
287
+ secondary.text = ""
288
+ secondaryLeading.visibility = GONE
289
+ secondary.visibility = GONE
290
+ secondaryLine.visibility = GONE
291
+ badges.removeAllViews()
292
+ badges.visibility = GONE
293
+ }
294
+
295
+ fun bind(
296
+ column: JSONObject,
297
+ rowBadges: JSONArray?,
298
+ primaryColor: Int,
299
+ secondaryColor: Int,
300
+ infoColor: Int,
301
+ primarySize: Float,
302
+ style: JSONObject,
303
+ fixedTableGeometry: Boolean = false,
304
+ ) {
305
+ reset()
306
+ gravity =
307
+ when (column.optString("alignment", "start")) {
308
+ "center" -> Gravity.CENTER_HORIZONTAL
309
+ "end" -> Gravity.END
310
+ else -> Gravity.START
311
+ }
312
+ fun bindText(
313
+ view: NativeListTextView,
314
+ value: String,
315
+ slot: String,
316
+ size: Float,
317
+ weight: String,
318
+ color: Int,
319
+ line: Int,
320
+ ) {
321
+ val resolved =
322
+ JSONObject(style.optJSONObject(slot)?.toString() ?: "{}").apply {
323
+ if (!has("alignment")) put("alignment", column.optString("alignment", "start"))
324
+ }
325
+ NativeListResolvedText.resolve(context, value, resolved, size, weight, color, line, 1, false)
326
+ .bind(view)
327
+ if (resolved.optString("truncate") != "clip") view.ellipsize = TextUtils.TruncateAt.END
328
+ }
329
+ bindText(primary, column.optString("text"), "columns", primarySize, "medium", primaryColor, 20)
330
+ bindText(
331
+ secondaryLeading,
332
+ column.optString("secondaryLeadingText"),
333
+ "columnSecondary",
334
+ 12f,
335
+ "regular",
336
+ secondaryColor,
337
+ 16,
338
+ )
339
+ bindText(
340
+ secondary,
341
+ column.optString("secondaryText"),
342
+ "columnSecondary",
343
+ 12f,
344
+ "regular",
345
+ secondaryColor,
346
+ 16,
347
+ )
348
+ if (rowBadges != null && rowBadges.length() > 0) {
349
+ badges.visibility = VISIBLE
350
+ for (index in 0 until minOf(2, rowBadges.length())) {
351
+ val badge =
352
+ TextView(context).apply {
353
+ includeFontPadding = false
354
+ fontFeatureSettings = "tnum"
355
+ gravity = Gravity.CENTER
356
+ text = rowBadges.getJSONObject(index).optString("text")
357
+ textSize = sp(10f)
358
+ typeface = NativeListFonts.regular(context)
359
+ setTextColor(infoColor)
360
+ setPadding(dp(6), 0, dp(6), 0)
361
+ background =
362
+ GradientDrawable().apply {
363
+ setColor(parseNativeListColor("#008FF519"))
364
+ cornerRadius = dp(4).toFloat()
365
+ }
366
+ }
367
+ badges.addView(
368
+ badge,
369
+ LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
370
+ if (index > 0) marginStart = dp(4)
371
+ },
372
+ )
373
+ }
374
+ }
375
+ val secondaryLeadingText = column.optString("secondaryLeadingText")
376
+ if (secondaryLeadingText.isNotEmpty()) {
377
+ secondaryLeading.visibility = VISIBLE
378
+ secondaryLine.visibility = VISIBLE
379
+ }
380
+ val secondaryText = column.optString("secondaryText")
381
+ if (secondaryText.isNotEmpty()) {
382
+ secondary.visibility = VISIBLE
383
+ secondaryLine.visibility = VISIBLE
384
+ }
385
+ if (!fixedTableGeometry) applyStyle(style)
386
+ if (fixedTableGeometry) {
387
+ // Legacy table lines: fixed 20dp primary and 16dp secondary/badge boxes.
388
+ primaryLine.layoutParams.height = dp(20)
389
+ primary.layoutParams.height = dp(20)
390
+ badges.layoutParams.height = dp(16)
391
+ for (index in 0 until badges.childCount) badges.getChildAt(index).layoutParams.height = dp(16)
392
+ secondaryLine.layoutParams.height = dp(16)
393
+ secondaryLeading.layoutParams.height = dp(16)
394
+ secondary.layoutParams.height = dp(16)
395
+ }
396
+ }
397
+
398
+ fun bindLegacyLinear(
399
+ column: JSONObject,
400
+ rowBadges: JSONArray?,
401
+ primaryColor: Int,
402
+ secondaryColor: Int,
403
+ infoColor: Int,
404
+ ) {
405
+ reset()
406
+ gravity = Gravity.START
407
+ val value = android.text.SpannableStringBuilder(column.optString("text"))
408
+ value.setSpan(
409
+ android.text.style.ForegroundColorSpan(primaryColor),
410
+ 0,
411
+ value.length,
412
+ android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
413
+ )
414
+ if (rowBadges != null)
415
+ for (index in 0 until minOf(2, rowBadges.length())) {
416
+ val start = value.length
417
+ value.append(" ${rowBadges.getJSONObject(index).optString("text")} ")
418
+ value.setSpan(
419
+ android.text.style.ForegroundColorSpan(infoColor),
420
+ start,
421
+ value.length,
422
+ android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
423
+ )
424
+ value.setSpan(
425
+ android.text.style.BackgroundColorSpan(parseNativeListColor("#008FF519")),
426
+ start,
427
+ value.length,
428
+ android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
429
+ )
430
+ }
431
+ val secondaryText = column.optString("secondaryText")
432
+ if (secondaryText.isNotEmpty()) {
433
+ val start = value.length
434
+ value.append("\n$secondaryText")
435
+ value.setSpan(
436
+ android.text.style.ForegroundColorSpan(secondaryColor),
437
+ start,
438
+ value.length,
439
+ android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
440
+ )
441
+ }
442
+ primaryLine.layoutParams.width = LayoutParams.MATCH_PARENT
443
+ primary.layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
444
+ primary.includeFontPadding = true
445
+ primary.fontFeatureSettings = "tnum"
446
+ primary.textSize = sp(16f)
447
+ primary.typeface = NativeListFonts.medium(context)
448
+ primary.setLineSpacing(0f, 1f)
449
+ primary.letterSpacing = 0f
450
+ primary.opticalOffsetY = 0f
451
+ primary.setHorizontallyScrolling(false)
452
+ primary.ellipsize = null
453
+ primary.maxLines = if (secondaryText.isEmpty()) 1 else 2
454
+ primary.gravity =
455
+ when (column.optString("alignment", "start")) {
456
+ "center" -> Gravity.CENTER
457
+ "end" -> Gravity.END
458
+ else -> Gravity.START
459
+ }
460
+ primary.text = value
461
+ primary.visibility = VISIBLE
462
+ }
463
+
464
+ private fun applyStyle(style: JSONObject) {
465
+ style.optJSONObject("columns")?.let {
466
+ if (it.has("alignment")) {
467
+ primaryLine.layoutParams.width = LayoutParams.MATCH_PARENT
468
+ primary.layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
469
+ }
470
+ }
471
+ style.optJSONObject("columnSecondary")?.let {
472
+ if (it.has("alignment"))
473
+ secondary.layoutParams =
474
+ LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f).apply { marginStart = dp(4) }
475
+ }
476
+ (secondaryLine.layoutParams as MarginLayoutParams).topMargin =
477
+ style.optDouble("lineGap", 4.0).let { (it * resources.displayMetrics.density).roundToInt() }
478
+ (badges.layoutParams as MarginLayoutParams).marginStart =
479
+ style.optDouble("titleBadgeGap", 6.0).let {
480
+ (it * resources.displayMetrics.density).roundToInt()
481
+ }
482
+ }
483
+
484
+ private fun dp(value: Int): Int = NativeListScale.dp(resources, value)
485
+
486
+ private fun sp(value: Float): Float = NativeListScale.font(resources, value)
487
+ }