@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,551 @@
|
|
|
1
|
+
package com.margelo.nitro.nativelist
|
|
2
|
+
|
|
3
|
+
import android.graphics.Color
|
|
4
|
+
import android.graphics.Outline
|
|
5
|
+
import android.graphics.Typeface
|
|
6
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
|
+
import android.text.TextUtils
|
|
8
|
+
import android.view.Gravity
|
|
9
|
+
import android.view.View
|
|
10
|
+
import android.view.ViewOutlineProvider
|
|
11
|
+
import android.widget.LinearLayout
|
|
12
|
+
import android.widget.TextView
|
|
13
|
+
import androidx.core.widget.TextViewCompat
|
|
14
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
15
|
+
import org.json.JSONArray
|
|
16
|
+
import org.json.JSONObject
|
|
17
|
+
|
|
18
|
+
internal class NativeListMetricCardRowView(context: ThemedReactContext) :
|
|
19
|
+
NativeListRendererRowView(context) {
|
|
20
|
+
private val visual = NativeListLeadingVisual(context)
|
|
21
|
+
private val column = LinearLayout(context).apply { orientation = VERTICAL }
|
|
22
|
+
private val titleLine =
|
|
23
|
+
LinearLayout(context).apply {
|
|
24
|
+
orientation = HORIZONTAL
|
|
25
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
26
|
+
}
|
|
27
|
+
private val title = NativeListTextView(context)
|
|
28
|
+
private val label = NativeListTextView(context)
|
|
29
|
+
private val trend = NativeListTextView(context)
|
|
30
|
+
private val detail = NativeListTextView(context)
|
|
31
|
+
private val badge = NativeListTextView(context)
|
|
32
|
+
private val metricImages = List(5) { NativeListImageSlot(context) }
|
|
33
|
+
private val usedImages = mutableSetOf<Int>()
|
|
34
|
+
private var rowKey = ""
|
|
35
|
+
private var compositeMetricTitle: TextView? = null
|
|
36
|
+
override val assetFields = listOf("visual", "metrics")
|
|
37
|
+
override val defaultCornerRadius = 12
|
|
38
|
+
|
|
39
|
+
override fun unselectedBackground(
|
|
40
|
+
item: NativeListItem,
|
|
41
|
+
theme: JSONObject?,
|
|
42
|
+
layout: String,
|
|
43
|
+
itemIndex: Int?,
|
|
44
|
+
) =
|
|
45
|
+
if (layout == "sectioned" && !item.json.optBoolean("selected"))
|
|
46
|
+
super.unselectedBackground(item, theme, layout, itemIndex)
|
|
47
|
+
else color(theme, "subduedBackground", "#F9F9F9")
|
|
48
|
+
|
|
49
|
+
override fun defaultHeight(item: NativeListItem, layout: String) =
|
|
50
|
+
if (item.json.optString("variant") in setOf("activity", "performance")) 0 else 132
|
|
51
|
+
|
|
52
|
+
init {
|
|
53
|
+
titleLine.addView(title, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
54
|
+
titleLine.addView(badge)
|
|
55
|
+
column.addView(titleLine)
|
|
56
|
+
column.addView(label)
|
|
57
|
+
column.addView(trend)
|
|
58
|
+
column.addView(detail)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private fun color(theme: JSONObject?, name: String, fallback: String) =
|
|
62
|
+
parseNativeListColor(theme?.optString(name, fallback) ?: fallback)
|
|
63
|
+
|
|
64
|
+
private fun safeColor(value: String, fallback: Int) =
|
|
65
|
+
runCatching { parseNativeListColor(value) }.getOrDefault(fallback)
|
|
66
|
+
|
|
67
|
+
private fun roundedFill(color: Int, radius: Float) =
|
|
68
|
+
GradientDrawable().apply {
|
|
69
|
+
setColor(color)
|
|
70
|
+
cornerRadius = dp(radius.toInt()).toFloat()
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private fun sp(value: Float) = if (sourceScale) value else NativeListScale.font(resources, value)
|
|
74
|
+
|
|
75
|
+
private fun wrap() = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
|
76
|
+
|
|
77
|
+
private val circleOutlineProvider =
|
|
78
|
+
object : ViewOutlineProvider() {
|
|
79
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
80
|
+
outline.setOval(0, 0, view.width, view.height)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
private fun visualSources(visual: JSONObject): List<Pair<JSONObject, String>> {
|
|
85
|
+
val kind = visual.optString("kind")
|
|
86
|
+
val variant =
|
|
87
|
+
when (kind) {
|
|
88
|
+
"token" -> "token"
|
|
89
|
+
"network" -> "network"
|
|
90
|
+
"account",
|
|
91
|
+
"wallet" -> "avatar"
|
|
92
|
+
else -> "generic"
|
|
93
|
+
}
|
|
94
|
+
if (kind == "icon") return emptyList()
|
|
95
|
+
if (kind == "stackedImages") {
|
|
96
|
+
val images = visual.optJSONArray("images") ?: return emptyList()
|
|
97
|
+
return (0 until images.length()).map { images.getJSONObject(it) to "generic" }
|
|
98
|
+
}
|
|
99
|
+
return visual.optJSONObject("image")?.let { listOf(it to variant) } ?: emptyList()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun dataTextColor(tone: String, theme: JSONObject?): Int =
|
|
103
|
+
when (tone) {
|
|
104
|
+
"positive" -> color(theme, "positive", "#00713FDE")
|
|
105
|
+
"negative" -> color(theme, "negative", "#C40006D3")
|
|
106
|
+
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
107
|
+
else -> color(theme, "primaryText", "#000000DF")
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override fun bindContent(
|
|
111
|
+
item: NativeListItem,
|
|
112
|
+
theme: JSONObject?,
|
|
113
|
+
layout: String,
|
|
114
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
115
|
+
) {
|
|
116
|
+
removeAllViews()
|
|
117
|
+
usedImages.clear()
|
|
118
|
+
rowKey = item.key
|
|
119
|
+
compositeMetricTitle = null
|
|
120
|
+
orientation = VERTICAL
|
|
121
|
+
gravity = Gravity.START
|
|
122
|
+
val data = item.json
|
|
123
|
+
val style = data.optJSONObject("style") ?: JSONObject()
|
|
124
|
+
val image = style.optJSONObject("image") ?: JSONObject()
|
|
125
|
+
val hp =
|
|
126
|
+
if (style.has("horizontalPadding")) stylePx(style.optDouble("horizontalPadding")) else dp(14)
|
|
127
|
+
val vp =
|
|
128
|
+
if (style.has("verticalPadding")) stylePx(style.optDouble("verticalPadding")) else dp(14)
|
|
129
|
+
setPadding(hp, vp, hp, vp)
|
|
130
|
+
fun text(
|
|
131
|
+
view: NativeListTextView,
|
|
132
|
+
value: String,
|
|
133
|
+
slot: String,
|
|
134
|
+
size: Float,
|
|
135
|
+
weight: String = "regular",
|
|
136
|
+
ink: Int = color(theme, "secondaryText", "#0000009B"),
|
|
137
|
+
) {
|
|
138
|
+
NativeListResolvedText.resolve(
|
|
139
|
+
context,
|
|
140
|
+
value,
|
|
141
|
+
style.optJSONObject(slot),
|
|
142
|
+
size,
|
|
143
|
+
weight,
|
|
144
|
+
ink,
|
|
145
|
+
0,
|
|
146
|
+
1,
|
|
147
|
+
sourceScale || slot == "subtitle",
|
|
148
|
+
)
|
|
149
|
+
.bind(view)
|
|
150
|
+
if (style.optJSONObject(slot)?.optString("truncate") != "clip")
|
|
151
|
+
view.ellipsize = TextUtils.TruncateAt.END
|
|
152
|
+
}
|
|
153
|
+
if (data.optString("variant") in setOf("activity", "performance")) {
|
|
154
|
+
visual.recycle()
|
|
155
|
+
bindCompositeMetricCard(item, theme, data.optString("variant"))
|
|
156
|
+
val heading = compositeMetricTitle as? NativeListTextView
|
|
157
|
+
if (heading != null) {
|
|
158
|
+
val spacing = heading.letterSpacing
|
|
159
|
+
NativeListResolvedText.resolve(
|
|
160
|
+
context,
|
|
161
|
+
data.optString("title").uppercase(),
|
|
162
|
+
style.optJSONObject("title"),
|
|
163
|
+
11f,
|
|
164
|
+
"regular",
|
|
165
|
+
color(theme, "disabledText", "#00000072"),
|
|
166
|
+
14,
|
|
167
|
+
1,
|
|
168
|
+
sourceScale,
|
|
169
|
+
)
|
|
170
|
+
.bind(heading)
|
|
171
|
+
heading.letterSpacing = spacing
|
|
172
|
+
// Legacy metricText headings always ellipsize unless a style clips them.
|
|
173
|
+
if (style.optJSONObject("title")?.optString("truncate") != "clip")
|
|
174
|
+
heading.ellipsize = TextUtils.TruncateAt.END
|
|
175
|
+
}
|
|
176
|
+
if (style.has("lineGap")) {
|
|
177
|
+
val gap = stylePx(style.optDouble("lineGap"))
|
|
178
|
+
for (index in 1 until childCount) {
|
|
179
|
+
val params = getChildAt(index).layoutParams as LayoutParams
|
|
180
|
+
params.topMargin = gap
|
|
181
|
+
params.bottomMargin = 0
|
|
182
|
+
getChildAt(index).layoutParams = params
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} else {
|
|
186
|
+
data.optJSONObject("visual")?.let { descriptor ->
|
|
187
|
+
(visual.parent as? android.view.ViewGroup)?.removeView(visual)
|
|
188
|
+
visual.bind(descriptor, image, item.key, theme, false, sourceScale)
|
|
189
|
+
addView(
|
|
190
|
+
visual,
|
|
191
|
+
LayoutParams(
|
|
192
|
+
if (image.has("width")) stylePx(image.optDouble("width")) else dp(32),
|
|
193
|
+
if (image.has("height")) stylePx(image.optDouble("height")) else dp(32),
|
|
194
|
+
)
|
|
195
|
+
.apply {
|
|
196
|
+
marginEnd = dp(12)
|
|
197
|
+
bottomMargin =
|
|
198
|
+
if (style.has("leadingGap")) stylePx(style.optDouble("leadingGap"))
|
|
199
|
+
else if (style.has("lineGap")) stylePx(style.optDouble("lineGap")) else 0
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
} ?: visual.recycle()
|
|
203
|
+
text(
|
|
204
|
+
title,
|
|
205
|
+
data.optString("value"),
|
|
206
|
+
"value",
|
|
207
|
+
if (data.optString("size") == "large") 24f else 18f,
|
|
208
|
+
"semibold",
|
|
209
|
+
color(theme, "primaryText", "#000000DF"),
|
|
210
|
+
)
|
|
211
|
+
text(
|
|
212
|
+
label,
|
|
213
|
+
data.optString("title"),
|
|
214
|
+
"title",
|
|
215
|
+
11f,
|
|
216
|
+
ink = color(theme, "disabledText", "#00000072"),
|
|
217
|
+
)
|
|
218
|
+
text(
|
|
219
|
+
trend,
|
|
220
|
+
data.optString("trend"),
|
|
221
|
+
"trend",
|
|
222
|
+
12f,
|
|
223
|
+
ink =
|
|
224
|
+
when (data.optString("trendTone")) {
|
|
225
|
+
"positive" -> color(theme, "positive", "#00713FDE")
|
|
226
|
+
"negative" -> color(theme, "negative", "#C40006D3")
|
|
227
|
+
else -> color(theme, "secondaryText", "#0000009B")
|
|
228
|
+
},
|
|
229
|
+
)
|
|
230
|
+
text(detail, data.optString("subtitle"), "subtitle", 14f)
|
|
231
|
+
NativeListResolvedText.resolve(
|
|
232
|
+
context,
|
|
233
|
+
data.optJSONObject("badge")?.optString("text") ?: "",
|
|
234
|
+
null,
|
|
235
|
+
12f,
|
|
236
|
+
"medium",
|
|
237
|
+
color(theme, "accent", "#0D8200FC"),
|
|
238
|
+
0,
|
|
239
|
+
1,
|
|
240
|
+
sourceScale,
|
|
241
|
+
)
|
|
242
|
+
.bind(badge)
|
|
243
|
+
// Legacy: the badge follows the weighted title without an extra margin.
|
|
244
|
+
(badge.layoutParams as LayoutParams).marginStart = 0
|
|
245
|
+
for (view in listOf(label, trend, detail)) (view.layoutParams as LayoutParams).topMargin =
|
|
246
|
+
if (style.has("lineGap")) stylePx(style.optDouble("lineGap")) else 0
|
|
247
|
+
(column.parent as? android.view.ViewGroup)?.removeView(column)
|
|
248
|
+
addView(column, weightedWidth())
|
|
249
|
+
}
|
|
250
|
+
for (index in metricImages.indices) if (index !in usedImages) metricImages[index].recycle()
|
|
251
|
+
style
|
|
252
|
+
.optJSONObject("container")
|
|
253
|
+
?.optString("contentVerticalAlignment")
|
|
254
|
+
?.takeIf { it.isNotEmpty() }
|
|
255
|
+
?.let {
|
|
256
|
+
gravity =
|
|
257
|
+
Gravity.START or
|
|
258
|
+
when (it) {
|
|
259
|
+
"top" -> Gravity.TOP
|
|
260
|
+
"bottom" -> Gravity.BOTTOM
|
|
261
|
+
else -> Gravity.CENTER_VERTICAL
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
override fun recycleContent() {
|
|
267
|
+
visual.recycle()
|
|
268
|
+
metricImages.forEach { it.recycle() }
|
|
269
|
+
removeAllViews()
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
override fun disposeContent() {
|
|
273
|
+
visual.dispose()
|
|
274
|
+
metricImages.forEach { it.dispose() }
|
|
275
|
+
removeAllViews()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private fun bindCompositeMetricCard(item: NativeListItem, theme: JSONObject?, variant: String) {
|
|
279
|
+
val metrics = item.json.optJSONArray("metrics") ?: JSONArray()
|
|
280
|
+
addView(
|
|
281
|
+
metricText(
|
|
282
|
+
value = item.json.optString("title").uppercase(),
|
|
283
|
+
size = 11f,
|
|
284
|
+
lineHeight = 14,
|
|
285
|
+
typeface = NativeListFonts.regular(context),
|
|
286
|
+
textColor = color(theme, "disabledText", "#00000072"),
|
|
287
|
+
letterSpacingDp = 1.2f,
|
|
288
|
+
)
|
|
289
|
+
.also { compositeMetricTitle = it },
|
|
290
|
+
weightedWidth(),
|
|
291
|
+
)
|
|
292
|
+
if (variant == "activity") {
|
|
293
|
+
addView(activityHeroMetrics(metrics, theme), weightedWidth().apply { topMargin = dp(14) })
|
|
294
|
+
addView(
|
|
295
|
+
View(context).apply { setBackgroundColor(color(theme, "separator", "#0000001F")) },
|
|
296
|
+
LayoutParams(LayoutParams.MATCH_PARENT, 1).apply {
|
|
297
|
+
topMargin = dp(14)
|
|
298
|
+
bottomMargin = dp(14)
|
|
299
|
+
},
|
|
300
|
+
)
|
|
301
|
+
addView(activityCompactMetrics(metrics, theme), weightedWidth())
|
|
302
|
+
} else {
|
|
303
|
+
val winRateRow =
|
|
304
|
+
LinearLayout(context).apply {
|
|
305
|
+
orientation = HORIZONTAL
|
|
306
|
+
gravity = Gravity.BOTTOM
|
|
307
|
+
if (metrics.length() > 0) {
|
|
308
|
+
addView(
|
|
309
|
+
makeMetricColumn(
|
|
310
|
+
metric = metrics.getJSONObject(0),
|
|
311
|
+
visualSlot = 0,
|
|
312
|
+
theme = theme,
|
|
313
|
+
alignment = Gravity.START,
|
|
314
|
+
valueSize = 18f,
|
|
315
|
+
valueLineHeight = 24,
|
|
316
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
317
|
+
),
|
|
318
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
if (metrics.length() > 1) {
|
|
322
|
+
addView(
|
|
323
|
+
makeMetricColumn(
|
|
324
|
+
metric = metrics.getJSONObject(1),
|
|
325
|
+
visualSlot = 1,
|
|
326
|
+
theme = theme,
|
|
327
|
+
alignment = Gravity.END,
|
|
328
|
+
valueSize = 14f,
|
|
329
|
+
valueLineHeight = 20,
|
|
330
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
331
|
+
),
|
|
332
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
addView(winRateRow, weightedWidth().apply { topMargin = dp(14) })
|
|
337
|
+
val progressValue = item.json.optDouble("progress", 0.0).coerceIn(0.0, 1.0)
|
|
338
|
+
val progress =
|
|
339
|
+
LinearLayout(context).apply {
|
|
340
|
+
orientation = HORIZONTAL
|
|
341
|
+
clipToOutline = true
|
|
342
|
+
outlineProvider =
|
|
343
|
+
object : ViewOutlineProvider() {
|
|
344
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
345
|
+
outline.setRoundRect(0, 0, view.width, view.height, dp(2).toFloat())
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
if (progressValue > 0.0) {
|
|
349
|
+
addView(
|
|
350
|
+
View(context).apply { setBackgroundColor(parseNativeListColor("#22AB15")) },
|
|
351
|
+
LayoutParams(0, LayoutParams.MATCH_PARENT, progressValue.toFloat()),
|
|
352
|
+
)
|
|
353
|
+
}
|
|
354
|
+
if (progressValue < 1.0) {
|
|
355
|
+
addView(
|
|
356
|
+
View(context).apply { setBackgroundColor(parseNativeListColor("#E5484D")) },
|
|
357
|
+
LayoutParams(0, LayoutParams.MATCH_PARENT, (1.0 - progressValue).toFloat()),
|
|
358
|
+
)
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
addView(progress, LayoutParams(LayoutParams.MATCH_PARENT, dp(4)).apply { topMargin = dp(8) })
|
|
362
|
+
addView(
|
|
363
|
+
performanceAverageMetrics(metrics, theme),
|
|
364
|
+
weightedWidth().apply { topMargin = dp(14) },
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private fun activityHeroMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
370
|
+
LinearLayout(context).apply {
|
|
371
|
+
orientation = HORIZONTAL
|
|
372
|
+
gravity = Gravity.BOTTOM
|
|
373
|
+
if (metrics.length() > 0) {
|
|
374
|
+
addView(
|
|
375
|
+
makeMetricColumn(
|
|
376
|
+
metric = metrics.getJSONObject(0),
|
|
377
|
+
visualSlot = 0,
|
|
378
|
+
theme = theme,
|
|
379
|
+
alignment = Gravity.START,
|
|
380
|
+
valueSize = 16f,
|
|
381
|
+
valueLineHeight = 24,
|
|
382
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
383
|
+
),
|
|
384
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
if (metrics.length() > 1) {
|
|
388
|
+
addView(
|
|
389
|
+
makeMetricColumn(
|
|
390
|
+
metric = metrics.getJSONObject(1),
|
|
391
|
+
visualSlot = 1,
|
|
392
|
+
theme = theme,
|
|
393
|
+
alignment = Gravity.END,
|
|
394
|
+
valueSize = 14f,
|
|
395
|
+
valueLineHeight = 20,
|
|
396
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
397
|
+
),
|
|
398
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
399
|
+
)
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
private fun activityCompactMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
404
|
+
LinearLayout(context).apply {
|
|
405
|
+
orientation = HORIZONTAL
|
|
406
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
407
|
+
for (index in 2 until metrics.length()) {
|
|
408
|
+
val alignment =
|
|
409
|
+
when (index) {
|
|
410
|
+
2 -> Gravity.START
|
|
411
|
+
metrics.length() - 1 -> Gravity.END
|
|
412
|
+
else -> Gravity.CENTER_HORIZONTAL
|
|
413
|
+
}
|
|
414
|
+
addView(
|
|
415
|
+
makeMetricColumn(
|
|
416
|
+
metric = metrics.getJSONObject(index),
|
|
417
|
+
visualSlot = index,
|
|
418
|
+
theme = theme,
|
|
419
|
+
alignment = alignment,
|
|
420
|
+
valueSize = 14f,
|
|
421
|
+
valueLineHeight = 20,
|
|
422
|
+
valueTypeface = NativeListFonts.medium(context),
|
|
423
|
+
),
|
|
424
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
425
|
+
)
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private fun performanceAverageMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
430
|
+
LinearLayout(context).apply {
|
|
431
|
+
orientation = HORIZONTAL
|
|
432
|
+
val indices =
|
|
433
|
+
listOf(2, metrics.length() - 1).distinct().filter { it in 0 until metrics.length() }
|
|
434
|
+
indices.forEachIndexed { position, index ->
|
|
435
|
+
val metric = metrics.getJSONObject(index)
|
|
436
|
+
val card =
|
|
437
|
+
makeMetricColumn(
|
|
438
|
+
metric = metric,
|
|
439
|
+
visualSlot = index,
|
|
440
|
+
theme = theme,
|
|
441
|
+
alignment = Gravity.START,
|
|
442
|
+
valueSize = 14f,
|
|
443
|
+
valueLineHeight = 20,
|
|
444
|
+
valueTypeface = NativeListFonts.medium(context),
|
|
445
|
+
)
|
|
446
|
+
.apply {
|
|
447
|
+
setPadding(dp(10), dp(10), dp(10), dp(10))
|
|
448
|
+
background = roundedFill(color(theme, "strongBackground", "#0000000F"), 8f)
|
|
449
|
+
}
|
|
450
|
+
addView(
|
|
451
|
+
card,
|
|
452
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f).apply {
|
|
453
|
+
if (position > 0) marginStart = dp(8)
|
|
454
|
+
},
|
|
455
|
+
)
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
private fun makeMetricColumn(
|
|
460
|
+
metric: JSONObject,
|
|
461
|
+
visualSlot: Int,
|
|
462
|
+
theme: JSONObject?,
|
|
463
|
+
alignment: Int,
|
|
464
|
+
valueSize: Float,
|
|
465
|
+
valueLineHeight: Int,
|
|
466
|
+
valueTypeface: Typeface,
|
|
467
|
+
) =
|
|
468
|
+
LinearLayout(context).apply {
|
|
469
|
+
orientation = VERTICAL
|
|
470
|
+
gravity = alignment
|
|
471
|
+
addView(
|
|
472
|
+
metricText(
|
|
473
|
+
value = metric.optString("label"),
|
|
474
|
+
size = 11f,
|
|
475
|
+
lineHeight = 14,
|
|
476
|
+
typeface = NativeListFonts.regular(context),
|
|
477
|
+
textColor = color(theme, "disabledText", "#00000072"),
|
|
478
|
+
gravity = alignment,
|
|
479
|
+
)
|
|
480
|
+
)
|
|
481
|
+
val valueText =
|
|
482
|
+
metricText(
|
|
483
|
+
value = metric.optString("value"),
|
|
484
|
+
size = valueSize,
|
|
485
|
+
lineHeight = valueLineHeight,
|
|
486
|
+
typeface = valueTypeface,
|
|
487
|
+
textColor = dataTextColor(metric.optString("tone"), theme),
|
|
488
|
+
gravity = alignment,
|
|
489
|
+
)
|
|
490
|
+
val valueVisual = metric.optJSONObject("visual")
|
|
491
|
+
val valueView =
|
|
492
|
+
if (valueVisual != null && visualSlot in metricImages.indices) {
|
|
493
|
+
LinearLayout(context).apply {
|
|
494
|
+
orientation = HORIZONTAL
|
|
495
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
496
|
+
val slot = metricImages[visualSlot]
|
|
497
|
+
usedImages.add(visualSlot)
|
|
498
|
+
val image = slot.view
|
|
499
|
+
(image.parent as? android.view.ViewGroup)?.removeView(image)
|
|
500
|
+
image.background = null
|
|
501
|
+
image.visibility = VISIBLE
|
|
502
|
+
image.outlineProvider = circleOutlineProvider
|
|
503
|
+
image.clipToOutline = true
|
|
504
|
+
valueVisual.optString("backgroundColor").takeIf(String::isNotEmpty)?.let {
|
|
505
|
+
backgroundColor ->
|
|
506
|
+
image.background = roundedFill(safeColor(backgroundColor, Color.WHITE), 8f)
|
|
507
|
+
}
|
|
508
|
+
visualSources(valueVisual).firstOrNull()?.let { (source, variant) ->
|
|
509
|
+
slot.bind(source, rowKey + ":" + visualSlot + ":" + metric.optString("key"), variant)
|
|
510
|
+
} ?: slot.recycle()
|
|
511
|
+
addView(image, LayoutParams(dp(16), dp(16)).apply { marginEnd = dp(6) })
|
|
512
|
+
addView(valueText, wrap())
|
|
513
|
+
}
|
|
514
|
+
} else {
|
|
515
|
+
valueText
|
|
516
|
+
}
|
|
517
|
+
addView(
|
|
518
|
+
valueView,
|
|
519
|
+
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
520
|
+
topMargin = dp(2)
|
|
521
|
+
},
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private fun metricText(
|
|
526
|
+
value: String,
|
|
527
|
+
size: Float,
|
|
528
|
+
lineHeight: Int,
|
|
529
|
+
typeface: Typeface,
|
|
530
|
+
textColor: Int,
|
|
531
|
+
letterSpacingDp: Float = 0f,
|
|
532
|
+
gravity: Int = Gravity.START,
|
|
533
|
+
) =
|
|
534
|
+
NativeListTextView(context).apply {
|
|
535
|
+
includeFontPadding = false
|
|
536
|
+
text = value
|
|
537
|
+
textSize = sp(size)
|
|
538
|
+
this.typeface = typeface
|
|
539
|
+
setTextColor(textColor)
|
|
540
|
+
this.gravity = gravity
|
|
541
|
+
maxLines = 1
|
|
542
|
+
ellipsize = TextUtils.TruncateAt.END
|
|
543
|
+
fontFeatureSettings = "tnum"
|
|
544
|
+
TextViewCompat.setLineHeight(this, dp(lineHeight))
|
|
545
|
+
if (letterSpacingDp > 0f) {
|
|
546
|
+
letterSpacing = letterSpacingDp / sp(size)
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
private fun weightedWidth() = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
551
|
+
}
|
|
@@ -6,6 +6,8 @@ import java.nio.ByteBuffer
|
|
|
6
6
|
import java.security.MessageDigest
|
|
7
7
|
import java.util.Locale
|
|
8
8
|
|
|
9
|
+
internal data class NativeSelectionTarget(val scope: String, val key: String?)
|
|
10
|
+
|
|
9
11
|
internal fun isNativeListRowPressEnabled(
|
|
10
12
|
type: String,
|
|
11
13
|
variant: String,
|
|
@@ -25,6 +27,13 @@ internal fun isNativeListWholeRowInteractive(
|
|
|
25
27
|
pressDisabled = pressDisabled,
|
|
26
28
|
)
|
|
27
29
|
|
|
30
|
+
internal fun resolveNativeListSelectorBackgroundColor(
|
|
31
|
+
rowType: String,
|
|
32
|
+
styleBackgroundColor: String?,
|
|
33
|
+
rowBackgroundColor: String?,
|
|
34
|
+
): String? = styleBackgroundColor?.takeIf(String::isNotEmpty)
|
|
35
|
+
?: rowBackgroundColor?.takeIf { rowType != "walletGroup" && it.isNotEmpty() }
|
|
36
|
+
|
|
28
37
|
// The image fallback-state cache is process-wide, so it is keyed by a digest of the request
|
|
29
38
|
// identity instead of the raw headers, which can carry credentials such as Authorization.
|
|
30
39
|
internal fun nativeListSourceFallbackStateKey(uri: String, headers: Map<String, String>): String? {
|
|
@@ -47,6 +56,9 @@ private fun updateLengthPrefixed(digest: MessageDigest, value: String) {
|
|
|
47
56
|
digest.update(bytes)
|
|
48
57
|
}
|
|
49
58
|
|
|
59
|
+
// Template type alone partitions reuse; data keys and styles never select a renderer.
|
|
60
|
+
internal enum class NativeListRendererKey { MESSAGE, RAIL, MEDIA_TILE, ACTION, SYSTEM, ACTIVITY, DATA_ROW, METRIC_CARD, MARKET, IDENTITY, SECTION_HEADER, WALLET_GROUP }
|
|
61
|
+
|
|
50
62
|
internal data class NativeListItem(
|
|
51
63
|
val key: String,
|
|
52
64
|
val type: String,
|
|
@@ -55,6 +67,15 @@ internal data class NativeListItem(
|
|
|
55
67
|
val json: JSONObject,
|
|
56
68
|
) {
|
|
57
69
|
val content: String = json.toString()
|
|
70
|
+
val rendererKey: NativeListRendererKey
|
|
71
|
+
get() = NativeListRendererRegistry.key(type)
|
|
72
|
+
val styledHeight: Double?
|
|
73
|
+
get() = json.optJSONObject("style")?.optJSONObject("container")
|
|
74
|
+
?.takeIf { it.has("height") }?.optDouble("height")
|
|
75
|
+
// Selector presentations use their explicit-height geometry when either explicit height field
|
|
76
|
+
// is set (Web `hasExplicitRowHeight`). Row-local only; list-wide source scale stays row.height.
|
|
77
|
+
val hasExplicitHeight: Boolean
|
|
78
|
+
get() = styledHeight != null || json.has("height")
|
|
58
79
|
// OneKey patch: only a host-validated stable snapshot can request a lightweight diff payload.
|
|
59
80
|
var selectionUpdateFromContent: String? = null
|
|
60
81
|
|
|
@@ -107,6 +128,7 @@ internal data class NativeListItem(
|
|
|
107
128
|
fun parse(json: JSONObject): NativeListItem {
|
|
108
129
|
val key = json.getString("key")
|
|
109
130
|
val type = json.getString("type")
|
|
131
|
+
NativeListRendererRegistry.key(type) // Reject unsupported templates at the model boundary.
|
|
110
132
|
return NativeListItem(
|
|
111
133
|
key = key,
|
|
112
134
|
type = type,
|
|
@@ -141,6 +163,7 @@ internal data class NativeListConfig(
|
|
|
141
163
|
val sectionIndexHapticsEnabled: Boolean,
|
|
142
164
|
val sectionIndexCenteredInWindow: Boolean,
|
|
143
165
|
val theme: JSONObject?,
|
|
166
|
+
val listStyle: JSONObject?,
|
|
144
167
|
val fixedFooter: NativeListItem?,
|
|
145
168
|
val items: List<NativeListItem>,
|
|
146
169
|
) {
|
|
@@ -190,6 +213,7 @@ internal data class NativeListConfig(
|
|
|
190
213
|
sectionIndexHapticsEnabled = sectionIndex?.optBoolean("hapticsEnabled", true) ?: true,
|
|
191
214
|
sectionIndexCenteredInWindow = sectionIndex?.optBoolean("centeredInWindow", false) ?: false,
|
|
192
215
|
theme = root.optJSONObject("theme"),
|
|
216
|
+
listStyle = root.optJSONObject("listStyle"),
|
|
193
217
|
fixedFooter = fixedFooter,
|
|
194
218
|
items = items,
|
|
195
219
|
)
|