@onekeyfe/react-native-native-list 3.0.157-alpha.249 → 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,711 @@
|
|
|
1
|
+
package com.margelo.nitro.nativelist
|
|
2
|
+
|
|
3
|
+
// OneKey patch: selector checkboxes share the source React Native border/background renderer.
|
|
4
|
+
import android.graphics.Color
|
|
5
|
+
import android.graphics.Typeface
|
|
6
|
+
import android.graphics.drawable.GradientDrawable
|
|
7
|
+
import android.graphics.drawable.StateListDrawable
|
|
8
|
+
import android.text.Spannable
|
|
9
|
+
import android.text.SpannableStringBuilder
|
|
10
|
+
import android.text.TextUtils
|
|
11
|
+
import android.text.style.AbsoluteSizeSpan
|
|
12
|
+
import android.text.style.ForegroundColorSpan
|
|
13
|
+
import android.util.TypedValue
|
|
14
|
+
import android.view.Gravity
|
|
15
|
+
import android.view.View
|
|
16
|
+
import android.widget.LinearLayout
|
|
17
|
+
import android.widget.ProgressBar
|
|
18
|
+
import android.widget.TextView
|
|
19
|
+
import androidx.core.widget.TextViewCompat
|
|
20
|
+
import com.facebook.react.uimanager.BackgroundStyleApplicator
|
|
21
|
+
import com.facebook.react.uimanager.LengthPercentage
|
|
22
|
+
import com.facebook.react.uimanager.LengthPercentageType
|
|
23
|
+
import com.facebook.react.uimanager.style.BorderRadiusProp
|
|
24
|
+
import com.facebook.react.uimanager.style.LogicalEdge
|
|
25
|
+
import kotlin.math.roundToInt
|
|
26
|
+
import org.json.JSONArray
|
|
27
|
+
import org.json.JSONObject
|
|
28
|
+
|
|
29
|
+
// Bounded controls only. Event ownership and epochs stay with the row host.
|
|
30
|
+
internal class NativeListAccessoryStack(context: android.content.Context) : LinearLayout(context) {
|
|
31
|
+
private val trailingViews = List(2) { NativeListTextView(context) }
|
|
32
|
+
private val trailingIcons = List(2) { OneKeyIconView(context) }
|
|
33
|
+
private val checkbox = OneKeyCheckboxView(context)
|
|
34
|
+
private val spinner = ProgressBar(context)
|
|
35
|
+
private var boundCheckboxData: JSONObject? = null
|
|
36
|
+
private val semanticValueViews = mutableListOf<TextView>()
|
|
37
|
+
private var selectorUsesSourceScale = false
|
|
38
|
+
private var checkboxUsesSelectorStyle = false
|
|
39
|
+
private var checkboxCheckedColor = Color.BLACK
|
|
40
|
+
private var checkboxUncheckedColor = Color.WHITE
|
|
41
|
+
private var checkboxIconColor = Color.WHITE
|
|
42
|
+
private var checkboxBorderColor = Color.LTGRAY
|
|
43
|
+
private var iconSubduedColor = Color.GRAY
|
|
44
|
+
val firstVisibleIcon: View?
|
|
45
|
+
get() = trailingIcons.firstOrNull { it.visibility == VISIBLE }
|
|
46
|
+
|
|
47
|
+
val hasCheckbox: Boolean
|
|
48
|
+
get() = checkbox.visibility == VISIBLE
|
|
49
|
+
|
|
50
|
+
val visibleValues: List<TextView>
|
|
51
|
+
get() = trailingViews.filter { it.visibility == VISIBLE }
|
|
52
|
+
|
|
53
|
+
var endInset: Int? = null
|
|
54
|
+
|
|
55
|
+
/** Legacy: overhanging selector controls require the host row not to clip its padding. */
|
|
56
|
+
var requestsUnclippedHost = false
|
|
57
|
+
private set
|
|
58
|
+
var onAction: ((String, View, Int?, NativeSelectionTarget?) -> Unit)? = null
|
|
59
|
+
private val selectorAccessibilityDelegate =
|
|
60
|
+
object : View.AccessibilityDelegate() {
|
|
61
|
+
override fun onInitializeAccessibilityNodeInfo(
|
|
62
|
+
host: View,
|
|
63
|
+
info: android.view.accessibility.AccessibilityNodeInfo,
|
|
64
|
+
) {
|
|
65
|
+
super.onInitializeAccessibilityNodeInfo(host, info)
|
|
66
|
+
info.viewIdResourceName = host.getTag(com.facebook.react.R.id.react_test_id) as? String
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
private val trailingPaintFlags = trailingViews.map { it.paintFlags }
|
|
71
|
+
|
|
72
|
+
/** The caller's `value` / `valueSecondary` text style bound to [view], if any. */
|
|
73
|
+
fun explicitTextStyle(view: TextView, style: JSONObject?): JSONObject? =
|
|
74
|
+
when (semanticValueViews.indexOf(view)) {
|
|
75
|
+
0 -> style?.optJSONObject("value")
|
|
76
|
+
1 -> style?.optJSONObject("valueSecondary")
|
|
77
|
+
else -> null
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Legacy selector source-scale typography for every visible trailing text. */
|
|
81
|
+
fun applySourceTypography(style: JSONObject?) {
|
|
82
|
+
trailingViews.forEach { view ->
|
|
83
|
+
if (view.visibility == VISIBLE)
|
|
84
|
+
applyNativeListSourceTypography(view, explicitTextStyle(view, style))
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
init {
|
|
89
|
+
orientation = VERTICAL
|
|
90
|
+
gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
91
|
+
trailingViews.forEach { addView(it) }
|
|
92
|
+
trailingIcons.forEach { addView(it) }
|
|
93
|
+
addView(checkbox)
|
|
94
|
+
addView(spinner)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fun bind(
|
|
98
|
+
item: NativeListItem,
|
|
99
|
+
descriptors: JSONArray,
|
|
100
|
+
theme: JSONObject?,
|
|
101
|
+
style: JSONObject,
|
|
102
|
+
sourceScale: Boolean,
|
|
103
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
104
|
+
horizontal: Boolean = false,
|
|
105
|
+
) {
|
|
106
|
+
selectorUsesSourceScale = sourceScale
|
|
107
|
+
reset()
|
|
108
|
+
tag = item
|
|
109
|
+
val primaryText = color(theme, "primaryText", "#000000DF")
|
|
110
|
+
checkboxCheckedColor = primaryText
|
|
111
|
+
checkboxUncheckedColor = color(theme, "inverseText", "#FCFCFC")
|
|
112
|
+
checkboxIconColor = checkboxUncheckedColor
|
|
113
|
+
checkboxBorderColor = Color.argb(0x31, 0, 0, 0)
|
|
114
|
+
iconSubduedColor = color(theme, "iconSubdued", "#00000072")
|
|
115
|
+
checkboxUsesSelectorStyle = item.json.optString("presentation") == "networkSelector"
|
|
116
|
+
if (checkboxUsesSelectorStyle) {
|
|
117
|
+
checkboxCheckedColor = color(theme, "checkboxBackground", "#202020")
|
|
118
|
+
checkboxBorderColor = color(theme, "checkboxBorder", "#00000031")
|
|
119
|
+
checkboxIconColor = color(theme, "checkboxIcon", "#FFFFFF")
|
|
120
|
+
checkboxUncheckedColor = checkboxIconColor
|
|
121
|
+
}
|
|
122
|
+
val kinds =
|
|
123
|
+
(0 until descriptors.length()).map { descriptors.getJSONObject(it).optString("kind") }
|
|
124
|
+
orientation = if (horizontal) HORIZONTAL else VERTICAL
|
|
125
|
+
gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
126
|
+
// Legacy: trailing text uses primaryText, never the selector checkbox fill.
|
|
127
|
+
trailingViews.forEach { it.setTextColor(primaryText) }
|
|
128
|
+
bindAccessories(item, descriptors, theme, checkboxState)
|
|
129
|
+
if (horizontal && "checkbox" in kinds)
|
|
130
|
+
(checkbox.layoutParams as LayoutParams).marginStart = dp(12)
|
|
131
|
+
for ((index, key) in listOf("value", "valueSecondary").withIndex()) {
|
|
132
|
+
style.optJSONObject(key)?.let { value ->
|
|
133
|
+
semanticValueViews.getOrNull(index)?.let { applyStyledText(it, value) }
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (style.has("trailingGap")) {
|
|
137
|
+
var previous = false
|
|
138
|
+
for (i in 0 until childCount) {
|
|
139
|
+
val v = getChildAt(i)
|
|
140
|
+
if (v.visibility != GONE) {
|
|
141
|
+
val params = v.layoutParams as LayoutParams
|
|
142
|
+
if (orientation == HORIZONTAL)
|
|
143
|
+
params.marginStart = if (previous) styleDp(style.optDouble("trailingGap")) else 0
|
|
144
|
+
else params.topMargin = if (previous) styleDp(style.optDouble("trailingGap")) else 0
|
|
145
|
+
previous = true
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
visibility =
|
|
150
|
+
if ((0 until childCount).any { getChildAt(it).visibility != GONE }) VISIBLE else GONE
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fun updateSelection(
|
|
154
|
+
item: NativeListItem,
|
|
155
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
156
|
+
) {
|
|
157
|
+
tag = item
|
|
158
|
+
if (boundCheckboxData != null) {
|
|
159
|
+
val descriptors = item.json.optJSONArray("trailing")
|
|
160
|
+
val latest =
|
|
161
|
+
(0 until (descriptors?.length() ?: 0))
|
|
162
|
+
.mapNotNull { descriptors?.optJSONObject(it) }
|
|
163
|
+
.lastOrNull { it.optString("kind") == "checkbox" } ?: item.json.optJSONObject("checkbox")
|
|
164
|
+
latest?.let { bindCheckbox(item, it, checkboxState) }
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
fun anchorInset(view: View): Int =
|
|
169
|
+
if (
|
|
170
|
+
(tag as? NativeListItem)?.json?.optString("presentation") == "accountSelector" &&
|
|
171
|
+
view in trailingIcons &&
|
|
172
|
+
(view.layoutParams as MarginLayoutParams).marginStart < 0
|
|
173
|
+
)
|
|
174
|
+
dp(7)
|
|
175
|
+
else 0
|
|
176
|
+
|
|
177
|
+
fun reset() {
|
|
178
|
+
tag = null
|
|
179
|
+
endInset = null
|
|
180
|
+
requestsUnclippedHost = false
|
|
181
|
+
clipChildren = true
|
|
182
|
+
clipToPadding = true
|
|
183
|
+
semanticValueViews.clear()
|
|
184
|
+
boundCheckboxData = null
|
|
185
|
+
trailingViews.forEachIndexed { index, v ->
|
|
186
|
+
v.paintFlags = trailingPaintFlags[index]
|
|
187
|
+
v.visibility = GONE
|
|
188
|
+
v.text = ""
|
|
189
|
+
v.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
|
190
|
+
v.setOnClickListener(null)
|
|
191
|
+
v.background = null
|
|
192
|
+
v.setPadding(0, 0, 0, 0)
|
|
193
|
+
// Legacy trailing text: END gravity, one line, no ellipsis.
|
|
194
|
+
v.gravity = Gravity.END
|
|
195
|
+
v.opticalOffsetY = 0f
|
|
196
|
+
v.maxLines = 1
|
|
197
|
+
v.setHorizontallyScrolling(false)
|
|
198
|
+
v.ellipsize = null
|
|
199
|
+
v.setLineSpacing(0f, 1f)
|
|
200
|
+
v.letterSpacing = 0f
|
|
201
|
+
}
|
|
202
|
+
trailingIcons.forEach {
|
|
203
|
+
it.visibility = GONE
|
|
204
|
+
it.setOnClickListener(null)
|
|
205
|
+
it.background = null
|
|
206
|
+
it.useSourceScale = selectorUsesSourceScale
|
|
207
|
+
it.glyphSizeDp = null
|
|
208
|
+
it.alpha = 1f
|
|
209
|
+
}
|
|
210
|
+
checkbox.visibility = GONE
|
|
211
|
+
checkbox.setOnClickListener(null)
|
|
212
|
+
checkbox.alpha = 1f
|
|
213
|
+
checkbox.layoutParams = LayoutParams(dp(20), dp(20))
|
|
214
|
+
spinner.visibility = GONE
|
|
215
|
+
spinner.alpha = 1f
|
|
216
|
+
spinner.layoutParams = LayoutParams(dp(20), dp(20))
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private fun emitAction(
|
|
220
|
+
item: NativeListItem,
|
|
221
|
+
key: String,
|
|
222
|
+
target: NativeSelectionTarget?,
|
|
223
|
+
view: View,
|
|
224
|
+
source: String,
|
|
225
|
+
slot: Int? = null,
|
|
226
|
+
) {
|
|
227
|
+
// Row-level disabled gating is per control (see showTrailing/bindCheckbox), as in legacy.
|
|
228
|
+
onAction?.invoke(key, view, slot, target)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private fun dp(value: Int) =
|
|
232
|
+
if (selectorUsesSourceScale) (value * resources.displayMetrics.density).roundToInt()
|
|
233
|
+
else NativeListScale.dp(resources, value)
|
|
234
|
+
|
|
235
|
+
private fun scaledDp(value: Float) =
|
|
236
|
+
if (selectorUsesSourceScale) value * resources.displayMetrics.density
|
|
237
|
+
else NativeListScale.dp(resources, value)
|
|
238
|
+
|
|
239
|
+
private fun sp(value: Float) =
|
|
240
|
+
if (selectorUsesSourceScale) value else NativeListScale.font(resources, value)
|
|
241
|
+
|
|
242
|
+
private fun styleDp(value: Double) = (value * resources.displayMetrics.density).roundToInt()
|
|
243
|
+
|
|
244
|
+
private fun bindAccessories(
|
|
245
|
+
item: NativeListItem,
|
|
246
|
+
accessories: JSONArray?,
|
|
247
|
+
theme: JSONObject?,
|
|
248
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
249
|
+
) {
|
|
250
|
+
if (accessories == null) return
|
|
251
|
+
var textIndex = 0
|
|
252
|
+
for (index in 0 until minOf(2, accessories.length())) {
|
|
253
|
+
val accessory = accessories.getJSONObject(index)
|
|
254
|
+
when (accessory.optString("kind")) {
|
|
255
|
+
"value" -> {
|
|
256
|
+
showTrailing(
|
|
257
|
+
textIndex,
|
|
258
|
+
accessory.optString("text"),
|
|
259
|
+
!accessory.optBoolean("secondary", false),
|
|
260
|
+
)
|
|
261
|
+
semanticValueViews.add(trailingViews[textIndex])
|
|
262
|
+
applyValueSegments(trailingViews[textIndex], accessory.optJSONArray("textSegments"))
|
|
263
|
+
textIndex++
|
|
264
|
+
}
|
|
265
|
+
"valuePair" -> {
|
|
266
|
+
showTrailingValuePair(textIndex, accessory, theme)
|
|
267
|
+
semanticValueViews.add(trailingViews[textIndex++])
|
|
268
|
+
}
|
|
269
|
+
"checkbox" -> bindCheckbox(item, accessory, checkboxState)
|
|
270
|
+
"radio" ->
|
|
271
|
+
showTrailing(
|
|
272
|
+
textIndex++,
|
|
273
|
+
if (accessory.optBoolean("checked")) "●" else "○",
|
|
274
|
+
true,
|
|
275
|
+
accessory.optString("actionKey").takeUnless { accessory.optBoolean("disabled", false) },
|
|
276
|
+
)
|
|
277
|
+
"switch" ->
|
|
278
|
+
showTrailing(
|
|
279
|
+
textIndex++,
|
|
280
|
+
if (accessory.optBoolean("value")) "ON" else "OFF",
|
|
281
|
+
true,
|
|
282
|
+
accessory.optString("actionKey").takeUnless { accessory.optBoolean("disabled", false) },
|
|
283
|
+
)
|
|
284
|
+
"chevron" ->
|
|
285
|
+
showTrailingIcon(
|
|
286
|
+
textIndex++,
|
|
287
|
+
item,
|
|
288
|
+
JSONObject(accessory.toString()).apply {
|
|
289
|
+
put("name", "ChevronRightSmallOutline")
|
|
290
|
+
if (optString("actionKey").isEmpty()) put("actionKey", "press")
|
|
291
|
+
},
|
|
292
|
+
theme,
|
|
293
|
+
)
|
|
294
|
+
"menu" -> showTrailingMenu(textIndex++, accessory.optString("actionKey"))
|
|
295
|
+
"drag" ->
|
|
296
|
+
showTrailingIcon(
|
|
297
|
+
textIndex++,
|
|
298
|
+
item,
|
|
299
|
+
JSONObject(accessory.toString()).apply { put("name", "DragOutline") },
|
|
300
|
+
theme,
|
|
301
|
+
)
|
|
302
|
+
"icon" -> showTrailingIcon(textIndex++, item, accessory, theme)
|
|
303
|
+
"spinner" -> spinner.visibility = VISIBLE
|
|
304
|
+
"progress" ->
|
|
305
|
+
showTrailing(textIndex++, "${(accessory.optDouble("value") * 100).toInt()}%", false)
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
private fun bindCheckbox(
|
|
311
|
+
item: NativeListItem,
|
|
312
|
+
json: JSONObject,
|
|
313
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
314
|
+
) {
|
|
315
|
+
boundCheckboxData = json
|
|
316
|
+
val targetJson = json.optJSONObject("target")
|
|
317
|
+
val scope = targetJson?.optString("scope", "row") ?: "row"
|
|
318
|
+
val target =
|
|
319
|
+
NativeSelectionTarget(
|
|
320
|
+
scope,
|
|
321
|
+
when (scope) {
|
|
322
|
+
"section" -> targetJson?.optString("sectionKey")
|
|
323
|
+
"row" -> item.key
|
|
324
|
+
else -> null
|
|
325
|
+
},
|
|
326
|
+
)
|
|
327
|
+
val state = checkboxState(item, target, json.optString("state", "unchecked"))
|
|
328
|
+
val accessoryDisabled = json.optBoolean("disabled", false)
|
|
329
|
+
if (json.optBoolean("loading", false)) {
|
|
330
|
+
checkbox.visibility = GONE
|
|
331
|
+
checkbox.setOnClickListener(null)
|
|
332
|
+
spinner.visibility = VISIBLE
|
|
333
|
+
spinner.alpha =
|
|
334
|
+
if (!item.json.optBoolean("disabled", false) && accessoryDisabled) 0.5f else 1f
|
|
335
|
+
return
|
|
336
|
+
}
|
|
337
|
+
checkbox.visibility = VISIBLE
|
|
338
|
+
checkbox.setState(state, checkboxIconColor)
|
|
339
|
+
val usesSourceCheckboxGeometry = checkboxUsesSelectorStyle && item.hasExplicitHeight
|
|
340
|
+
checkbox.usesSelectorGeometry = usesSourceCheckboxGeometry
|
|
341
|
+
if (usesSourceCheckboxGeometry) {
|
|
342
|
+
// OneKey patch: source Yoga children may round into padding; retain their complete border.
|
|
343
|
+
clipToPadding = false
|
|
344
|
+
requestsUnclippedHost = true
|
|
345
|
+
// OneKey patch: even a transparent source border changes RN's background clipping path.
|
|
346
|
+
checkbox.background = null
|
|
347
|
+
BackgroundStyleApplicator.setBackgroundColor(
|
|
348
|
+
checkbox,
|
|
349
|
+
if (state == "unchecked") checkboxUncheckedColor else checkboxCheckedColor,
|
|
350
|
+
)
|
|
351
|
+
BackgroundStyleApplicator.setBorderWidth(checkbox, LogicalEdge.ALL, 2f)
|
|
352
|
+
BackgroundStyleApplicator.setBorderColor(
|
|
353
|
+
checkbox,
|
|
354
|
+
LogicalEdge.ALL,
|
|
355
|
+
if (state == "unchecked") checkboxBorderColor else Color.TRANSPARENT,
|
|
356
|
+
)
|
|
357
|
+
BackgroundStyleApplicator.setBorderRadius(
|
|
358
|
+
checkbox,
|
|
359
|
+
BorderRadiusProp.BORDER_RADIUS,
|
|
360
|
+
LengthPercentage(4f, LengthPercentageType.POINT),
|
|
361
|
+
)
|
|
362
|
+
} else {
|
|
363
|
+
checkbox.background =
|
|
364
|
+
if (state == "unchecked") {
|
|
365
|
+
if (checkboxUsesSelectorStyle)
|
|
366
|
+
GradientDrawable().apply {
|
|
367
|
+
setColor(checkboxUncheckedColor)
|
|
368
|
+
setStroke(dp(2), checkboxBorderColor)
|
|
369
|
+
cornerRadius = scaledDp(4f)
|
|
370
|
+
}
|
|
371
|
+
else roundedStroke(checkboxBorderColor, checkboxUncheckedColor, 4f)
|
|
372
|
+
} else {
|
|
373
|
+
roundedFill(checkboxCheckedColor, 4f)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
// Row-level disabled opacity already applies to this child. Only apply a
|
|
377
|
+
// local 0.5 when the accessory alone is disabled, never 0.5 * 0.5.
|
|
378
|
+
checkbox.alpha = if (!item.json.optBoolean("disabled", false) && accessoryDisabled) 0.5f else 1f
|
|
379
|
+
checkbox.isEnabled =
|
|
380
|
+
!item.json.optBoolean("disabled", false) &&
|
|
381
|
+
!accessoryDisabled &&
|
|
382
|
+
!json.optBoolean("loading", false)
|
|
383
|
+
checkbox.setOnClickListener {
|
|
384
|
+
emitAction(
|
|
385
|
+
item,
|
|
386
|
+
json.optString("actionKey", "selection"),
|
|
387
|
+
target,
|
|
388
|
+
checkbox,
|
|
389
|
+
"trailingAccessory",
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
private fun showTrailing(
|
|
395
|
+
index: Int,
|
|
396
|
+
value: String,
|
|
397
|
+
primary: Boolean,
|
|
398
|
+
actionKey: String? = null,
|
|
399
|
+
textColor: Int? = null,
|
|
400
|
+
) {
|
|
401
|
+
if (index !in trailingViews.indices || value.isEmpty()) return
|
|
402
|
+
val view = trailingViews[index]
|
|
403
|
+
view.maxLines = 1
|
|
404
|
+
view.text = value
|
|
405
|
+
view.textSize = sp(if (primary) 16f else 14f)
|
|
406
|
+
view.typeface =
|
|
407
|
+
if (primary) NativeListFonts.medium(context) else NativeListFonts.regular(context)
|
|
408
|
+
view.includeFontPadding = false
|
|
409
|
+
view.fontFeatureSettings = "tnum"
|
|
410
|
+
TextViewCompat.setLineHeight(view, dp(if (primary) 24 else 20))
|
|
411
|
+
textColor?.let(view::setTextColor)
|
|
412
|
+
view.visibility = VISIBLE
|
|
413
|
+
if (!actionKey.isNullOrEmpty()) {
|
|
414
|
+
view.setOnClickListener {
|
|
415
|
+
(tag as? NativeListItem)
|
|
416
|
+
?.takeUnless { item -> item.json.optBoolean("disabled", false) }
|
|
417
|
+
?.let { item -> emitAction(item, actionKey, null, view, "trailingAccessory", index) }
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
private fun showTrailingValuePair(index: Int, accessory: JSONObject, theme: JSONObject?) {
|
|
423
|
+
if (index !in trailingViews.indices) return
|
|
424
|
+
val primary = accessory.optString("primary")
|
|
425
|
+
val secondary = accessory.optString("secondary")
|
|
426
|
+
if (primary.isEmpty() && secondary.isEmpty()) return
|
|
427
|
+
val value = SpannableStringBuilder()
|
|
428
|
+
val primaryStart = value.length
|
|
429
|
+
value.append(primary)
|
|
430
|
+
value.setSpan(
|
|
431
|
+
ForegroundColorSpan(accessoryTextColor(accessory.optString("primaryTone"), "primary", theme)),
|
|
432
|
+
primaryStart,
|
|
433
|
+
value.length,
|
|
434
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
435
|
+
)
|
|
436
|
+
value.setSpan(
|
|
437
|
+
AbsoluteSizeSpan(
|
|
438
|
+
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp(16f), resources.displayMetrics)
|
|
439
|
+
.roundToInt()
|
|
440
|
+
),
|
|
441
|
+
primaryStart,
|
|
442
|
+
value.length,
|
|
443
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
444
|
+
)
|
|
445
|
+
if (primary.isNotEmpty() && secondary.isNotEmpty()) value.append('\n')
|
|
446
|
+
val secondaryStart = value.length
|
|
447
|
+
value.append(secondary)
|
|
448
|
+
value.setSpan(
|
|
449
|
+
ForegroundColorSpan(
|
|
450
|
+
accessoryTextColor(accessory.optString("secondaryTone"), "secondary", theme)
|
|
451
|
+
),
|
|
452
|
+
secondaryStart,
|
|
453
|
+
value.length,
|
|
454
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
455
|
+
)
|
|
456
|
+
value.setSpan(
|
|
457
|
+
AbsoluteSizeSpan(
|
|
458
|
+
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp(14f), resources.displayMetrics)
|
|
459
|
+
.roundToInt()
|
|
460
|
+
),
|
|
461
|
+
secondaryStart,
|
|
462
|
+
value.length,
|
|
463
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
464
|
+
)
|
|
465
|
+
val view = trailingViews[index]
|
|
466
|
+
view.text = value
|
|
467
|
+
view.maxLines = 2
|
|
468
|
+
view.gravity = Gravity.END
|
|
469
|
+
view.includeFontPadding = false
|
|
470
|
+
view.typeface = NativeListFonts.regular(context)
|
|
471
|
+
view.fontFeatureSettings = "tnum"
|
|
472
|
+
TextViewCompat.setLineHeight(view, dp(20))
|
|
473
|
+
view.visibility = VISIBLE
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private fun showTrailingMenu(index: Int, actionKey: String) {
|
|
477
|
+
showTrailing(index, "⋮", true, actionKey)
|
|
478
|
+
if (index !in trailingViews.indices) return
|
|
479
|
+
trailingViews[index].apply {
|
|
480
|
+
gravity = Gravity.CENTER
|
|
481
|
+
textSize = sp(20f)
|
|
482
|
+
layoutParams =
|
|
483
|
+
LayoutParams(dp(24), dp(24)).apply {
|
|
484
|
+
if (this@NativeListAccessoryStack.orientation == HORIZONTAL && index > 0)
|
|
485
|
+
marginStart = dp(8)
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private fun accessoryTextColor(tone: String, defaultTone: String, theme: JSONObject?): Int =
|
|
491
|
+
when (tone.ifEmpty { defaultTone }) {
|
|
492
|
+
"positive" -> color(theme, "positive", "#00713FDE")
|
|
493
|
+
"negative" -> color(theme, "negative", "#C40006D3")
|
|
494
|
+
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
495
|
+
else -> color(theme, "primaryText", "#000000DF")
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private fun showTrailingIcon(
|
|
499
|
+
index: Int,
|
|
500
|
+
item: NativeListItem,
|
|
501
|
+
data: JSONObject,
|
|
502
|
+
theme: JSONObject?,
|
|
503
|
+
) {
|
|
504
|
+
if (index !in trailingIcons.indices) return
|
|
505
|
+
val icon = trailingIcons[index]
|
|
506
|
+
icon.iconName = data.optString("name")
|
|
507
|
+
icon.tintColor = safeColor(data.optString("tintColor"), iconSubduedColor)
|
|
508
|
+
// OneKey patch: preserve the original 38dp press target around its 24dp layout slot.
|
|
509
|
+
icon.setTag(
|
|
510
|
+
com.facebook.react.R.id.react_test_id,
|
|
511
|
+
data.optString("testID").takeIf { it.isNotEmpty() },
|
|
512
|
+
)
|
|
513
|
+
icon.accessibilityDelegate = selectorAccessibilityDelegate
|
|
514
|
+
icon.contentDescription = data.optString("accessibilityLabel").takeIf { it.isNotEmpty() }
|
|
515
|
+
if (item.json.optString("presentation") == "accountSelector") {
|
|
516
|
+
icon.glyphSizeDp = 24
|
|
517
|
+
val isSourceMenu = item.hasExplicitHeight && icon.iconName == "DotHorOutline"
|
|
518
|
+
val size = if (item.hasExplicitHeight && icon.iconName == "PlusSmallOutline") 36 else 38
|
|
519
|
+
icon.layoutParams =
|
|
520
|
+
LayoutParams(dp(size), dp(size)).apply {
|
|
521
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
522
|
+
marginStart = -dp(7)
|
|
523
|
+
marginEnd = -dp(7)
|
|
524
|
+
}
|
|
525
|
+
// The frame overhangs its 24dp layout slot by 7dp on each side. The column
|
|
526
|
+
// clips children and the row clips to padding by default, which would cut
|
|
527
|
+
// the pressed circle down to a 24dp-wide pill.
|
|
528
|
+
this@NativeListAccessoryStack.clipChildren = false
|
|
529
|
+
clipToPadding = false
|
|
530
|
+
requestsUnclippedHost = true
|
|
531
|
+
if (isSourceMenu && data.optString("actionKey").isNotEmpty()) {
|
|
532
|
+
icon.background =
|
|
533
|
+
StateListDrawable().apply {
|
|
534
|
+
addState(
|
|
535
|
+
intArrayOf(android.R.attr.state_pressed),
|
|
536
|
+
roundedFill(color(theme, "rowPressedBackground", "#00000017"), 19f),
|
|
537
|
+
)
|
|
538
|
+
addState(intArrayOf(), roundedFill(Color.TRANSPARENT, 19f))
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
} else if (icon.iconName == "ChevronRightSmallOutline") {
|
|
542
|
+
icon.glyphSizeDp = null
|
|
543
|
+
// ListItem.DrillIn is a 24dp icon with mx=-6, for a 12dp layout footprint.
|
|
544
|
+
icon.layoutParams =
|
|
545
|
+
LayoutParams(dp(24), dp(24)).apply {
|
|
546
|
+
marginStart = -dp(6)
|
|
547
|
+
marginEnd = -dp(6)
|
|
548
|
+
}
|
|
549
|
+
} else {
|
|
550
|
+
// ListItem.IconButton medium keeps a 24dp glyph in a 36dp frame. Its
|
|
551
|
+
// m=-7 moves the last frame 7dp through the row's trailing padding. For
|
|
552
|
+
// the Bookmark XStack, gap=$6 plus both negative margins leaves 10dp
|
|
553
|
+
// between the physical frames (46dp between glyph centers).
|
|
554
|
+
icon.glyphSizeDp = 24
|
|
555
|
+
endInset = dp(5)
|
|
556
|
+
icon.layoutParams =
|
|
557
|
+
LayoutParams(dp(36), dp(36)).apply {
|
|
558
|
+
gravity = Gravity.END
|
|
559
|
+
if (this@NativeListAccessoryStack.orientation == HORIZONTAL && index > 0) {
|
|
560
|
+
marginStart = dp(10)
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
icon.visibility = VISIBLE
|
|
565
|
+
icon.isEnabled = !data.optBoolean("disabled", false)
|
|
566
|
+
icon.alpha = if (icon.isEnabled) 1f else 0.4f
|
|
567
|
+
val actionKey = data.optString("actionKey")
|
|
568
|
+
if (icon.isEnabled && actionKey.isNotEmpty()) {
|
|
569
|
+
icon.setOnClickListener {
|
|
570
|
+
emitAction(item, actionKey, null, icon, "trailingAccessory", index)
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
private fun applyValueSegments(
|
|
576
|
+
view: TextView,
|
|
577
|
+
segments: JSONArray?,
|
|
578
|
+
fontSize: Int = 16,
|
|
579
|
+
lineHeight: Int = 24,
|
|
580
|
+
medium: Boolean = true,
|
|
581
|
+
) {
|
|
582
|
+
if (segments == null || segments.length() == 0) return
|
|
583
|
+
val value = SpannableStringBuilder()
|
|
584
|
+
for (index in 0 until segments.length()) {
|
|
585
|
+
val segment = segments.getJSONObject(index)
|
|
586
|
+
val start = value.length
|
|
587
|
+
value.append(segment.optString("text"))
|
|
588
|
+
if (segment.optString("style") == "subscript") {
|
|
589
|
+
val size = kotlin.math.ceil(fontSize * 0.6).toFloat()
|
|
590
|
+
val span =
|
|
591
|
+
if (selectorUsesSourceScale)
|
|
592
|
+
AbsoluteSizeSpan(
|
|
593
|
+
kotlin.math.ceil((size * resources.displayMetrics.density).toDouble()).toInt(),
|
|
594
|
+
false,
|
|
595
|
+
)
|
|
596
|
+
else AbsoluteSizeSpan(sp(size).roundToInt(), true)
|
|
597
|
+
value.setSpan(span, start, value.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
view.textSize = sp(fontSize.toFloat())
|
|
601
|
+
view.typeface =
|
|
602
|
+
if (medium) NativeListFonts.medium(context) else NativeListFonts.regular(context)
|
|
603
|
+
TextViewCompat.setLineHeight(view, dp(lineHeight))
|
|
604
|
+
view.text = value
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
private fun applyStyledText(view: TextView, style: JSONObject) {
|
|
608
|
+
if (style.has("fontSize")) {
|
|
609
|
+
view.setTextSize(
|
|
610
|
+
TypedValue.COMPLEX_UNIT_PX,
|
|
611
|
+
(style.optDouble("fontSize") * resources.displayMetrics.density).toFloat(),
|
|
612
|
+
)
|
|
613
|
+
val text = SpannableStringBuilder(view.text)
|
|
614
|
+
text.getSpans(0, text.length, AbsoluteSizeSpan::class.java).forEach(text::removeSpan)
|
|
615
|
+
view.text = text
|
|
616
|
+
}
|
|
617
|
+
style.optString("fontWeight").takeIf(String::isNotEmpty)?.let {
|
|
618
|
+
view.typeface = marketTypeface(it, "regular")
|
|
619
|
+
}
|
|
620
|
+
style.optString("color").takeIf(String::isNotEmpty)?.let {
|
|
621
|
+
val color = safeColor(it, view.currentTextColor)
|
|
622
|
+
view.setTextColor(color)
|
|
623
|
+
val text = SpannableStringBuilder(view.text)
|
|
624
|
+
text.getSpans(0, text.length, ForegroundColorSpan::class.java).forEach(text::removeSpan)
|
|
625
|
+
view.text = text
|
|
626
|
+
}
|
|
627
|
+
if (style.has("lineHeight")) {
|
|
628
|
+
val text = SpannableStringBuilder(view.text)
|
|
629
|
+
text.getSpans(0, text.length, NativeListLineHeightSpan::class.java).forEach(text::removeSpan)
|
|
630
|
+
text.setSpan(
|
|
631
|
+
NativeListLineHeightSpan(styleDp(style.optDouble("lineHeight"))),
|
|
632
|
+
0,
|
|
633
|
+
text.length,
|
|
634
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
635
|
+
)
|
|
636
|
+
view.setLineSpacing(0f, 1f)
|
|
637
|
+
view.text = text
|
|
638
|
+
}
|
|
639
|
+
if (style.has("lines")) {
|
|
640
|
+
view.maxLines = style.optInt("lines").coerceIn(1, 3)
|
|
641
|
+
view.ellipsize = TextUtils.TruncateAt.END
|
|
642
|
+
}
|
|
643
|
+
if (style.has("truncate") || style.has("lines")) {
|
|
644
|
+
view.setHorizontallyScrolling(view.maxLines == 1)
|
|
645
|
+
view.ellipsize =
|
|
646
|
+
if (style.optString("truncate", "tail") == "clip") null else TextUtils.TruncateAt.END
|
|
647
|
+
if (view.maxLines == 1) {
|
|
648
|
+
val text = SpannableStringBuilder(view.text)
|
|
649
|
+
Regex("\\r\\n|[\\r\\n]").findAll(text).toList().asReversed().forEach { match ->
|
|
650
|
+
text.replace(match.range.first, match.range.last + 1, " ")
|
|
651
|
+
}
|
|
652
|
+
view.text = text
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
if (style.has("verticalAlignment")) {
|
|
656
|
+
view.gravity =
|
|
657
|
+
(view.gravity and Gravity.VERTICAL_GRAVITY_MASK.inv()) or
|
|
658
|
+
when (style.optString("verticalAlignment")) {
|
|
659
|
+
"top" -> Gravity.TOP
|
|
660
|
+
"bottom" -> Gravity.BOTTOM
|
|
661
|
+
else -> Gravity.CENTER_VERTICAL
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
if (style.has("offsetY"))
|
|
665
|
+
(view as? NativeListTextView)?.opticalOffsetY =
|
|
666
|
+
(style.optDouble("offsetY") * resources.displayMetrics.density).toFloat()
|
|
667
|
+
style.optString("alignment").takeIf(String::isNotEmpty)?.let {
|
|
668
|
+
view.gravity =
|
|
669
|
+
(view.gravity and Gravity.VERTICAL_GRAVITY_MASK) or
|
|
670
|
+
when (it) {
|
|
671
|
+
"center" -> Gravity.CENTER_HORIZONTAL
|
|
672
|
+
"end" -> Gravity.END
|
|
673
|
+
else -> Gravity.START
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// OneKey patch: keep a small idle member pool after reuse or a direct rebind.
|
|
679
|
+
// The compact proxy/expansion keeps every member until it leaves that state.
|
|
680
|
+
|
|
681
|
+
private fun marketTypeface(weight: String, fallback: String): Typeface =
|
|
682
|
+
when (weight.ifEmpty { fallback }) {
|
|
683
|
+
"regular" -> NativeListFonts.regular(context)
|
|
684
|
+
"semibold" -> NativeListFonts.semibold(context)
|
|
685
|
+
"bold" -> NativeListFonts.bold(context)
|
|
686
|
+
else -> NativeListFonts.medium(context)
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
private fun safeColor(value: String?, fallback: Int): Int =
|
|
690
|
+
try {
|
|
691
|
+
if (value.isNullOrEmpty()) fallback else parseNativeListColor(value)
|
|
692
|
+
} catch (_: IllegalArgumentException) {
|
|
693
|
+
fallback
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
private fun color(theme: JSONObject?, key: String, fallback: String): Int =
|
|
697
|
+
safeColor(theme?.optString(key, fallback), parseNativeListColor(fallback))
|
|
698
|
+
|
|
699
|
+
private fun roundedFill(color: Int, radiusDp: Float) =
|
|
700
|
+
GradientDrawable().apply {
|
|
701
|
+
setColor(color)
|
|
702
|
+
cornerRadius = scaledDp(radiusDp)
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private fun roundedStroke(stroke: Int, fill: Int, radiusDp: Float) =
|
|
706
|
+
GradientDrawable().apply {
|
|
707
|
+
setColor(fill)
|
|
708
|
+
setStroke(dp(2), stroke)
|
|
709
|
+
cornerRadius = scaledDp(radiusDp)
|
|
710
|
+
}
|
|
711
|
+
}
|