@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.
- 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,169 @@
|
|
|
1
|
+
package com.margelo.nitro.nativelist
|
|
2
|
+
|
|
3
|
+
import android.view.Gravity
|
|
4
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
5
|
+
import org.json.JSONArray
|
|
6
|
+
import org.json.JSONObject
|
|
7
|
+
|
|
8
|
+
internal class NativeListActionRowView(context: ThemedReactContext) :
|
|
9
|
+
NativeListRendererRowView(context) {
|
|
10
|
+
private val visual = NativeListLeadingVisual(context)
|
|
11
|
+
private val title = NativeListTextView(context)
|
|
12
|
+
private val accessories = NativeListAccessoryStack(context)
|
|
13
|
+
override val assetFields = listOf("icon")
|
|
14
|
+
|
|
15
|
+
init {
|
|
16
|
+
visual.glyphSize = 24
|
|
17
|
+
visual.iconBorder = false
|
|
18
|
+
orientation = HORIZONTAL
|
|
19
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
20
|
+
addView(visual)
|
|
21
|
+
addView(title, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
22
|
+
addView(accessories)
|
|
23
|
+
accessories.onAction = { key, view, slot, target ->
|
|
24
|
+
emitAction(key, view, "trailingAccessory", slot, target, accessories.anchorInset(view))
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private val titlePaintFlags = title.paintFlags
|
|
29
|
+
private val fallbackPaintFlags = visual.fallbackTextView.paintFlags
|
|
30
|
+
|
|
31
|
+
override fun defaultHeight(item: NativeListItem, layout: String) =
|
|
32
|
+
when {
|
|
33
|
+
item.json.optString("presentation") == "accountSelector" -> 48
|
|
34
|
+
item.json.has("icon") -> 60
|
|
35
|
+
else -> 44
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
override fun bindContent(
|
|
39
|
+
item: NativeListItem,
|
|
40
|
+
theme: JSONObject?,
|
|
41
|
+
layout: String,
|
|
42
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
43
|
+
) {
|
|
44
|
+
val style = item.json.optJSONObject("style") ?: JSONObject()
|
|
45
|
+
val icon = item.json.optJSONObject("icon")
|
|
46
|
+
val alignment = style.optJSONObject("container")?.optString("contentVerticalAlignment")
|
|
47
|
+
gravity =
|
|
48
|
+
when (alignment) {
|
|
49
|
+
"top" -> Gravity.TOP
|
|
50
|
+
"bottom" -> Gravity.BOTTOM
|
|
51
|
+
else -> Gravity.CENTER_VERTICAL
|
|
52
|
+
}
|
|
53
|
+
val selector = item.json.optString("presentation") == "accountSelector"
|
|
54
|
+
visual.roundedGlyphOrigin = selector && item.hasExplicitHeight
|
|
55
|
+
val descriptors =
|
|
56
|
+
item.json.optJSONObject("checkbox")?.let { JSONArray().put(it) }
|
|
57
|
+
?: item.json.optJSONArray("trailing")
|
|
58
|
+
?: JSONArray()
|
|
59
|
+
accessories.bind(item, descriptors, theme, style, sourceScale, checkboxState)
|
|
60
|
+
// Legacy: overhanging selector icons/checkboxes must not be clipped by row padding.
|
|
61
|
+
clipToPadding = !accessories.requestsUnclippedHost
|
|
62
|
+
val hp =
|
|
63
|
+
if (style.has("horizontalPadding")) stylePx(style.optDouble("horizontalPadding"))
|
|
64
|
+
else dp(if (layout == "table") 16 else 12)
|
|
65
|
+
val vp =
|
|
66
|
+
if (style.has("verticalPadding")) stylePx(style.optDouble("verticalPadding")) else dp(8)
|
|
67
|
+
setPadding(hp, vp, if (style.has("horizontalPadding")) hp else accessories.endInset ?: hp, vp)
|
|
68
|
+
val titleParams = title.layoutParams as LayoutParams
|
|
69
|
+
// Legacy: the weighted title abuts the trailing accessories without a gap.
|
|
70
|
+
titleParams.marginEnd = 0
|
|
71
|
+
title.layoutParams = titleParams
|
|
72
|
+
val tone = item.json.optString("tone")
|
|
73
|
+
val token =
|
|
74
|
+
if (selector && tone != "primary") "secondaryText"
|
|
75
|
+
else if (!selector && tone == "danger") "negative" else "primaryText"
|
|
76
|
+
val fallback =
|
|
77
|
+
if (token == "negative") "#C40006D3"
|
|
78
|
+
else if (selector || token == "secondaryText") "#0000009B" else "#000000DF"
|
|
79
|
+
NativeListResolvedText.resolve(
|
|
80
|
+
context,
|
|
81
|
+
item.json.optString("title"),
|
|
82
|
+
style.optJSONObject("title"),
|
|
83
|
+
16f,
|
|
84
|
+
if (selector && icon == null) "regular" else "medium",
|
|
85
|
+
parseNativeListColor(theme?.optString(token, fallback) ?: fallback),
|
|
86
|
+
24,
|
|
87
|
+
1,
|
|
88
|
+
sourceScale,
|
|
89
|
+
)
|
|
90
|
+
.bind(title)
|
|
91
|
+
title.paintFlags = titlePaintFlags
|
|
92
|
+
// Legacy selector typography covered every text in the row, including the
|
|
93
|
+
// trailing accessory text; explicit style fontSize/lineHeight stay as set.
|
|
94
|
+
if (selector && sourceScale) {
|
|
95
|
+
applyNativeListSourceTypography(title, style.optJSONObject("title"))
|
|
96
|
+
accessories.applySourceTypography(style)
|
|
97
|
+
}
|
|
98
|
+
visual.visibility = if (icon == null) GONE else VISIBLE
|
|
99
|
+
// Legacy: the 1px icon outline is kept only for a non-selector icon with an
|
|
100
|
+
// explicit background; selector icons use a plain 8dp rounded fill.
|
|
101
|
+
visual.iconBorder = !selector && icon?.has("backgroundColor") == true
|
|
102
|
+
if (icon != null) {
|
|
103
|
+
val source = JSONObject(icon.toString())
|
|
104
|
+
if (!source.has("backgroundColor"))
|
|
105
|
+
source.put(
|
|
106
|
+
"backgroundColor",
|
|
107
|
+
if (selector) theme?.optString("strongBackground", "#0000000F") ?: "#0000000F"
|
|
108
|
+
else "#00000000",
|
|
109
|
+
)
|
|
110
|
+
val image = JSONObject(style.optJSONObject("image")?.toString() ?: "{}")
|
|
111
|
+
if (selector && !image.has("cornerRadius") && !image.has("shape"))
|
|
112
|
+
image.put("cornerRadius", scaledDp(8f) / resources.displayMetrics.density)
|
|
113
|
+
visual.layoutParams =
|
|
114
|
+
LayoutParams(
|
|
115
|
+
if (image.has("width")) stylePx(image.optDouble("width"))
|
|
116
|
+
else dp(if (selector) 32 else 40),
|
|
117
|
+
if (image.has("height")) stylePx(image.optDouble("height"))
|
|
118
|
+
else dp(if (selector) 32 else 40),
|
|
119
|
+
)
|
|
120
|
+
.apply {
|
|
121
|
+
marginEnd =
|
|
122
|
+
if (style.has("leadingGap")) stylePx(style.optDouble("leadingGap"))
|
|
123
|
+
// Legacy: Yoga rounds the cumulative selector edges, not each gap.
|
|
124
|
+
else if (selector && item.hasExplicitHeight) dp(56) - dp(12) - dp(32)
|
|
125
|
+
else dp(12)
|
|
126
|
+
}
|
|
127
|
+
visual.bind(source, image, item.key, theme, false, sourceScale)
|
|
128
|
+
} else visual.recycle()
|
|
129
|
+
visual.fallbackTextView.paintFlags = fallbackPaintFlags
|
|
130
|
+
// Legacy selector typography also covered the leading fallback text.
|
|
131
|
+
if (selector && sourceScale && icon != null)
|
|
132
|
+
applyNativeListSourceTypography(visual.fallbackTextView, null)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
136
|
+
super.onLayout(changed, l, t, r, b)
|
|
137
|
+
val item = tag as? NativeListItem ?: return
|
|
138
|
+
if (
|
|
139
|
+
item.hasExplicitHeight &&
|
|
140
|
+
item.json.optString("presentation") == "accountSelector" &&
|
|
141
|
+
item.json
|
|
142
|
+
.optJSONObject("style")
|
|
143
|
+
?.optJSONObject("container")
|
|
144
|
+
?.has("contentVerticalAlignment") != true
|
|
145
|
+
) {
|
|
146
|
+
for (view in listOf(title, accessories)) if (view.visibility != GONE)
|
|
147
|
+
view.offsetTopAndBottom(
|
|
148
|
+
paddingTop + (height - paddingTop - paddingBottom - view.height + 1) / 2 - view.top
|
|
149
|
+
)
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
override fun bindSelectionContent(
|
|
154
|
+
item: NativeListItem,
|
|
155
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
156
|
+
) {
|
|
157
|
+
accessories.updateSelection(item, checkboxState)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
override fun recycleContent() {
|
|
161
|
+
visual.recycle()
|
|
162
|
+
accessories.reset()
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
override fun disposeContent() {
|
|
166
|
+
visual.dispose()
|
|
167
|
+
accessories.reset()
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
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 com.facebook.react.uimanager.ThemedReactContext
|
|
8
|
+
import org.json.JSONObject
|
|
9
|
+
|
|
10
|
+
internal class NativeListActivityRowView(context: ThemedReactContext) :
|
|
11
|
+
NativeListRendererRowView(context) {
|
|
12
|
+
private val content =
|
|
13
|
+
LinearLayout(context).apply {
|
|
14
|
+
orientation = HORIZONTAL
|
|
15
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
16
|
+
}
|
|
17
|
+
private val visual = NativeListLeadingVisual(context)
|
|
18
|
+
private val column = LinearLayout(context).apply { orientation = VERTICAL }
|
|
19
|
+
private val titleLine =
|
|
20
|
+
LinearLayout(context).apply {
|
|
21
|
+
orientation = HORIZONTAL
|
|
22
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
23
|
+
}
|
|
24
|
+
private val title = NativeListTextView(context)
|
|
25
|
+
private val description = NativeListTextView(context)
|
|
26
|
+
private val status = NativeListTextView(context)
|
|
27
|
+
private val failure = NativeListTextView(context)
|
|
28
|
+
private val amounts =
|
|
29
|
+
LinearLayout(context).apply {
|
|
30
|
+
orientation = VERTICAL
|
|
31
|
+
gravity = Gravity.END
|
|
32
|
+
}
|
|
33
|
+
private val values = List(2) { NativeListTextView(context) }
|
|
34
|
+
private val actions = LinearLayout(context).apply { orientation = HORIZONTAL }
|
|
35
|
+
private val buttons = List(3) { NativeListTextView(context) }
|
|
36
|
+
override val assetFields = listOf("leading", "secondaryLeading")
|
|
37
|
+
|
|
38
|
+
init {
|
|
39
|
+
titleLine.addView(title, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
40
|
+
titleLine.addView(failure)
|
|
41
|
+
column.addView(titleLine)
|
|
42
|
+
column.addView(description)
|
|
43
|
+
column.addView(status)
|
|
44
|
+
values.forEach(amounts::addView)
|
|
45
|
+
buttons.forEach(actions::addView)
|
|
46
|
+
content.addView(visual)
|
|
47
|
+
content.addView(column, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
48
|
+
content.addView(amounts)
|
|
49
|
+
addView(content, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
50
|
+
addView(actions)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override fun defaultHeight(item: NativeListItem, layout: String) =
|
|
54
|
+
if ((item.json.optJSONArray("footerActions")?.length() ?: 0) > 0) 104 else 60
|
|
55
|
+
|
|
56
|
+
override fun bindContent(
|
|
57
|
+
item: NativeListItem,
|
|
58
|
+
theme: JSONObject?,
|
|
59
|
+
layout: String,
|
|
60
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
61
|
+
) {
|
|
62
|
+
val data = item.json
|
|
63
|
+
val style = data.optJSONObject("style") ?: JSONObject()
|
|
64
|
+
val image = style.optJSONObject("image") ?: JSONObject()
|
|
65
|
+
fun color(name: String, fallback: String) =
|
|
66
|
+
parseNativeListColor(theme?.optString(name, fallback) ?: fallback)
|
|
67
|
+
fun fill(value: Int, radius: Int) =
|
|
68
|
+
GradientDrawable().apply {
|
|
69
|
+
setColor(value)
|
|
70
|
+
cornerRadius = dp(radius).toFloat()
|
|
71
|
+
}
|
|
72
|
+
fun bind(
|
|
73
|
+
view: NativeListTextView,
|
|
74
|
+
value: String,
|
|
75
|
+
slot: String,
|
|
76
|
+
size: Float,
|
|
77
|
+
weight: String = "regular",
|
|
78
|
+
ink: Int = color("secondaryText", "#0000009B"),
|
|
79
|
+
line: Int = 0,
|
|
80
|
+
lines: Int = 1,
|
|
81
|
+
align: String = "start",
|
|
82
|
+
) {
|
|
83
|
+
val textStyle =
|
|
84
|
+
JSONObject(style.optJSONObject(slot)?.toString() ?: "{}").apply {
|
|
85
|
+
if (!has("alignment")) put("alignment", align)
|
|
86
|
+
}
|
|
87
|
+
NativeListResolvedText.resolve(
|
|
88
|
+
context,
|
|
89
|
+
value,
|
|
90
|
+
textStyle,
|
|
91
|
+
size,
|
|
92
|
+
weight,
|
|
93
|
+
ink,
|
|
94
|
+
line,
|
|
95
|
+
lines,
|
|
96
|
+
sourceScale,
|
|
97
|
+
)
|
|
98
|
+
.bind(view)
|
|
99
|
+
view.ellipsize =
|
|
100
|
+
if (textStyle.optString("truncate") == "clip") null else TextUtils.TruncateAt.END
|
|
101
|
+
}
|
|
102
|
+
orientation = VERTICAL
|
|
103
|
+
val hasFooterActions = (data.optJSONArray("footerActions")?.length() ?: 0) > 0
|
|
104
|
+
gravity =
|
|
105
|
+
when (style.optJSONObject("container")?.optString("contentVerticalAlignment")) {
|
|
106
|
+
"bottom" -> Gravity.BOTTOM
|
|
107
|
+
"top" -> Gravity.TOP
|
|
108
|
+
"center" -> Gravity.CENTER_VERTICAL
|
|
109
|
+
// Legacy: the footer-action column is top/start aligned.
|
|
110
|
+
else -> if (hasFooterActions) Gravity.START else Gravity.CENTER_VERTICAL
|
|
111
|
+
}
|
|
112
|
+
content.gravity =
|
|
113
|
+
when (style.optJSONObject("container")?.optString("contentVerticalAlignment")) {
|
|
114
|
+
"bottom" -> Gravity.BOTTOM
|
|
115
|
+
"top" -> Gravity.TOP
|
|
116
|
+
else -> Gravity.CENTER_VERTICAL
|
|
117
|
+
}
|
|
118
|
+
val hp =
|
|
119
|
+
if (style.has("horizontalPadding")) stylePx(style.optDouble("horizontalPadding"))
|
|
120
|
+
else dp(if (layout == "table") 16 else 12)
|
|
121
|
+
val vp =
|
|
122
|
+
if (style.has("verticalPadding")) stylePx(style.optDouble("verticalPadding")) else dp(8)
|
|
123
|
+
setPadding(hp, vp, hp, vp)
|
|
124
|
+
val iw = if (image.has("width")) stylePx(image.optDouble("width")) else dp(40)
|
|
125
|
+
val ih = if (image.has("height")) stylePx(image.optDouble("height")) else dp(40)
|
|
126
|
+
val gap = if (style.has("leadingGap")) stylePx(style.optDouble("leadingGap")) else dp(12)
|
|
127
|
+
visual.layoutParams = LayoutParams(iw, ih).apply { marginEnd = gap }
|
|
128
|
+
visual.bind(
|
|
129
|
+
// Legacy: a missing visual still reserves an empty, transparent slot.
|
|
130
|
+
data.optJSONObject("leading") ?: JSONObject().put("backgroundColor", "#00000000"),
|
|
131
|
+
image,
|
|
132
|
+
item.key,
|
|
133
|
+
theme,
|
|
134
|
+
false,
|
|
135
|
+
sourceScale,
|
|
136
|
+
data.optJSONObject("secondaryLeading"),
|
|
137
|
+
)
|
|
138
|
+
bind(
|
|
139
|
+
title,
|
|
140
|
+
data.optString("title"),
|
|
141
|
+
"title",
|
|
142
|
+
16f,
|
|
143
|
+
"medium",
|
|
144
|
+
color("primaryText", "#000000DF"),
|
|
145
|
+
24,
|
|
146
|
+
)
|
|
147
|
+
bind(description, data.optString("description"), "description", 14f, line = 20, lines = 2)
|
|
148
|
+
val failed = data.optString("status") == "Failed"
|
|
149
|
+
bind(status, if (failed) "" else data.optString("status"), "status", 12f)
|
|
150
|
+
bind(
|
|
151
|
+
failure,
|
|
152
|
+
if (failed) "Failed" else "",
|
|
153
|
+
"status",
|
|
154
|
+
12f,
|
|
155
|
+
"medium",
|
|
156
|
+
color("negative", "#C40006D3"),
|
|
157
|
+
)
|
|
158
|
+
failure.background = fill(color("criticalBackground", "#F3000D14"), 4)
|
|
159
|
+
failure.setPadding(dp(8), dp(2), dp(8), dp(2))
|
|
160
|
+
(failure.layoutParams as LayoutParams).apply {
|
|
161
|
+
marginStart =
|
|
162
|
+
if (style.has("titleBadgeGap")) stylePx(style.optDouble("titleBadgeGap")) else 0
|
|
163
|
+
}
|
|
164
|
+
for (view in listOf(description, status)) (view.layoutParams as LayoutParams).apply {
|
|
165
|
+
topMargin = if (style.has("lineGap")) stylePx(style.optDouble("lineGap")) else 0
|
|
166
|
+
}
|
|
167
|
+
for ((index, slot) in listOf("primaryAmount", "secondaryAmount").withIndex()) {
|
|
168
|
+
val value = data.optString(slot)
|
|
169
|
+
bind(
|
|
170
|
+
values[index],
|
|
171
|
+
value,
|
|
172
|
+
slot,
|
|
173
|
+
if (index == 0) 16f else 14f,
|
|
174
|
+
if (index == 0) "medium" else "regular",
|
|
175
|
+
if (index == 0)
|
|
176
|
+
color(
|
|
177
|
+
if (value.startsWith("+")) "positive" else "primaryText",
|
|
178
|
+
if (value.startsWith("+")) "#00713FDE" else "#000000DF",
|
|
179
|
+
)
|
|
180
|
+
// Legacy: both amounts default to primaryText; only "+" amounts are positive.
|
|
181
|
+
else color("primaryText", "#000000DF"),
|
|
182
|
+
line = if (index == 0) 24 else 20,
|
|
183
|
+
align = "end",
|
|
184
|
+
)
|
|
185
|
+
values[index].layoutParams =
|
|
186
|
+
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
187
|
+
if (index > 0)
|
|
188
|
+
topMargin = if (style.has("trailingGap")) stylePx(style.optDouble("trailingGap")) else 0
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
val descriptors = data.optJSONArray("footerActions")
|
|
192
|
+
val count = minOf(3, descriptors?.length() ?: 0)
|
|
193
|
+
actions.visibility = if (count == 0) GONE else VISIBLE
|
|
194
|
+
actions.layoutParams =
|
|
195
|
+
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
196
|
+
marginStart = if (style.has("leadingGap") || image.has("width")) iw + gap else dp(52)
|
|
197
|
+
topMargin = if (style.has("lineGap")) stylePx(style.optDouble("lineGap")) else dp(8)
|
|
198
|
+
bottomMargin = dp(4)
|
|
199
|
+
}
|
|
200
|
+
for ((index, button) in buttons.withIndex()) {
|
|
201
|
+
button.visibility = if (index < count) VISIBLE else GONE
|
|
202
|
+
button.setOnClickListener(null)
|
|
203
|
+
if (index >= count) continue
|
|
204
|
+
val action = descriptors!!.getJSONObject(index)
|
|
205
|
+
NativeListResolvedText.resolve(
|
|
206
|
+
context,
|
|
207
|
+
action.optString("label"),
|
|
208
|
+
null,
|
|
209
|
+
// Legacy footer action: 14sp semibold on a 20dp line.
|
|
210
|
+
14f,
|
|
211
|
+
"semibold",
|
|
212
|
+
color(
|
|
213
|
+
if (action.optString("tone") == "danger") "negative" else "primaryText",
|
|
214
|
+
if (action.optString("tone") == "danger") "#C40006D3" else "#000000DF",
|
|
215
|
+
),
|
|
216
|
+
20,
|
|
217
|
+
1,
|
|
218
|
+
sourceScale,
|
|
219
|
+
)
|
|
220
|
+
.bind(button)
|
|
221
|
+
button.background = fill(color("strongBackground", "#0000000F"), 8)
|
|
222
|
+
button.setPadding(dp(8), dp(4), dp(8), dp(4))
|
|
223
|
+
button.isEnabled = !data.optBoolean("disabled") && !action.optBoolean("disabled")
|
|
224
|
+
button.layoutParams =
|
|
225
|
+
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
226
|
+
marginEnd = dp(8)
|
|
227
|
+
}
|
|
228
|
+
button.setOnClickListener {
|
|
229
|
+
emitAction(action.optString("key"), button, "footerAction", index)
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
override fun recycleContent() {
|
|
235
|
+
visual.recycle()
|
|
236
|
+
buttons.forEach { it.setOnClickListener(null) }
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
override fun disposeContent() {
|
|
240
|
+
visual.dispose()
|
|
241
|
+
buttons.forEach { it.setOnClickListener(null) }
|
|
242
|
+
}
|
|
243
|
+
}
|
|
@@ -13,6 +13,9 @@ import java.util.WeakHashMap
|
|
|
13
13
|
import kotlin.math.ceil
|
|
14
14
|
import kotlin.math.roundToInt
|
|
15
15
|
|
|
16
|
+
internal class NativeListViewHolder(val rowView: NativeListRowHost) :
|
|
17
|
+
androidx.recyclerview.widget.RecyclerView.ViewHolder(rowView)
|
|
18
|
+
|
|
16
19
|
internal class NativeListAdapter(
|
|
17
20
|
private val context: ThemedReactContext,
|
|
18
21
|
) : RecyclerView.Adapter<NativeListViewHolder>() {
|
|
@@ -44,7 +47,7 @@ internal class NativeListAdapter(
|
|
|
44
47
|
private var marketSourceItems: List<NativeListItem>? = null
|
|
45
48
|
private var marketSourceEdges = DoubleArray(0)
|
|
46
49
|
private val createdRows = Collections.newSetFromMap(
|
|
47
|
-
WeakHashMap<
|
|
50
|
+
WeakHashMap<NativeListRowHost, Boolean>(),
|
|
48
51
|
)
|
|
49
52
|
var usesSelectorSourceScale = false
|
|
50
53
|
var theme: JSONObject? = null
|
|
@@ -52,18 +55,28 @@ internal class NativeListAdapter(
|
|
|
52
55
|
if (field?.toString() != value?.toString()) needsThemeRebind = true
|
|
53
56
|
field = value
|
|
54
57
|
}
|
|
58
|
+
// Reuses the theme rebind path: chrome lives outside the row payload, so a
|
|
59
|
+
// changed list style would otherwise not reach rows DiffUtil considers equal.
|
|
60
|
+
var listStyle: JSONObject? = null
|
|
61
|
+
set(value) {
|
|
62
|
+
if (field?.toString() != value?.toString()) needsThemeRebind = true
|
|
63
|
+
field = value
|
|
64
|
+
}
|
|
55
65
|
var layout: String = "linear"
|
|
56
66
|
var orientation: String = "vertical"
|
|
57
67
|
var selectedKeys: Set<String> = emptySet()
|
|
58
68
|
var checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String = { _, _, fallback -> fallback }
|
|
59
69
|
var onRowPress: ((NativeListItem, NativeListActionOrigin) -> Unit)? = null
|
|
60
70
|
var onAction: ((NativeListItem, String, NativeSelectionTarget?, NativeListActionOrigin?) -> Unit)? = null
|
|
61
|
-
var onBindingInvalidated: ((
|
|
71
|
+
var onBindingInvalidated: ((NativeListRowHost, Long) -> Unit)? = null
|
|
62
72
|
|
|
63
73
|
override fun getItemCount(): Int = reorderItems?.size ?: differ.currentList.size
|
|
64
74
|
|
|
75
|
+
override fun getItemViewType(position: Int): Int =
|
|
76
|
+
requireNotNull(itemAt(position)).rendererKey.ordinal
|
|
77
|
+
|
|
65
78
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NativeListViewHolder {
|
|
66
|
-
val view =
|
|
79
|
+
val view = NativeListRendererRegistry.create(context, viewType)
|
|
67
80
|
view.layoutParams = ViewGroup.LayoutParams(
|
|
68
81
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
69
82
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
|
@@ -79,6 +92,7 @@ internal class NativeListAdapter(
|
|
|
79
92
|
|
|
80
93
|
override fun onBindViewHolder(holder: NativeListViewHolder, position: Int) {
|
|
81
94
|
val item = itemAt(position) ?: return
|
|
95
|
+
holder.rowView.listStyle = listStyle
|
|
82
96
|
holder.rowView.bind(
|
|
83
97
|
item,
|
|
84
98
|
theme,
|
|
@@ -135,7 +149,7 @@ internal class NativeListAdapter(
|
|
|
135
149
|
items.forEachIndexed { index, item ->
|
|
136
150
|
val style = item.json.optJSONObject("style")
|
|
137
151
|
val height = item.json.optDouble("height", 0.0)
|
|
138
|
-
val sourceHeight = if (item.type == "market" && style != null) {
|
|
152
|
+
val sourceHeight = item.styledHeight ?: if (item.type == "market" && style != null) {
|
|
139
153
|
val titleHeight = ceil((style.optJSONObject("title")?.optDouble("lineHeight", 24.0) ?: 24.0) * density) / density
|
|
140
154
|
val subtitleHeight = if (item.json.optString("subtitle").isNotEmpty() || item.json.optJSONObject("subtitlePrefix") != null) {
|
|
141
155
|
ceil((style.optJSONObject("subtitle")?.optDouble("lineHeight", 20.0) ?: 20.0) * density) / density + style.optDouble("lineGap", 0.0)
|
|
@@ -201,14 +215,14 @@ internal class NativeListAdapter(
|
|
|
201
215
|
marketSourceEdges = DoubleArray(0)
|
|
202
216
|
reorderItems = null
|
|
203
217
|
suppressDifferUpdates = false
|
|
204
|
-
createdRows.forEach(
|
|
218
|
+
createdRows.forEach(NativeListRowHost::dispose)
|
|
205
219
|
createdRows.clear()
|
|
206
220
|
}
|
|
207
221
|
|
|
208
222
|
companion object {
|
|
209
223
|
private val DIFF = object : DiffUtil.ItemCallback<NativeListItem>() {
|
|
210
224
|
override fun areItemsTheSame(oldItem: NativeListItem, newItem: NativeListItem): Boolean =
|
|
211
|
-
oldItem.key == newItem.key
|
|
225
|
+
oldItem.key == newItem.key
|
|
212
226
|
|
|
213
227
|
override fun areContentsTheSame(oldItem: NativeListItem, newItem: NativeListItem): Boolean =
|
|
214
228
|
oldItem.revision == newItem.revision && oldItem.content == newItem.content
|