@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
|
@@ -1,4538 +0,0 @@
|
|
|
1
|
-
package com.margelo.nitro.nativelist
|
|
2
|
-
|
|
3
|
-
import android.animation.Animator
|
|
4
|
-
import android.animation.AnimatorListenerAdapter
|
|
5
|
-
import android.animation.TimeInterpolator
|
|
6
|
-
import android.animation.ValueAnimator
|
|
7
|
-
import android.graphics.Color
|
|
8
|
-
import android.graphics.Canvas
|
|
9
|
-
import android.graphics.LinearGradient
|
|
10
|
-
import android.graphics.Matrix
|
|
11
|
-
import android.graphics.Outline
|
|
12
|
-
import android.graphics.Paint
|
|
13
|
-
import android.graphics.Path
|
|
14
|
-
import android.graphics.RectF
|
|
15
|
-
import android.graphics.Shader
|
|
16
|
-
import android.graphics.Typeface
|
|
17
|
-
import android.graphics.drawable.Drawable
|
|
18
|
-
import android.graphics.drawable.GradientDrawable
|
|
19
|
-
import android.graphics.drawable.StateListDrawable
|
|
20
|
-
import android.os.Handler
|
|
21
|
-
import android.os.Looper
|
|
22
|
-
import android.text.Spannable
|
|
23
|
-
import android.text.SpannableStringBuilder
|
|
24
|
-
import android.text.TextUtils
|
|
25
|
-
import android.text.style.AbsoluteSizeSpan
|
|
26
|
-
import android.text.style.BackgroundColorSpan
|
|
27
|
-
import android.text.style.ForegroundColorSpan
|
|
28
|
-
import android.util.TypedValue
|
|
29
|
-
import android.view.Gravity
|
|
30
|
-
import android.view.MotionEvent
|
|
31
|
-
import android.view.View
|
|
32
|
-
import android.view.ViewGroup
|
|
33
|
-
import android.view.ViewOutlineProvider
|
|
34
|
-
import android.view.animation.LinearInterpolator
|
|
35
|
-
import android.widget.FrameLayout
|
|
36
|
-
import android.widget.LinearLayout
|
|
37
|
-
import android.widget.ProgressBar
|
|
38
|
-
import android.widget.TextView
|
|
39
|
-
import com.facebook.react.uimanager.ThemedReactContext
|
|
40
|
-
// OneKey patch: selector checkboxes share the source React Native border/background renderer.
|
|
41
|
-
import com.facebook.react.uimanager.BackgroundStyleApplicator
|
|
42
|
-
import com.facebook.react.uimanager.LengthPercentage
|
|
43
|
-
import com.facebook.react.uimanager.LengthPercentageType
|
|
44
|
-
import com.facebook.react.uimanager.style.BorderRadiusProp
|
|
45
|
-
import com.facebook.react.uimanager.style.LogicalEdge
|
|
46
|
-
import com.margelo.nitro.onekeyimage.OneKeyImageReusableView
|
|
47
|
-
import androidx.core.graphics.PathParser
|
|
48
|
-
import androidx.core.widget.TextViewCompat
|
|
49
|
-
import androidx.recyclerview.widget.RecyclerView
|
|
50
|
-
import org.json.JSONArray
|
|
51
|
-
import org.json.JSONObject
|
|
52
|
-
import kotlin.math.roundToInt
|
|
53
|
-
|
|
54
|
-
// Market/TokenListSkeleton: source geometry and the native Skeleton's 3s shimmer.
|
|
55
|
-
private class NativeListMarketSkeleton(context: android.content.Context, backgroundColor: Int) : View(context) {
|
|
56
|
-
private val marks = Array(5) { RectF() }
|
|
57
|
-
private val paint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
58
|
-
private val matrix = Matrix()
|
|
59
|
-
private val shaders = arrayOfNulls<LinearGradient>(5)
|
|
60
|
-
private val dark = Color.red(backgroundColor) * 0.299 + Color.green(backgroundColor) * 0.587 + Color.blue(backgroundColor) * 0.114 < 128
|
|
61
|
-
private val colors = intArrayOf(
|
|
62
|
-
Color.parseColor(if (dark) "#111111" else "#FAFAFA"),
|
|
63
|
-
Color.parseColor(if (dark) "#333333" else "#CDCDCD"),
|
|
64
|
-
Color.parseColor(if (dark) "#111111" else "#FAFAFA"),
|
|
65
|
-
)
|
|
66
|
-
private var phase = 0f
|
|
67
|
-
private val animator = ValueAnimator.ofFloat(0f, 1f).apply {
|
|
68
|
-
duration = 3000
|
|
69
|
-
repeatCount = ValueAnimator.INFINITE
|
|
70
|
-
interpolator = LinearInterpolator()
|
|
71
|
-
addUpdateListener { phase = it.animatedValue as Float; invalidate() }
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private fun dp(value: Float) = NativeListScale.dp(resources, value)
|
|
75
|
-
|
|
76
|
-
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
|
77
|
-
super.onSizeChanged(w, h, oldw, oldh)
|
|
78
|
-
marks[0].set(0f, 0f, dp(32f), dp(32f))
|
|
79
|
-
marks[1].set(dp(44f), 0f, dp(124f), dp(16f))
|
|
80
|
-
marks[2].set(dp(44f), dp(20f), dp(104f), dp(32f))
|
|
81
|
-
marks[3].set(w - dp(168f), dp(7f), w - dp(88f), dp(25f))
|
|
82
|
-
marks[4].set(w - dp(80f), dp(7f), w.toFloat(), dp(25f))
|
|
83
|
-
marks.forEachIndexed { index, mark ->
|
|
84
|
-
shaders[index] = LinearGradient(0f, 0f, mark.width(), 0f, colors, floatArrayOf(0f, 0.5f, 1f), Shader.TileMode.CLAMP)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
override fun onDraw(canvas: Canvas) {
|
|
89
|
-
super.onDraw(canvas)
|
|
90
|
-
marks.forEachIndexed { index, mark ->
|
|
91
|
-
matrix.setTranslate(mark.left - mark.width() + phase * mark.width() * 3f, 0f)
|
|
92
|
-
shaders[index]?.setLocalMatrix(matrix)
|
|
93
|
-
paint.shader = shaders[index]
|
|
94
|
-
val radius = dp(if (index == 0) 16f else 8f)
|
|
95
|
-
canvas.drawRoundRect(mark, radius, radius, paint)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
override fun onAttachedToWindow() {
|
|
100
|
-
super.onAttachedToWindow()
|
|
101
|
-
if (windowVisibility == VISIBLE) animator.start()
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
override fun onDetachedFromWindow() {
|
|
105
|
-
animator.cancel()
|
|
106
|
-
super.onDetachedFromWindow()
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
override fun onWindowVisibilityChanged(visibility: Int) {
|
|
110
|
-
super.onWindowVisibilityChanged(visibility)
|
|
111
|
-
if (visibility == VISIBLE && isAttachedToWindow) {
|
|
112
|
-
if (!animator.isStarted) animator.start()
|
|
113
|
-
} else animator.cancel()
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
internal data class NativeListActionOrigin(
|
|
118
|
-
val sourceView: View,
|
|
119
|
-
val ownerRowView: NativeListRowView,
|
|
120
|
-
val bindingEpoch: Long,
|
|
121
|
-
val source: String,
|
|
122
|
-
val slot: Int? = null,
|
|
123
|
-
val anchorInsetPixels: Int = 0,
|
|
124
|
-
val windowPointPixels: android.graphics.PointF? = null,
|
|
125
|
-
)
|
|
126
|
-
|
|
127
|
-
/** React Native color strings use CSS #RRGGBBAA ordering; Android expects #AARRGGBB. */
|
|
128
|
-
internal fun parseNativeListColor(value: String): Int {
|
|
129
|
-
val normalized = value.trim()
|
|
130
|
-
val androidValue = if (
|
|
131
|
-
normalized.length == 9 && normalized[0] == '#' &&
|
|
132
|
-
normalized.drop(1).all { it.isDigit() || it.lowercaseChar() in 'a'..'f' }
|
|
133
|
-
) {
|
|
134
|
-
"#${normalized.takeLast(2)}${normalized.substring(1, 7)}"
|
|
135
|
-
} else {
|
|
136
|
-
normalized
|
|
137
|
-
}
|
|
138
|
-
return Color.parseColor(androidValue)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
internal object NativeListScale {
|
|
142
|
-
private const val STANDARD_DP_WIDTH_THRESHOLD = 400
|
|
143
|
-
|
|
144
|
-
fun factor(resources: android.content.res.Resources): Float {
|
|
145
|
-
val widthDp = resources.configuration.screenWidthDp
|
|
146
|
-
return if (widthDp in 1 until STANDARD_DP_WIDTH_THRESHOLD) 0.9f else 1f
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
fun dp(resources: android.content.res.Resources, value: Int): Int =
|
|
150
|
-
(value * factor(resources) * resources.displayMetrics.density).roundToInt()
|
|
151
|
-
|
|
152
|
-
fun dp(resources: android.content.res.Resources, value: Float): Float =
|
|
153
|
-
(value * factor(resources) * resources.displayMetrics.density).roundToInt().toFloat()
|
|
154
|
-
|
|
155
|
-
fun font(resources: android.content.res.Resources, value: Float): Float =
|
|
156
|
-
if (factor(resources) == 1f) value else (value * factor(resources)).roundToInt().toFloat()
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
internal object NativeListFonts {
|
|
160
|
-
private val cache = mutableMapOf<String, Typeface>()
|
|
161
|
-
|
|
162
|
-
private fun load(context: android.content.Context, weight: String, fallback: String): Typeface =
|
|
163
|
-
cache.getOrPut(weight) {
|
|
164
|
-
try {
|
|
165
|
-
Typeface.createFromAsset(context.assets, "fonts/Roobert-$weight.ttf")
|
|
166
|
-
} catch (_: RuntimeException) {
|
|
167
|
-
Typeface.create(fallback, Typeface.NORMAL)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
fun regular(context: android.content.Context) = load(context, "Regular", "sans-serif")
|
|
172
|
-
fun medium(context: android.content.Context) = load(context, "Medium", "sans-serif-medium")
|
|
173
|
-
fun semibold(context: android.content.Context) = load(context, "SemiBold", "sans-serif-medium")
|
|
174
|
-
fun bold(context: android.content.Context) = load(context, "Bold", "sans-serif")
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
internal data class NativeSelectionTarget(val scope: String, val key: String?)
|
|
178
|
-
|
|
179
|
-
// OneKey patch: match React Native's CustomLineHeightSpan for first/last line bounds.
|
|
180
|
-
private class SelectorLineHeightSpan(private val lineHeight: Int) : android.text.style.LineHeightSpan {
|
|
181
|
-
override fun chooseHeight(text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int, fm: Paint.FontMetricsInt) {
|
|
182
|
-
val leading = lineHeight - (fm.descent - fm.ascent)
|
|
183
|
-
fm.ascent -= kotlin.math.ceil(leading / 2.0).toInt()
|
|
184
|
-
fm.descent += kotlin.math.floor(leading / 2.0).toInt()
|
|
185
|
-
if (start == 0) fm.top = fm.ascent
|
|
186
|
-
if (end == text.length) fm.bottom = fm.descent
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
private class DottedUnderlineTextView(context: android.content.Context) : TextView(context) {
|
|
191
|
-
var useSourceScale = false
|
|
192
|
-
private fun scaledDp(value: Float) = if (useSourceScale) value * resources.displayMetrics.density else NativeListScale.dp(resources, value)
|
|
193
|
-
var showsDottedUnderline = false
|
|
194
|
-
var dottedUnderlineColor = Color.TRANSPARENT
|
|
195
|
-
private val dottedPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
196
|
-
|
|
197
|
-
override fun onDraw(canvas: Canvas) {
|
|
198
|
-
super.onDraw(canvas)
|
|
199
|
-
if (!showsDottedUnderline || text.isEmpty()) return
|
|
200
|
-
dottedPaint.color = dottedUnderlineColor
|
|
201
|
-
val radius = scaledDp(0.75f)
|
|
202
|
-
val spacing = scaledDp(4f)
|
|
203
|
-
val lineWidth = paint.measureText(text.toString()).coerceAtMost(width.toFloat())
|
|
204
|
-
val y = height - radius
|
|
205
|
-
var x = scaledDp(1f)
|
|
206
|
-
while (x <= lineWidth - radius) {
|
|
207
|
-
canvas.drawCircle(x, y, radius, dottedPaint)
|
|
208
|
-
x += spacing
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private class PackedTitleLineLayout(context: android.content.Context) : LinearLayout(context) {
|
|
214
|
-
var packsChildrenAtStart = false
|
|
215
|
-
// OneKey patch: optional cap for the Market subtitle's localized name.
|
|
216
|
-
var leadingTextMaxWidth = Int.MAX_VALUE
|
|
217
|
-
|
|
218
|
-
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
219
|
-
if (!packsChildrenAtStart || childCount < 2) {
|
|
220
|
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
221
|
-
return
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
val title = getChildAt(0)
|
|
225
|
-
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
|
226
|
-
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
|
227
|
-
var accessoryWidth = 0
|
|
228
|
-
for (index in 1 until childCount) {
|
|
229
|
-
val child = getChildAt(index)
|
|
230
|
-
if (child.visibility == GONE) continue
|
|
231
|
-
measureChildWithMargins(
|
|
232
|
-
child,
|
|
233
|
-
widthMeasureSpec,
|
|
234
|
-
paddingLeft + paddingRight + accessoryWidth,
|
|
235
|
-
heightMeasureSpec,
|
|
236
|
-
paddingTop + paddingBottom,
|
|
237
|
-
)
|
|
238
|
-
val margins = child.layoutParams as MarginLayoutParams
|
|
239
|
-
accessoryWidth += child.measuredWidth + margins.leftMargin + margins.rightMargin
|
|
240
|
-
}
|
|
241
|
-
val titleMargins = title.layoutParams as MarginLayoutParams
|
|
242
|
-
if (widthMode != MeasureSpec.UNSPECIFIED) {
|
|
243
|
-
(title as TextView).maxWidth = (widthSize - paddingLeft - paddingRight - accessoryWidth -
|
|
244
|
-
titleMargins.leftMargin - titleMargins.rightMargin).coerceAtLeast(0).coerceAtMost(leadingTextMaxWidth)
|
|
245
|
-
}
|
|
246
|
-
(title.layoutParams as LayoutParams).apply {
|
|
247
|
-
width = LayoutParams.WRAP_CONTENT
|
|
248
|
-
weight = 0f
|
|
249
|
-
}
|
|
250
|
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
251
|
-
if (widthMode != MeasureSpec.UNSPECIFIED) {
|
|
252
|
-
setMeasuredDimension(widthSize, measuredHeight)
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// OneKey patch: fit subtitle segments at intrinsic width, shrinking text only when necessary.
|
|
258
|
-
private class SelectorSubtitleLayout(context: android.content.Context) : LinearLayout(context) {
|
|
259
|
-
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
260
|
-
val available = MeasureSpec.getSize(widthMeasureSpec)
|
|
261
|
-
val labels = mutableListOf<Pair<TextView, Int>>()
|
|
262
|
-
var fixedWidth = 0
|
|
263
|
-
for (index in 0 until childCount) {
|
|
264
|
-
val child = getChildAt(index)
|
|
265
|
-
val params = child.layoutParams as LayoutParams
|
|
266
|
-
if (child is TextView) {
|
|
267
|
-
child.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightMeasureSpec)
|
|
268
|
-
labels.add(child to child.measuredWidth)
|
|
269
|
-
} else fixedWidth += params.width + params.leftMargin + params.rightMargin
|
|
270
|
-
}
|
|
271
|
-
val desired = labels.sumOf { it.second }
|
|
272
|
-
val textWidth = (available - fixedWidth).coerceAtLeast(0)
|
|
273
|
-
labels.forEach { (label, width) ->
|
|
274
|
-
(label.layoutParams as LayoutParams).width = if (desired <= textWidth) width else (width.toLong() * textWidth / desired.coerceAtLeast(1)).toInt()
|
|
275
|
-
}
|
|
276
|
-
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
private class NativeListTableColumnView(context: android.content.Context) : LinearLayout(context) {
|
|
281
|
-
private val primaryLine = LinearLayout(context)
|
|
282
|
-
private val primary = TextView(context)
|
|
283
|
-
private val badges = LinearLayout(context)
|
|
284
|
-
private val secondaryLine = LinearLayout(context)
|
|
285
|
-
private val secondaryLeading = TextView(context)
|
|
286
|
-
private val secondary = TextView(context)
|
|
287
|
-
|
|
288
|
-
init {
|
|
289
|
-
orientation = VERTICAL
|
|
290
|
-
primaryLine.orientation = HORIZONTAL
|
|
291
|
-
primaryLine.gravity = Gravity.CENTER_VERTICAL
|
|
292
|
-
badges.orientation = HORIZONTAL
|
|
293
|
-
badges.gravity = Gravity.CENTER_VERTICAL
|
|
294
|
-
primary.includeFontPadding = false
|
|
295
|
-
primary.fontFeatureSettings = "tnum"
|
|
296
|
-
primary.maxLines = 1
|
|
297
|
-
primary.ellipsize = TextUtils.TruncateAt.END
|
|
298
|
-
primary.textSize = sp(14f)
|
|
299
|
-
primary.typeface = NativeListFonts.medium(context)
|
|
300
|
-
TextViewCompat.setLineHeight(primary, dp(20))
|
|
301
|
-
secondaryLine.orientation = HORIZONTAL
|
|
302
|
-
secondaryLine.gravity = Gravity.CENTER_VERTICAL
|
|
303
|
-
listOf(secondaryLeading, secondary).forEach { label ->
|
|
304
|
-
label.includeFontPadding = false
|
|
305
|
-
label.fontFeatureSettings = "tnum"
|
|
306
|
-
label.maxLines = 1
|
|
307
|
-
label.ellipsize = TextUtils.TruncateAt.END
|
|
308
|
-
label.textSize = sp(12f)
|
|
309
|
-
label.typeface = NativeListFonts.regular(context)
|
|
310
|
-
TextViewCompat.setLineHeight(label, dp(16))
|
|
311
|
-
}
|
|
312
|
-
secondaryLeading.maxWidth = dp(120)
|
|
313
|
-
secondaryLine.addView(secondaryLeading, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)))
|
|
314
|
-
secondaryLine.addView(secondary, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)).apply {
|
|
315
|
-
marginStart = dp(4)
|
|
316
|
-
})
|
|
317
|
-
primaryLine.addView(primary, LayoutParams(LayoutParams.WRAP_CONTENT, dp(20)))
|
|
318
|
-
primaryLine.addView(badges, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)).apply {
|
|
319
|
-
marginStart = dp(6)
|
|
320
|
-
})
|
|
321
|
-
addView(primaryLine, LayoutParams(LayoutParams.WRAP_CONTENT, dp(20)))
|
|
322
|
-
addView(secondaryLine, LayoutParams(LayoutParams.MATCH_PARENT, dp(16)).apply {
|
|
323
|
-
topMargin = dp(4)
|
|
324
|
-
})
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
fun reset() {
|
|
328
|
-
primary.text = ""
|
|
329
|
-
secondaryLeading.text = ""
|
|
330
|
-
secondary.text = ""
|
|
331
|
-
secondaryLeading.visibility = GONE
|
|
332
|
-
secondary.visibility = GONE
|
|
333
|
-
secondaryLine.visibility = GONE
|
|
334
|
-
badges.removeAllViews()
|
|
335
|
-
badges.visibility = GONE
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
fun bind(
|
|
339
|
-
column: JSONObject,
|
|
340
|
-
rowBadges: JSONArray?,
|
|
341
|
-
primaryColor: Int,
|
|
342
|
-
secondaryColor: Int,
|
|
343
|
-
infoColor: Int,
|
|
344
|
-
) {
|
|
345
|
-
reset()
|
|
346
|
-
gravity = when (column.optString("alignment", "start")) {
|
|
347
|
-
"center" -> Gravity.CENTER_HORIZONTAL
|
|
348
|
-
"end" -> Gravity.END
|
|
349
|
-
else -> Gravity.START
|
|
350
|
-
}
|
|
351
|
-
primary.gravity = gravity
|
|
352
|
-
secondaryLeading.gravity = gravity
|
|
353
|
-
secondary.gravity = gravity
|
|
354
|
-
primary.text = column.optString("text")
|
|
355
|
-
primary.setTextColor(primaryColor)
|
|
356
|
-
if (rowBadges != null && rowBadges.length() > 0) {
|
|
357
|
-
badges.visibility = VISIBLE
|
|
358
|
-
for (index in 0 until minOf(2, rowBadges.length())) {
|
|
359
|
-
val badge = TextView(context).apply {
|
|
360
|
-
includeFontPadding = false
|
|
361
|
-
fontFeatureSettings = "tnum"
|
|
362
|
-
gravity = Gravity.CENTER
|
|
363
|
-
text = rowBadges.getJSONObject(index).optString("text")
|
|
364
|
-
textSize = sp(10f)
|
|
365
|
-
typeface = NativeListFonts.regular(context)
|
|
366
|
-
setTextColor(infoColor)
|
|
367
|
-
setPadding(dp(6), 0, dp(6), 0)
|
|
368
|
-
background = GradientDrawable().apply {
|
|
369
|
-
setColor(parseNativeListColor("#008FF519"))
|
|
370
|
-
cornerRadius = dp(4).toFloat()
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
badges.addView(badge, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)).apply {
|
|
374
|
-
if (index > 0) marginStart = dp(4)
|
|
375
|
-
})
|
|
376
|
-
}
|
|
377
|
-
}
|
|
378
|
-
val secondaryLeadingText = column.optString("secondaryLeadingText")
|
|
379
|
-
if (secondaryLeadingText.isNotEmpty()) {
|
|
380
|
-
secondaryLeading.text = secondaryLeadingText
|
|
381
|
-
secondaryLeading.setTextColor(secondaryColor)
|
|
382
|
-
secondaryLeading.visibility = VISIBLE
|
|
383
|
-
secondaryLine.visibility = VISIBLE
|
|
384
|
-
}
|
|
385
|
-
val secondaryText = column.optString("secondaryText")
|
|
386
|
-
if (secondaryText.isNotEmpty()) {
|
|
387
|
-
secondary.text = secondaryText
|
|
388
|
-
secondary.setTextColor(secondaryColor)
|
|
389
|
-
secondary.visibility = VISIBLE
|
|
390
|
-
secondaryLine.visibility = VISIBLE
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
private fun dp(value: Int): Int = NativeListScale.dp(resources, value)
|
|
395
|
-
private fun sp(value: Float): Float = NativeListScale.font(resources, value)
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
private object NativeListSourceFallbackState {
|
|
399
|
-
private const val CACHE_LIMIT = 128
|
|
400
|
-
private val sources = LinkedHashMap<String, Unit>(CACHE_LIMIT, 0.75f, true)
|
|
401
|
-
|
|
402
|
-
fun has(key: String): Boolean = synchronized(sources) {
|
|
403
|
-
sources[key] != null
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
fun remember(key: String) {
|
|
407
|
-
synchronized(sources) {
|
|
408
|
-
sources[key] = Unit
|
|
409
|
-
while (sources.size > CACHE_LIMIT) {
|
|
410
|
-
sources.remove(sources.entries.first().key)
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
fun forget(key: String) {
|
|
416
|
-
synchronized(sources) {
|
|
417
|
-
sources.remove(key)
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
internal class NativeListRowView(
|
|
423
|
-
private val reactContext: ThemedReactContext,
|
|
424
|
-
) : LinearLayout(reactContext) {
|
|
425
|
-
private var marketLeadingUsesSourceClip = false
|
|
426
|
-
private val leadingFrame = object : FrameLayout(context) {
|
|
427
|
-
override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean {
|
|
428
|
-
if (!marketLeadingUsesSourceClip || child !== leadingImages[0]) return super.drawChild(canvas, child, drawingTime)
|
|
429
|
-
// OneKey patch: clip only the Market avatar to the source border's padding box.
|
|
430
|
-
val saved = canvas.save()
|
|
431
|
-
BackgroundStyleApplicator.clipToPaddingBox(this, canvas)
|
|
432
|
-
return try { super.drawChild(canvas, child, drawingTime) } finally { canvas.restoreToCount(saved) }
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
// OneKey patch: reset selector fragments and corner decorations on every bind.
|
|
436
|
-
private val selectorViews = mutableListOf<View>()
|
|
437
|
-
private var selectorUsesSourceScale = false
|
|
438
|
-
private val selectorAccessibilityDelegate = object : View.AccessibilityDelegate() {
|
|
439
|
-
override fun onInitializeAccessibilityNodeInfo(host: View, info: android.view.accessibility.AccessibilityNodeInfo) {
|
|
440
|
-
super.onInitializeAccessibilityNodeInfo(host, info)
|
|
441
|
-
info.viewIdResourceName = host.getTag(com.facebook.react.R.id.react_test_id) as? String
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
private val selectorOriginalFontFeatures = mutableMapOf<TextView, String?>()
|
|
445
|
-
private val selectorOriginalPaintFlags = mutableMapOf<TextView, Int>()
|
|
446
|
-
private val marketOriginalPaintFlags = mutableMapOf<TextView, Int>()
|
|
447
|
-
private val selectorLineHeights = mutableMapOf<TextView, Int>()
|
|
448
|
-
private val selectorFontSizes = mutableMapOf<TextView, Float>()
|
|
449
|
-
private val selectorImages = mutableListOf<OneKeyImageReusableView>()
|
|
450
|
-
private var selectorHeight: Int? = null
|
|
451
|
-
private val leadingImages = List(3) { OneKeyImageReusableView(reactContext) }
|
|
452
|
-
private val leadingOverlayBackground = View(context)
|
|
453
|
-
private val leadingCornerIconFrame = FrameLayout(context)
|
|
454
|
-
private val leadingCornerIcon = OneKeyIconView(context)
|
|
455
|
-
private val leadingFallback = TextView(context)
|
|
456
|
-
private val leadingIcon = OneKeyIconView(context)
|
|
457
|
-
private val favoriteIcon = OneKeyIconView(context)
|
|
458
|
-
private val headerTitleIcon = OneKeyIconView(context)
|
|
459
|
-
private val headerValueIcon = OneKeyIconView(context)
|
|
460
|
-
private val leadingActionIcon = OneKeyIconView(context)
|
|
461
|
-
private val secondaryImage = OneKeyImageReusableView(reactContext)
|
|
462
|
-
private val mediaNetworkImage = OneKeyImageReusableView(reactContext)
|
|
463
|
-
private val metricVisualImages = List(5) { OneKeyImageReusableView(reactContext) }
|
|
464
|
-
private val mainColumn = LinearLayout(context)
|
|
465
|
-
private val mediaMetadataRow = LinearLayout(context)
|
|
466
|
-
private val titleLine = PackedTitleLineLayout(context)
|
|
467
|
-
private val title = DottedUnderlineTextView(context)
|
|
468
|
-
private val subtitle = TextView(context)
|
|
469
|
-
// OneKey patch: the first text shrinks while the volume retains its width.
|
|
470
|
-
private val marketSubtitleLine = PackedTitleLineLayout(context)
|
|
471
|
-
private val tertiary = TextView(context)
|
|
472
|
-
private val status = TextView(context)
|
|
473
|
-
private val metricSubtitle = TextView(context)
|
|
474
|
-
private val badgeLine = TextView(context)
|
|
475
|
-
private val marketBadgeViews = List(3) { LinearLayout(context) }
|
|
476
|
-
private val marketBadgeLabels = List(3) { TextView(context) }
|
|
477
|
-
private val marketBadgeImages = List(3) { OneKeyImageReusableView(reactContext) }
|
|
478
|
-
private val marketBadgeGlyphs = List(3) { OneKeyIconView(context) }
|
|
479
|
-
private val activityContentRow = LinearLayout(context)
|
|
480
|
-
private val actionLine = LinearLayout(context)
|
|
481
|
-
private val actionViews = List(3) { TextView(context) }
|
|
482
|
-
private val trailingColumn = LinearLayout(context)
|
|
483
|
-
private val trailingViews = List(2) { TextView(context) }
|
|
484
|
-
private val trailingIcons = List(2) { OneKeyIconView(context) }
|
|
485
|
-
private val checkbox = OneKeyCheckboxView(context)
|
|
486
|
-
private val spinner = ProgressBar(context)
|
|
487
|
-
private val dataContainer = LinearLayout(context)
|
|
488
|
-
private val dataColumns = List(4) { TextView(context) }
|
|
489
|
-
private val tableDataContainer = LinearLayout(context)
|
|
490
|
-
private val tableDataColumns = List(4) { NativeListTableColumnView(context) }
|
|
491
|
-
private val unreadDot = View(context)
|
|
492
|
-
private val mediaBadge = TextView(context)
|
|
493
|
-
private val skeletonPrimary = View(context)
|
|
494
|
-
private val skeletonSecondary = View(context)
|
|
495
|
-
private val walletGroupRows = mutableListOf<NativeListRowView>()
|
|
496
|
-
private val walletGroupDragBadgeBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
497
|
-
private val walletGroupDragBadgeBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
498
|
-
style = Paint.Style.STROKE
|
|
499
|
-
}
|
|
500
|
-
private val walletGroupDragBadgeTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
501
|
-
textAlign = Paint.Align.CENTER
|
|
502
|
-
typeface = NativeListFonts.semibold(context)
|
|
503
|
-
}
|
|
504
|
-
private var walletGroupDragChildCount = 0
|
|
505
|
-
private var walletGroupExpandedHeightPx = 0
|
|
506
|
-
private var walletGroupExpandAnimator: ValueAnimator? = null
|
|
507
|
-
private var isMediaTile = false
|
|
508
|
-
private var boundKey: String? = null
|
|
509
|
-
// OneKey patch: delayed retries cannot survive cell rebinding or recycling.
|
|
510
|
-
private val selectorImageRetries = mutableMapOf<OneKeyImageReusableView, Runnable>()
|
|
511
|
-
var bindingEpoch: Long = 0
|
|
512
|
-
private set
|
|
513
|
-
private var boundCheckboxData: JSONObject? = null
|
|
514
|
-
private var currentLayout = "linear"
|
|
515
|
-
private var restingRowBackground: Drawable? = null
|
|
516
|
-
private var pressedRowBackground: Drawable? = null
|
|
517
|
-
// OneKey patch: preserve a held row independently from RecyclerView snapshot rebinding.
|
|
518
|
-
private var touchPressed = false
|
|
519
|
-
private val marketLongPressHandler = Handler(Looper.getMainLooper())
|
|
520
|
-
private var marketLongPressRunnable: Runnable? = null
|
|
521
|
-
private var marketTouchStartX = 0f
|
|
522
|
-
private var marketTouchStartY = 0f
|
|
523
|
-
private var marketTouchX = 0f
|
|
524
|
-
private var marketTouchY = 0f
|
|
525
|
-
private var marketLongPressFired = false
|
|
526
|
-
private var reorderActive = false
|
|
527
|
-
private var checkboxCheckedColor = Color.rgb(32, 32, 32)
|
|
528
|
-
private var checkboxUncheckedColor = Color.rgb(252, 252, 252)
|
|
529
|
-
private var checkboxBorderColor = Color.rgb(206, 206, 206)
|
|
530
|
-
private var checkboxIconColor = Color.rgb(252, 252, 252)
|
|
531
|
-
private var checkboxUsesSelectorStyle = false
|
|
532
|
-
private var iconSubduedColor = Color.rgb(141, 141, 141)
|
|
533
|
-
private var visualBackdropColor = Color.WHITE
|
|
534
|
-
private var imagePlaceholderColor = "#0000000F"
|
|
535
|
-
private val circleOutlineProvider = object : ViewOutlineProvider() {
|
|
536
|
-
override fun getOutline(view: View, outline: Outline) {
|
|
537
|
-
outline.setOval(0, 0, view.width, view.height)
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
private val separatorPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
541
|
-
private var showsSeparator = false
|
|
542
|
-
|
|
543
|
-
var onRowPress: ((NativeListItem, NativeListActionOrigin) -> Unit)? = null
|
|
544
|
-
var onAction: ((NativeListItem, String, NativeSelectionTarget?, NativeListActionOrigin?) -> Unit)? = null
|
|
545
|
-
var onBindingInvalidated: ((NativeListRowView, Long) -> Unit)? = null
|
|
546
|
-
|
|
547
|
-
init {
|
|
548
|
-
gravity = Gravity.CENTER_VERTICAL
|
|
549
|
-
isClickable = true
|
|
550
|
-
isFocusable = true
|
|
551
|
-
|
|
552
|
-
leadingFallback.gravity = Gravity.CENTER
|
|
553
|
-
leadingFallback.typeface = NativeListFonts.bold(context)
|
|
554
|
-
leadingFrame.addView(leadingFallback)
|
|
555
|
-
leadingFrame.addView(leadingIcon)
|
|
556
|
-
leadingFrame.addView(leadingImages[0])
|
|
557
|
-
leadingFrame.addView(leadingOverlayBackground)
|
|
558
|
-
leadingFrame.addView(leadingImages[1])
|
|
559
|
-
leadingFrame.addView(leadingImages[2])
|
|
560
|
-
leadingCornerIconFrame.addView(leadingCornerIcon)
|
|
561
|
-
leadingFrame.addView(leadingCornerIconFrame)
|
|
562
|
-
leadingFrame.addView(unreadDot)
|
|
563
|
-
leadingFrame.addView(mediaBadge)
|
|
564
|
-
leadingFrame.clipChildren = false
|
|
565
|
-
leadingFrame.clipToPadding = false
|
|
566
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(18), dp(18), Gravity.CENTER)
|
|
567
|
-
favoriteIcon.layoutParams = LayoutParams(dp(24), dp(24))
|
|
568
|
-
leadingActionIcon.layoutParams = LayoutParams(dp(24), dp(24))
|
|
569
|
-
leadingCornerIcon.layoutParams = FrameLayout.LayoutParams(dp(18), dp(18), Gravity.CENTER)
|
|
570
|
-
unreadDot.layoutParams = FrameLayout.LayoutParams(dp(8), dp(8), Gravity.TOP or Gravity.END)
|
|
571
|
-
mainColumn.orientation = VERTICAL
|
|
572
|
-
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
573
|
-
title.typeface = NativeListFonts.regular(context)
|
|
574
|
-
subtitle.typeface = NativeListFonts.regular(context)
|
|
575
|
-
tertiary.typeface = NativeListFonts.regular(context)
|
|
576
|
-
status.typeface = NativeListFonts.regular(context)
|
|
577
|
-
metricSubtitle.typeface = NativeListFonts.regular(context)
|
|
578
|
-
badgeLine.typeface = NativeListFonts.medium(context)
|
|
579
|
-
listOf(title, subtitle, tertiary, status, metricSubtitle, badgeLine).forEach {
|
|
580
|
-
it.includeFontPadding = false
|
|
581
|
-
it.fontFeatureSettings = "tnum"
|
|
582
|
-
}
|
|
583
|
-
subtitle.maxLines = 2
|
|
584
|
-
status.maxLines = 1
|
|
585
|
-
metricSubtitle.maxLines = 1
|
|
586
|
-
badgeLine.maxLines = 1
|
|
587
|
-
titleLine.orientation = HORIZONTAL
|
|
588
|
-
titleLine.gravity = Gravity.CENTER_VERTICAL
|
|
589
|
-
titleLine.addView(title, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
590
|
-
titleLine.addView(badgeLine, wrap())
|
|
591
|
-
marketBadgeViews.forEachIndexed { index, badge ->
|
|
592
|
-
badge.orientation = HORIZONTAL
|
|
593
|
-
badge.gravity = Gravity.CENTER_VERTICAL
|
|
594
|
-
badge.isClickable = true
|
|
595
|
-
marketBadgeGlyphs[index].visibility = GONE
|
|
596
|
-
marketBadgeImages[index].visibility = GONE
|
|
597
|
-
marketBadgeLabels[index].apply {
|
|
598
|
-
includeFontPadding = false
|
|
599
|
-
maxLines = 1
|
|
600
|
-
ellipsize = TextUtils.TruncateAt.END
|
|
601
|
-
textSize = sp(11f)
|
|
602
|
-
typeface = NativeListFonts.medium(context)
|
|
603
|
-
}
|
|
604
|
-
badge.addView(marketBadgeGlyphs[index], LayoutParams(dp(16), dp(16)))
|
|
605
|
-
badge.addView(marketBadgeImages[index], LayoutParams(dp(14), dp(14)))
|
|
606
|
-
badge.addView(marketBadgeLabels[index], wrap())
|
|
607
|
-
}
|
|
608
|
-
mainColumn.addView(titleLine)
|
|
609
|
-
mainColumn.addView(subtitle)
|
|
610
|
-
mainColumn.addView(tertiary)
|
|
611
|
-
mainColumn.addView(status)
|
|
612
|
-
mainColumn.addView(metricSubtitle)
|
|
613
|
-
|
|
614
|
-
actionLine.orientation = HORIZONTAL
|
|
615
|
-
actionViews.forEach { action ->
|
|
616
|
-
action.typeface = NativeListFonts.semibold(context)
|
|
617
|
-
action.includeFontPadding = false
|
|
618
|
-
action.textSize = sp(14f)
|
|
619
|
-
TextViewCompat.setLineHeight(action, dp(20))
|
|
620
|
-
action.setPadding(dp(8), dp(4), dp(8), dp(4))
|
|
621
|
-
actionLine.addView(action)
|
|
622
|
-
}
|
|
623
|
-
mainColumn.addView(actionLine)
|
|
624
|
-
|
|
625
|
-
trailingColumn.orientation = VERTICAL
|
|
626
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
627
|
-
trailingViews.forEach { trailingColumn.addView(it) }
|
|
628
|
-
trailingIcons.forEach { trailingColumn.addView(it) }
|
|
629
|
-
trailingColumn.addView(checkbox)
|
|
630
|
-
trailingColumn.addView(spinner)
|
|
631
|
-
|
|
632
|
-
dataContainer.orientation = HORIZONTAL
|
|
633
|
-
dataContainer.gravity = Gravity.CENTER_VERTICAL
|
|
634
|
-
dataColumns.forEach { dataContainer.addView(it) }
|
|
635
|
-
tableDataContainer.orientation = HORIZONTAL
|
|
636
|
-
tableDataContainer.gravity = Gravity.CENTER_VERTICAL
|
|
637
|
-
tableDataColumns.forEach { tableDataContainer.addView(it) }
|
|
638
|
-
|
|
639
|
-
mediaBadge.gravity = Gravity.CENTER
|
|
640
|
-
mediaBadge.typeface = NativeListFonts.regular(context)
|
|
641
|
-
secondaryImage.outlineProvider = object : ViewOutlineProvider() {
|
|
642
|
-
override fun getOutline(view: View, outline: Outline) {
|
|
643
|
-
outline.setRoundRect(0, 0, view.width, view.height, dp(6).toFloat())
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
secondaryImage.clipToOutline = true
|
|
647
|
-
|
|
648
|
-
setOnTouchListener { _, event ->
|
|
649
|
-
when (event.actionMasked) {
|
|
650
|
-
MotionEvent.ACTION_DOWN -> if ((tag as? NativeListItem)?.isRowPressEnabled == true) {
|
|
651
|
-
touchPressed = true
|
|
652
|
-
marketLongPressFired = false
|
|
653
|
-
val item = tag as? NativeListItem
|
|
654
|
-
if (item?.type == "market") {
|
|
655
|
-
marketTouchStartX = event.x
|
|
656
|
-
marketTouchStartY = event.y
|
|
657
|
-
marketTouchX = event.x
|
|
658
|
-
marketTouchY = event.y
|
|
659
|
-
item.json.optString("pressInActionKey").takeIf(String::isNotEmpty)?.let { actionKey ->
|
|
660
|
-
emitAction(item, actionKey, null, this, "row")
|
|
661
|
-
}
|
|
662
|
-
item.json.optString("longPressActionKey").takeIf(String::isNotEmpty)?.let { actionKey ->
|
|
663
|
-
val expectedKey = item.key
|
|
664
|
-
val runnable = Runnable {
|
|
665
|
-
val current = tag as? NativeListItem
|
|
666
|
-
if (touchPressed && current?.key == expectedKey && current.type == "market") {
|
|
667
|
-
marketLongPressRunnable = null
|
|
668
|
-
marketLongPressFired = true
|
|
669
|
-
val location = IntArray(2)
|
|
670
|
-
getLocationInWindow(location)
|
|
671
|
-
onAction?.invoke(current, actionKey, null, actionOrigin(this, "row").copy(
|
|
672
|
-
windowPointPixels = android.graphics.PointF(location[0] + marketTouchX, location[1] + marketTouchY),
|
|
673
|
-
))
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
marketLongPressRunnable = runnable
|
|
677
|
-
marketLongPressHandler.postDelayed(runnable, 800L)
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
if ((tag as? NativeListItem)?.type == "mediaTile") {
|
|
681
|
-
leadingFrame.alpha = 0.8f
|
|
682
|
-
} else {
|
|
683
|
-
background = pressedRowBackground
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
MotionEvent.ACTION_MOVE -> {
|
|
687
|
-
marketTouchX = event.x
|
|
688
|
-
marketTouchY = event.y
|
|
689
|
-
val outside = event.x < 0 || event.y < 0 || event.x >= width || event.y >= height
|
|
690
|
-
val movedMarket = (tag as? NativeListItem)?.type == "market" &&
|
|
691
|
-
(kotlin.math.abs(event.x - marketTouchStartX) > dp(10) ||
|
|
692
|
-
kotlin.math.abs(event.y - marketTouchStartY) > dp(10))
|
|
693
|
-
if (outside || movedMarket) {
|
|
694
|
-
cancelMarketLongPress()
|
|
695
|
-
touchPressed = false
|
|
696
|
-
restoreRestingBackground()
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
700
|
-
cancelMarketLongPress()
|
|
701
|
-
touchPressed = false
|
|
702
|
-
restoreRestingBackground()
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
false
|
|
706
|
-
}
|
|
707
|
-
setOnClickListener { view ->
|
|
708
|
-
(view.tag as? NativeListItem)?.let { item ->
|
|
709
|
-
if (item.type == "market" && marketLongPressFired) {
|
|
710
|
-
marketLongPressFired = false
|
|
711
|
-
return@let
|
|
712
|
-
}
|
|
713
|
-
// OneKey patch: allow create-address accessories when whole-row press is gated.
|
|
714
|
-
if (item.isRowPressEnabled) onRowPress?.invoke(item, actionOrigin(view, "row"))
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
setWillNotDraw(false)
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
override fun dispatchDraw(canvas: Canvas) {
|
|
721
|
-
super.dispatchDraw(canvas)
|
|
722
|
-
if (showsSeparator) {
|
|
723
|
-
val start = if ((tag as? NativeListItem)?.type == "identity") dp(60).toFloat() else dp(12).toFloat()
|
|
724
|
-
canvas.drawLine(start, height - 1f, width.toFloat(), height - 1f, separatorPaint)
|
|
725
|
-
}
|
|
726
|
-
if (reorderActive && (tag as? NativeListItem)?.type == "walletGroup" && walletGroupDragChildCount > 0) {
|
|
727
|
-
val label = "+$walletGroupDragChildCount"
|
|
728
|
-
val badgeHeight = dp(24).toFloat()
|
|
729
|
-
val badgeWidth = maxOf(
|
|
730
|
-
dp(24).toFloat(),
|
|
731
|
-
walletGroupDragBadgeTextPaint.measureText(label) + dp(12),
|
|
732
|
-
)
|
|
733
|
-
val right = width - dp(4).toFloat()
|
|
734
|
-
val bottom = height - dp(4).toFloat()
|
|
735
|
-
val left = right - badgeWidth
|
|
736
|
-
val top = bottom - badgeHeight
|
|
737
|
-
canvas.drawRoundRect(
|
|
738
|
-
left,
|
|
739
|
-
top,
|
|
740
|
-
right,
|
|
741
|
-
bottom,
|
|
742
|
-
badgeHeight / 2f,
|
|
743
|
-
badgeHeight / 2f,
|
|
744
|
-
walletGroupDragBadgeBackgroundPaint,
|
|
745
|
-
)
|
|
746
|
-
canvas.drawRoundRect(
|
|
747
|
-
left,
|
|
748
|
-
top,
|
|
749
|
-
right,
|
|
750
|
-
bottom,
|
|
751
|
-
dp(12).toFloat(),
|
|
752
|
-
dp(12).toFloat(),
|
|
753
|
-
walletGroupDragBadgeBorderPaint,
|
|
754
|
-
)
|
|
755
|
-
val metrics = walletGroupDragBadgeTextPaint.fontMetrics
|
|
756
|
-
val baseline = (top + bottom - metrics.ascent - metrics.descent) / 2f
|
|
757
|
-
canvas.drawText(label, (left + right) / 2f, baseline, walletGroupDragBadgeTextPaint)
|
|
758
|
-
}
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
// OneKey patch: RecyclerView may replace item delegates during a selection update.
|
|
762
|
-
override fun onInitializeAccessibilityNodeInfo(info: android.view.accessibility.AccessibilityNodeInfo) {
|
|
763
|
-
super.onInitializeAccessibilityNodeInfo(info)
|
|
764
|
-
info.viewIdResourceName = getTag(com.facebook.react.R.id.react_test_id) as? String
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
768
|
-
val marketItem = (tag as? NativeListItem)?.takeIf { it.type == "market" }
|
|
769
|
-
val marketStyle = marketItem?.json?.optJSONObject("style")
|
|
770
|
-
if (marketStyle?.has("horizontalPadding") == true) {
|
|
771
|
-
// OneKey patch: Yoga rounds the two absolute edges, not both padding values.
|
|
772
|
-
val sourcePadding = marketStyle.optDouble("horizontalPadding") * resources.displayMetrics.density
|
|
773
|
-
val width = MeasureSpec.getSize(widthMeasureSpec)
|
|
774
|
-
val rowHeight = selectorHeight
|
|
775
|
-
val sourceVerticalPadding = marketStyle.takeIf { it.has("verticalPadding") && rowHeight != null }
|
|
776
|
-
?.optDouble("verticalPadding")?.times(resources.displayMetrics.density)
|
|
777
|
-
setPadding(
|
|
778
|
-
sourcePadding.roundToInt(),
|
|
779
|
-
sourceVerticalPadding?.roundToInt() ?: paddingTop,
|
|
780
|
-
width - (width - sourcePadding).roundToInt(),
|
|
781
|
-
if (sourceVerticalPadding != null && rowHeight != null) rowHeight - (rowHeight - sourceVerticalPadding).roundToInt() else paddingBottom,
|
|
782
|
-
)
|
|
783
|
-
}
|
|
784
|
-
if (isMediaTile) {
|
|
785
|
-
val availableWidth = (MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight)
|
|
786
|
-
.coerceAtLeast(0)
|
|
787
|
-
leadingFrame.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, availableWidth)
|
|
788
|
-
}
|
|
789
|
-
// OneKey patch: RecyclerView must use the adapter's measured selector height.
|
|
790
|
-
super.onMeasure(widthMeasureSpec, selectorHeight?.let { MeasureSpec.makeMeasureSpec(it, MeasureSpec.EXACTLY) } ?: heightMeasureSpec)
|
|
791
|
-
val item = tag as? NativeListItem ?: return
|
|
792
|
-
if (item.json.has("height") && item.json.optString("presentation") == "networkSelector" && item.type in setOf("identity", "sectionHeader") && checkbox.visibility == VISIBLE && trailingColumn.parent === this) {
|
|
793
|
-
// OneKey patch: Yoga snaps the compound accessory before its children; their rounded edges can overflow it.
|
|
794
|
-
val density = resources.displayMetrics.density
|
|
795
|
-
val visibleValues = trailingViews.filter { it.visibility == VISIBLE }
|
|
796
|
-
val sourceTrailingWidth = visibleValues.sumOf { it.measuredWidth } + (20 + 12 * visibleValues.size) * density
|
|
797
|
-
val sourceTrailingLeft = (measuredWidth - 12 * density - sourceTrailingWidth).roundToInt()
|
|
798
|
-
val measuredTrailingLeft = measuredWidth - paddingRight - trailingColumn.measuredWidth
|
|
799
|
-
val mainWidth = (mainColumn.measuredWidth + sourceTrailingLeft - measuredTrailingLeft).coerceAtLeast(0)
|
|
800
|
-
mainColumn.measure(MeasureSpec.makeMeasureSpec(mainWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mainColumn.measuredHeight, MeasureSpec.EXACTLY))
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
|
|
804
|
-
// OneKey patch: Yoga rounds a half-pixel text center upward; LinearLayout truncates it.
|
|
805
|
-
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
|
806
|
-
super.onLayout(changed, left, top, right, bottom)
|
|
807
|
-
val item = tag as? NativeListItem ?: return
|
|
808
|
-
if (item.type == "market" && item.json.optJSONObject("style") != null) {
|
|
809
|
-
// OneKey patch: preserve Yoga's half-pixel centering for Market columns and badges.
|
|
810
|
-
for (column in listOf(leadingFrame, mainColumn, trailingColumn)) {
|
|
811
|
-
if (column.parent === this && column.visibility != GONE) {
|
|
812
|
-
column.offsetTopAndBottom((height - column.height + 1) / 2 - column.top)
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
// OneKey patch: token avatars stay centered in the row when text rounding adds a pixel.
|
|
816
|
-
if (item.json.optString("variant") != "token" && leadingFrame.parent === this && mainColumn.parent === this) {
|
|
817
|
-
leadingFrame.offsetTopAndBottom(mainColumn.top + (mainColumn.height - leadingFrame.height + 1) / 2 - leadingFrame.top)
|
|
818
|
-
}
|
|
819
|
-
// OneKey patch: preserve Yoga's absolute-edge rounding for the Market network badge.
|
|
820
|
-
if (leadingOverlayBackground.visibility == VISIBLE && item.json.optString("variant") == "token") {
|
|
821
|
-
val recycler = parent as? RecyclerView
|
|
822
|
-
val adapter = recycler?.adapter as? NativeListAdapter
|
|
823
|
-
val position = recycler?.getChildAdapterPosition(this) ?: RecyclerView.NO_POSITION
|
|
824
|
-
if (recycler != null && adapter != null && position != RecyclerView.NO_POSITION && adapter.itemAt(position)?.key == item.key) {
|
|
825
|
-
val density = resources.displayMetrics.density.toDouble()
|
|
826
|
-
val rowTop = adapter.marketSourceEdgePx(position)
|
|
827
|
-
val rowHeight = adapter.marketSourceEdgePx(position + 1) - rowTop
|
|
828
|
-
val imageHeight = item.json.optJSONObject("style")?.optJSONObject("image")?.optDouble("height", 32.0) ?: 32.0
|
|
829
|
-
val contentTop = (recycler.paddingTop / density).roundToInt() * density
|
|
830
|
-
val badgeTop = contentTop + rowTop + (rowHeight - imageHeight * density) / 2 + (imageHeight - 16) * density
|
|
831
|
-
val badgeHeight = (badgeTop + 20 * density).roundToInt() - badgeTop.roundToInt()
|
|
832
|
-
val top = ((imageHeight - 16) * density).roundToInt()
|
|
833
|
-
leadingOverlayBackground.layout(leadingOverlayBackground.left, top, leadingOverlayBackground.right, top + badgeHeight)
|
|
834
|
-
}
|
|
835
|
-
}
|
|
836
|
-
for (badge in marketBadgeViews) {
|
|
837
|
-
if (badge.parent === titleLine && badge.visibility != GONE) {
|
|
838
|
-
badge.offsetTopAndBottom((titleLine.height - badge.height + 1) / 2 - badge.top)
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
// OneKey patch: preserve fractional text advances and gaps until the final edge.
|
|
842
|
-
val style = item.json.optJSONObject("style")
|
|
843
|
-
for ((line, gap) in listOf(
|
|
844
|
-
titleLine to style.optDouble("titleBadgeGap", 4.0),
|
|
845
|
-
marketSubtitleLine to (item.json.optJSONObject("subtitlePrefix")?.optDouble("gap", 4.0) ?: 0.0),
|
|
846
|
-
)) {
|
|
847
|
-
var cursor = 0f
|
|
848
|
-
var hasPrevious = false
|
|
849
|
-
for (index in 0 until line.childCount) {
|
|
850
|
-
val child = line.getChildAt(index)
|
|
851
|
-
if (child.visibility == GONE) continue
|
|
852
|
-
if (hasPrevious) {
|
|
853
|
-
cursor += (gap * resources.displayMetrics.density).toFloat()
|
|
854
|
-
child.offsetLeftAndRight(cursor.roundToInt() - child.left)
|
|
855
|
-
}
|
|
856
|
-
val advance = if (index == 0 && child is TextView) {
|
|
857
|
-
val textWidth = if (line === titleLine) child.paint.measureText(child.text.toString()) else child.layout?.getLineWidth(0)
|
|
858
|
-
textWidth?.coerceAtMost(child.width.toFloat()) ?: child.width.toFloat()
|
|
859
|
-
} else child.width.toFloat()
|
|
860
|
-
cursor = if (hasPrevious) cursor + advance else child.left + advance
|
|
861
|
-
hasPrevious = true
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
val accessory = item.json.optJSONArray("trailing")?.optJSONObject(0)
|
|
866
|
-
if (item.type == "identity" && item.json.has("height") && item.json.optString("presentation") == "accountSelector" && accessory?.optString("kind") == "icon" && accessory.optString("name") == "PlusSmallOutline") {
|
|
867
|
-
// OneKey patch: the borderless Plus retains the source's fixed top18/negative7 slot.
|
|
868
|
-
val icon = trailingIcons[0]
|
|
869
|
-
trailingColumn.offsetTopAndBottom(dp(18) - dp(7) - trailingColumn.top - icon.top)
|
|
870
|
-
}
|
|
871
|
-
if (item.type == "action" && item.json.has("height") && item.json.optString("presentation") == "accountSelector" && leadingIcon.visibility == VISIBLE) {
|
|
872
|
-
// OneKey patch: Yoga rounds the 4dp padding inside the 32dp Add account icon upward.
|
|
873
|
-
leadingIcon.offsetLeftAndRight((leadingFrame.width - leadingIcon.width + 1) / 2 - leadingIcon.left)
|
|
874
|
-
leadingIcon.offsetTopAndBottom((leadingFrame.height - leadingIcon.height + 1) / 2 - leadingIcon.top)
|
|
875
|
-
}
|
|
876
|
-
if (!item.json.has("height")) return
|
|
877
|
-
val isNetworkIdentity = item.type == "identity" && item.json.optString("presentation") == "networkSelector"
|
|
878
|
-
val isAccountAction = item.type == "action" && item.json.optString("presentation") == "accountSelector"
|
|
879
|
-
val isNetworkSummary = item.type == "sectionHeader" && item.json.optString("presentation") == "networkSelector" && item.json.optString("variant") == "summary"
|
|
880
|
-
val centeredColumns = when {
|
|
881
|
-
isNetworkIdentity || isAccountAction -> listOf(mainColumn, trailingColumn)
|
|
882
|
-
isNetworkSummary -> listOf(trailingColumn)
|
|
883
|
-
else -> return
|
|
884
|
-
}
|
|
885
|
-
for (column in centeredColumns) {
|
|
886
|
-
if (column.parent !== this || column.visibility == GONE) continue
|
|
887
|
-
val margins = column.layoutParams as MarginLayoutParams
|
|
888
|
-
val available = height - paddingTop - paddingBottom - margins.topMargin - margins.bottomMargin
|
|
889
|
-
val desiredTop = paddingTop + margins.topMargin + (available - column.height + 1) / 2
|
|
890
|
-
column.offsetTopAndBottom(desiredTop - column.top)
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
fun bind(
|
|
895
|
-
item: NativeListItem,
|
|
896
|
-
theme: JSONObject?,
|
|
897
|
-
layout: String,
|
|
898
|
-
listOrientation: String,
|
|
899
|
-
itemIndex: Int?,
|
|
900
|
-
selected: Boolean,
|
|
901
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
902
|
-
useSourceScale: Boolean = false,
|
|
903
|
-
) {
|
|
904
|
-
val reusesImageIdentity = boundKey == item.key && (tag as? NativeListItem)?.type == item.type
|
|
905
|
-
val shouldRestorePressed = touchPressed && boundKey == item.key
|
|
906
|
-
cancelMarketLongPress()
|
|
907
|
-
marketLongPressFired = false
|
|
908
|
-
invalidateCurrentBinding()
|
|
909
|
-
bindingEpoch += 1
|
|
910
|
-
boundKey = item.key
|
|
911
|
-
touchPressed = shouldRestorePressed
|
|
912
|
-
currentLayout = layout
|
|
913
|
-
tag = item
|
|
914
|
-
selectorUsesSourceScale = item.type == "market" || item.type == "system" && item.json.optString("presentation") == "market" && item.json.optString("variant") == "retry" || item.usesSelectorSourceScale || useSourceScale
|
|
915
|
-
reorderActive = false
|
|
916
|
-
if (!reusesImageIdentity) {
|
|
917
|
-
selectorImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
918
|
-
leadingImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
919
|
-
secondaryImage.prepareForReuse()
|
|
920
|
-
mediaNetworkImage.prepareForReuse()
|
|
921
|
-
metricVisualImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
922
|
-
}
|
|
923
|
-
resetViews()
|
|
924
|
-
|
|
925
|
-
val primary = color(theme, "primaryText", "#000000DF")
|
|
926
|
-
val secondary = color(theme, "secondaryText", "#0000009B")
|
|
927
|
-
val accent = color(theme, "accent", "#0D8200FC")
|
|
928
|
-
checkboxCheckedColor = primary
|
|
929
|
-
checkboxUncheckedColor = color(theme, "inverseText", "#FCFCFC")
|
|
930
|
-
checkboxIconColor = checkboxUncheckedColor
|
|
931
|
-
checkboxUsesSelectorStyle = item.json.optString("presentation") == "networkSelector"
|
|
932
|
-
checkboxBorderColor = Color.argb(
|
|
933
|
-
0x31,
|
|
934
|
-
0,
|
|
935
|
-
0,
|
|
936
|
-
0,
|
|
937
|
-
)
|
|
938
|
-
if (item.json.optString("presentation") == "networkSelector") {
|
|
939
|
-
checkboxCheckedColor = color(theme, "checkboxBackground", "#202020")
|
|
940
|
-
checkboxBorderColor = color(theme, "checkboxBorder", "#00000031")
|
|
941
|
-
checkboxIconColor = color(theme, "checkboxIcon", "#FFFFFF")
|
|
942
|
-
// OneKey patch: the V1 checkbox fills even its unchecked body with iconInverse.
|
|
943
|
-
checkboxUncheckedColor = checkboxIconColor
|
|
944
|
-
}
|
|
945
|
-
iconSubduedColor = color(theme, "iconSubdued", "#00000072")
|
|
946
|
-
visualBackdropColor = color(theme, "rowBackground", "#FFFFFF")
|
|
947
|
-
imagePlaceholderColor = theme?.optString("strongBackground", "#0000000F")
|
|
948
|
-
?.takeIf(String::isNotEmpty) ?: "#0000000F"
|
|
949
|
-
unreadDot.background = roundedFill(
|
|
950
|
-
parseNativeListColor("#E5484D"),
|
|
951
|
-
4f,
|
|
952
|
-
)
|
|
953
|
-
applySelectionState(item, theme, layout, itemIndex, selected)
|
|
954
|
-
pressedRowBackground = groupedBackground(
|
|
955
|
-
when (item.type) {
|
|
956
|
-
"rail" -> "rail"
|
|
957
|
-
"mediaTile" -> "mediaTile"
|
|
958
|
-
else -> "single"
|
|
959
|
-
},
|
|
960
|
-
color(
|
|
961
|
-
theme,
|
|
962
|
-
if (item.type == "rail") "strongBackground" else "rowPressedBackground",
|
|
963
|
-
if (item.type == "rail") "#0000000F" else "#00000017",
|
|
964
|
-
),
|
|
965
|
-
)
|
|
966
|
-
background = if (touchPressed || reorderActive) pressedRowBackground else restingRowBackground
|
|
967
|
-
if (layout == "table") {
|
|
968
|
-
if (item.type == "dataRow") {
|
|
969
|
-
setPadding(dp(20), dp(10), dp(20), dp(10))
|
|
970
|
-
} else {
|
|
971
|
-
setPadding(dp(16), dp(8), dp(16), dp(8))
|
|
972
|
-
}
|
|
973
|
-
}
|
|
974
|
-
title.setTextColor(primary)
|
|
975
|
-
subtitle.setTextColor(secondary)
|
|
976
|
-
tertiary.setTextColor(secondary)
|
|
977
|
-
status.setTextColor(secondary)
|
|
978
|
-
metricSubtitle.setTextColor(secondary)
|
|
979
|
-
badgeLine.setTextColor(accent)
|
|
980
|
-
separatorPaint.color = color(theme, "separator", "#0000001F")
|
|
981
|
-
separatorPaint.strokeWidth = 1f
|
|
982
|
-
showsSeparator = item.json.optBoolean("separator", false) &&
|
|
983
|
-
!item.key.startsWith("token-") &&
|
|
984
|
-
!item.key.startsWith("balance-token-") &&
|
|
985
|
-
item.key != "linear-custom-token"
|
|
986
|
-
invalidate()
|
|
987
|
-
trailingViews.forEach { it.setTextColor(primary) }
|
|
988
|
-
isEnabled = !item.json.optBoolean("disabled", false)
|
|
989
|
-
if (!isEnabled) touchPressed = false
|
|
990
|
-
background = if (touchPressed || reorderActive) pressedRowBackground else restingRowBackground
|
|
991
|
-
// OneKey patch: deprecation dims the row without disabling menu controls.
|
|
992
|
-
// alpha = if (isEnabled) 1f else 0.5f
|
|
993
|
-
alpha = item.json.optDouble("opacity", 1.0).toFloat() * (if (isEnabled) 1f else 0.5f)
|
|
994
|
-
contentDescription = item.json.optString("accessibilityLabel", item.json.optString("title"))
|
|
995
|
-
// OneKey patch: retain stable original selector test identifiers.
|
|
996
|
-
setTag(com.facebook.react.R.id.react_test_id, item.json.optString("testID").takeIf { it.isNotEmpty() })
|
|
997
|
-
|
|
998
|
-
when (item.type) {
|
|
999
|
-
"walletGroup" -> bindWalletGroup(item, theme, layout, listOrientation, checkboxState)
|
|
1000
|
-
"identity" -> bindIdentity(item, theme, selected, checkboxState)
|
|
1001
|
-
"rail" -> bindRail(item, theme)
|
|
1002
|
-
"activity" -> bindActivity(item, theme)
|
|
1003
|
-
"message" -> bindMessage(item, theme)
|
|
1004
|
-
"dataRow" -> bindDataRow(item, theme, checkboxState)
|
|
1005
|
-
"market" -> bindMarket(item, theme)
|
|
1006
|
-
"mediaTile" -> bindMediaTile(item, theme)
|
|
1007
|
-
"metricCard" -> bindMetricCard(item, theme)
|
|
1008
|
-
"sectionHeader" -> bindSectionHeader(item, theme, checkboxState)
|
|
1009
|
-
"action" -> bindAction(item, theme, checkboxState)
|
|
1010
|
-
"system" -> bindSystem(item, theme)
|
|
1011
|
-
}
|
|
1012
|
-
// Keep passive rows out of accessibility and keyboard focus while allowing
|
|
1013
|
-
// their independently bound accessory controls to remain interactive.
|
|
1014
|
-
val wholeRowPressEnabled = item.isWholeRowInteractive
|
|
1015
|
-
isClickable = wholeRowPressEnabled
|
|
1016
|
-
isFocusable = wholeRowPressEnabled
|
|
1017
|
-
applySize(item)
|
|
1018
|
-
if (item.type == "system" && item.json.optString("variant") == "warning") {
|
|
1019
|
-
title.typeface = NativeListFonts.medium(context)
|
|
1020
|
-
title.textSize = sp(14f)
|
|
1021
|
-
subtitle.textSize = sp(14f)
|
|
1022
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
1023
|
-
TextViewCompat.setLineHeight(subtitle, dp(20))
|
|
1024
|
-
minimumHeight = 0
|
|
1025
|
-
}
|
|
1026
|
-
applyListOrientation(item, listOrientation)
|
|
1027
|
-
applySelectorTypography(item)
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// OneKey patch: keep a small idle member pool after reuse or a direct rebind.
|
|
1031
|
-
// The compact proxy/expansion keeps every member until it leaves that state.
|
|
1032
|
-
private fun trimWalletGroupRows(required: Int) {
|
|
1033
|
-
val retained = maxOf(8, required)
|
|
1034
|
-
while (walletGroupRows.size > retained) {
|
|
1035
|
-
val row = walletGroupRows.removeAt(walletGroupRows.lastIndex)
|
|
1036
|
-
(row.parent as? ViewGroup)?.removeView(row)
|
|
1037
|
-
row.dispose()
|
|
1038
|
-
row.onRowPress = null
|
|
1039
|
-
row.onAction = null
|
|
1040
|
-
row.onBindingInvalidated = null
|
|
1041
|
-
}
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
fun recycle() {
|
|
1045
|
-
cancelMarketLongPress()
|
|
1046
|
-
marketLongPressFired = false
|
|
1047
|
-
touchPressed = false
|
|
1048
|
-
restoreRestingBackground()
|
|
1049
|
-
invalidateCurrentBinding()
|
|
1050
|
-
boundKey = null
|
|
1051
|
-
selectorImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
1052
|
-
leadingImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
1053
|
-
secondaryImage.prepareForReuse()
|
|
1054
|
-
mediaNetworkImage.prepareForReuse()
|
|
1055
|
-
metricVisualImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
1056
|
-
walletGroupRows.forEach { row ->
|
|
1057
|
-
row.visibility = VISIBLE
|
|
1058
|
-
row.alpha = 1f
|
|
1059
|
-
row.recycle()
|
|
1060
|
-
}
|
|
1061
|
-
if (!reorderActive && walletGroupExpandAnimator?.isRunning != true) trimWalletGroupRows(0)
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
fun bindSelection(
|
|
1065
|
-
item: NativeListItem,
|
|
1066
|
-
theme: JSONObject?,
|
|
1067
|
-
layout: String,
|
|
1068
|
-
itemIndex: Int?,
|
|
1069
|
-
selected: Boolean,
|
|
1070
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1071
|
-
) {
|
|
1072
|
-
if (boundKey != item.key) return
|
|
1073
|
-
// OneKey patch: keep row callbacks and checkbox fallback state current without resetting images.
|
|
1074
|
-
tag = item
|
|
1075
|
-
if (item.type == "walletGroup") {
|
|
1076
|
-
val members = buildList {
|
|
1077
|
-
add(item.json.getJSONObject("parent"))
|
|
1078
|
-
item.json.getJSONArray("children").let { children ->
|
|
1079
|
-
for (index in 0 until children.length()) add(children.getJSONObject(index))
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
members.forEachIndexed { index, memberJson ->
|
|
1083
|
-
walletGroupRows.getOrNull(index)?.bindSelection(
|
|
1084
|
-
NativeListItem.parse(memberJson),
|
|
1085
|
-
theme,
|
|
1086
|
-
layout,
|
|
1087
|
-
null,
|
|
1088
|
-
memberJson.optBoolean("selected", false),
|
|
1089
|
-
checkboxState,
|
|
1090
|
-
)
|
|
1091
|
-
}
|
|
1092
|
-
// The group owns the subdued backdrop. Selection belongs to a member;
|
|
1093
|
-
// applying the translucent selected color to both levels produces dark
|
|
1094
|
-
// bands in the 12dp gaps and double-composites the parent highlight.
|
|
1095
|
-
background = restingRowBackground
|
|
1096
|
-
return
|
|
1097
|
-
}
|
|
1098
|
-
applySelectionState(item, theme, layout, itemIndex, selected)
|
|
1099
|
-
if (item.type == "identity" && item.json.optString("presentation") == "walletSidebar") {
|
|
1100
|
-
title.setTextColor(
|
|
1101
|
-
color(
|
|
1102
|
-
theme,
|
|
1103
|
-
if (selected) "primaryText" else "secondaryText",
|
|
1104
|
-
if (selected) "#FFFFFFED" else "#FFFFFFAF",
|
|
1105
|
-
),
|
|
1106
|
-
)
|
|
1107
|
-
}
|
|
1108
|
-
// OneKey patch: the comparator permits state changes only on these existing checkbox slots.
|
|
1109
|
-
// boundCheckboxData?.let { bindCheckbox(item, it, checkboxState) }
|
|
1110
|
-
if (boundCheckboxData != null) {
|
|
1111
|
-
val latestCheckbox = if (item.type == "identity") {
|
|
1112
|
-
item.json.optJSONArray("trailing")?.let { trailing ->
|
|
1113
|
-
(0 until trailing.length()).mapNotNull { trailing.optJSONObject(it) }
|
|
1114
|
-
.lastOrNull { it.optString("kind") == "checkbox" }
|
|
1115
|
-
}
|
|
1116
|
-
} else item.json.optJSONObject("checkbox")
|
|
1117
|
-
boundCheckboxData = latestCheckbox ?: boundCheckboxData
|
|
1118
|
-
boundCheckboxData?.let { bindCheckbox(item, it, checkboxState) }
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1122
|
-
fun bindStableSummary(item: NativeListItem) {
|
|
1123
|
-
if (
|
|
1124
|
-
boundKey != item.key ||
|
|
1125
|
-
item.type != "sectionHeader" ||
|
|
1126
|
-
item.json.optString("variant") != "summary"
|
|
1127
|
-
) {
|
|
1128
|
-
return
|
|
1129
|
-
}
|
|
1130
|
-
tag = item
|
|
1131
|
-
contentDescription = item.json.optString("accessibilityLabel", item.json.optString("title"))
|
|
1132
|
-
title.text = item.json.optString("title")
|
|
1133
|
-
trailingViews[0].text = item.json.optString("value")
|
|
1134
|
-
applySelectorTypography(item)
|
|
1135
|
-
}
|
|
1136
|
-
|
|
1137
|
-
fun dispose() {
|
|
1138
|
-
cancelMarketLongPress()
|
|
1139
|
-
invalidateCurrentBinding()
|
|
1140
|
-
restoreRestingBackground()
|
|
1141
|
-
selectorImages.forEach(OneKeyImageReusableView::dispose)
|
|
1142
|
-
selectorImages.clear()
|
|
1143
|
-
leadingImages.forEach(OneKeyImageReusableView::dispose)
|
|
1144
|
-
secondaryImage.dispose()
|
|
1145
|
-
mediaNetworkImage.dispose()
|
|
1146
|
-
metricVisualImages.forEach(OneKeyImageReusableView::dispose)
|
|
1147
|
-
marketBadgeImages.forEach(OneKeyImageReusableView::dispose)
|
|
1148
|
-
walletGroupRows.forEach(NativeListRowView::dispose)
|
|
1149
|
-
}
|
|
1150
|
-
|
|
1151
|
-
private fun actionOrigin(
|
|
1152
|
-
sourceView: View,
|
|
1153
|
-
source: String,
|
|
1154
|
-
slot: Int? = null,
|
|
1155
|
-
) = NativeListActionOrigin(sourceView, this, bindingEpoch, source, slot,
|
|
1156
|
-
if ((tag as? NativeListItem)?.json?.optString("presentation") == "accountSelector" && sourceView in trailingIcons && (sourceView.layoutParams as MarginLayoutParams).marginStart < 0) dp(7) else 0)
|
|
1157
|
-
|
|
1158
|
-
private fun emitAction(
|
|
1159
|
-
item: NativeListItem,
|
|
1160
|
-
actionKey: String,
|
|
1161
|
-
target: NativeSelectionTarget?,
|
|
1162
|
-
sourceView: View,
|
|
1163
|
-
source: String,
|
|
1164
|
-
slot: Int? = null,
|
|
1165
|
-
) {
|
|
1166
|
-
onAction?.invoke(item, actionKey, target, actionOrigin(sourceView, source, slot))
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// OneKey patch: match SizableText TABULAR_NUMS on dynamic labels without replacing their typeface.
|
|
1170
|
-
private fun applySelectorTypography(item: NativeListItem) {
|
|
1171
|
-
val usesSelectorTypography = item.json.optString("presentation") in setOf("accountSelector", "networkSelector", "walletSidebar") || item.type == "system" && item.json.optString("variant") == "warning"
|
|
1172
|
-
fun visit(view: View) {
|
|
1173
|
-
if (view is OneKeyIconView) view.useSourceScale = selectorUsesSourceScale
|
|
1174
|
-
if (view is DottedUnderlineTextView) view.useSourceScale = selectorUsesSourceScale
|
|
1175
|
-
if (view is TextView && usesSelectorTypography) {
|
|
1176
|
-
val selectorLineHeight = selectorLineHeights.getOrPut(view) { view.lineHeight }
|
|
1177
|
-
val original = view.fontFeatureSettings
|
|
1178
|
-
if (!selectorOriginalFontFeatures.containsKey(view)) selectorOriginalFontFeatures[view] = original
|
|
1179
|
-
view.fontFeatureSettings = if (original.isNullOrEmpty()) "tnum" else if (original.contains("tnum")) original else "$original, 'tnum' 1"
|
|
1180
|
-
// OneKey patch: SizableText disables font scaling and rounds font sizes to whole pixels.
|
|
1181
|
-
val sourceTypography = selectorUsesSourceScale || item.type == "system" && item.json.optString("variant") == "warning"
|
|
1182
|
-
if (sourceTypography) {
|
|
1183
|
-
// OneKey patch: React Native CustomStyleSpan disables hinting and preserves fractional advances.
|
|
1184
|
-
selectorOriginalPaintFlags.putIfAbsent(view, view.paintFlags)
|
|
1185
|
-
view.paintFlags = view.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
|
|
1186
|
-
val originalSize = selectorFontSizes.getOrPut(view) { view.textSize }
|
|
1187
|
-
val sourceSize = originalSize * resources.displayMetrics.density / resources.displayMetrics.scaledDensity
|
|
1188
|
-
view.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, kotlin.math.ceil(sourceSize.toDouble()).toFloat())
|
|
1189
|
-
view.letterSpacing = 0f
|
|
1190
|
-
}
|
|
1191
|
-
if (sourceTypography && view.text.isNotEmpty()) {
|
|
1192
|
-
val text = SpannableStringBuilder(view.text)
|
|
1193
|
-
text.getSpans(0, text.length, SelectorLineHeightSpan::class.java).forEach(text::removeSpan)
|
|
1194
|
-
text.setSpan(SelectorLineHeightSpan(selectorLineHeight), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
1195
|
-
view.setLineSpacing(0f, 1f)
|
|
1196
|
-
view.text = text
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
if (view is ViewGroup) for (index in 0 until view.childCount) visit(view.getChildAt(index))
|
|
1200
|
-
}
|
|
1201
|
-
visit(this)
|
|
1202
|
-
}
|
|
1203
|
-
|
|
1204
|
-
private fun invalidateCurrentBinding() {
|
|
1205
|
-
selectorImageRetries.forEach { (image, retry) -> image.removeCallbacks(retry) }
|
|
1206
|
-
selectorImageRetries.clear()
|
|
1207
|
-
if (boundKey == null) return
|
|
1208
|
-
onBindingInvalidated?.invoke(this, bindingEpoch)
|
|
1209
|
-
bindingEpoch += 1
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
private fun resetViews() {
|
|
1213
|
-
clipChildren = true
|
|
1214
|
-
clipToPadding = true
|
|
1215
|
-
selectorOriginalFontFeatures.forEach { (view, original) -> view.fontFeatureSettings = original }
|
|
1216
|
-
selectorOriginalFontFeatures.clear()
|
|
1217
|
-
selectorOriginalPaintFlags.forEach { (view, flags) -> view.paintFlags = flags }
|
|
1218
|
-
selectorOriginalPaintFlags.clear()
|
|
1219
|
-
marketOriginalPaintFlags.forEach { (view, flags) -> view.paintFlags = flags }
|
|
1220
|
-
marketOriginalPaintFlags.clear()
|
|
1221
|
-
selectorLineHeights.clear()
|
|
1222
|
-
selectorFontSizes.clear()
|
|
1223
|
-
selectorImages.forEach { (it.parent as? ViewGroup)?.removeView(it) }
|
|
1224
|
-
// OneKey patch: selector-only views cannot survive a recycled binding.
|
|
1225
|
-
selectorViews.forEach { (it.parent as? ViewGroup)?.removeView(it) }
|
|
1226
|
-
selectorViews.clear()
|
|
1227
|
-
selectorHeight = null
|
|
1228
|
-
marketBadgeViews.forEachIndexed { index, badge ->
|
|
1229
|
-
(badge.parent as? ViewGroup)?.removeView(badge)
|
|
1230
|
-
badge.visibility = GONE
|
|
1231
|
-
badge.background = null
|
|
1232
|
-
badge.setPadding(0, 0, 0, 0)
|
|
1233
|
-
badge.setOnClickListener(null)
|
|
1234
|
-
badge.contentDescription = null
|
|
1235
|
-
marketBadgeLabels[index].apply {
|
|
1236
|
-
// OneKey patch: restore every optional Market badge typography property on reuse.
|
|
1237
|
-
text = ""
|
|
1238
|
-
layoutParams = wrap()
|
|
1239
|
-
fontFeatureSettings = null
|
|
1240
|
-
textSize = sp(11f)
|
|
1241
|
-
typeface = NativeListFonts.medium(context)
|
|
1242
|
-
setLineSpacing(0f, 1f)
|
|
1243
|
-
includeFontPadding = false
|
|
1244
|
-
maxLines = 1
|
|
1245
|
-
ellipsize = TextUtils.TruncateAt.END
|
|
1246
|
-
gravity = Gravity.START or Gravity.CENTER_VERTICAL
|
|
1247
|
-
setTextColor(color(null, "secondaryText", "#0000009B"))
|
|
1248
|
-
}
|
|
1249
|
-
marketBadgeImages[index].prepareForReuse()
|
|
1250
|
-
marketBadgeImages[index].visibility = GONE
|
|
1251
|
-
marketBadgeGlyphs[index].apply {
|
|
1252
|
-
visibility = GONE
|
|
1253
|
-
iconName = ""
|
|
1254
|
-
tintColor = color(null, "secondaryText", "#0000009B")
|
|
1255
|
-
}
|
|
1256
|
-
}
|
|
1257
|
-
title.setOnClickListener(null)
|
|
1258
|
-
title.isClickable = false
|
|
1259
|
-
walletGroupRows.forEach { it.invalidateCurrentBinding() }
|
|
1260
|
-
walletGroupExpandAnimator?.removeAllListeners()
|
|
1261
|
-
walletGroupExpandAnimator?.cancel()
|
|
1262
|
-
walletGroupExpandAnimator = null
|
|
1263
|
-
layoutParams?.takeIf { it.height != ViewGroup.LayoutParams.WRAP_CONTENT }?.let { params ->
|
|
1264
|
-
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
1265
|
-
layoutParams = params
|
|
1266
|
-
}
|
|
1267
|
-
restoreRestingBackground()
|
|
1268
|
-
walletGroupRows.forEach { row ->
|
|
1269
|
-
row.visibility = VISIBLE
|
|
1270
|
-
row.alpha = 1f
|
|
1271
|
-
row.recycle()
|
|
1272
|
-
}
|
|
1273
|
-
activityContentRow.removeAllViews()
|
|
1274
|
-
(actionLine.parent as? ViewGroup)?.removeView(actionLine)
|
|
1275
|
-
removeAllViews()
|
|
1276
|
-
// OneKey patch: the old animator is cancelled and old children are detached.
|
|
1277
|
-
// Do not trim currently needed members when re-binding an expanded group.
|
|
1278
|
-
val nextItem = tag as? NativeListItem
|
|
1279
|
-
val required = if (nextItem?.type == "walletGroup") (nextItem.json.optJSONArray("children")?.length() ?: 0) + 1 else 0
|
|
1280
|
-
trimWalletGroupRows(required)
|
|
1281
|
-
orientation = HORIZONTAL
|
|
1282
|
-
gravity = Gravity.CENTER_VERTICAL
|
|
1283
|
-
minimumHeight = 0
|
|
1284
|
-
setPadding(dp(12), dp(8), dp(12), dp(8))
|
|
1285
|
-
isMediaTile = false
|
|
1286
|
-
walletGroupDragChildCount = 0
|
|
1287
|
-
walletGroupExpandedHeightPx = 0
|
|
1288
|
-
boundCheckboxData = null
|
|
1289
|
-
mainColumn.orientation = VERTICAL
|
|
1290
|
-
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
1291
|
-
// OneKey patch: remove the Market group before restoring shared labels.
|
|
1292
|
-
marketSubtitleLine.removeAllViews()
|
|
1293
|
-
mainColumn.removeView(marketSubtitleLine)
|
|
1294
|
-
mediaMetadataRow.removeView(subtitle)
|
|
1295
|
-
mediaMetadataRow.removeView(mediaNetworkImage)
|
|
1296
|
-
mainColumn.removeView(mediaMetadataRow)
|
|
1297
|
-
mainColumn.removeView(titleLine)
|
|
1298
|
-
mainColumn.removeView(subtitle)
|
|
1299
|
-
mainColumn.removeView(tertiary)
|
|
1300
|
-
mainColumn.addView(titleLine, 0)
|
|
1301
|
-
mainColumn.addView(subtitle, 1)
|
|
1302
|
-
mainColumn.addView(tertiary, 2)
|
|
1303
|
-
mainColumn.addView(actionLine)
|
|
1304
|
-
titleLine.packsChildrenAtStart = false
|
|
1305
|
-
titleLine.removeView(headerTitleIcon)
|
|
1306
|
-
trailingColumn.removeView(headerValueIcon)
|
|
1307
|
-
title.gravity = Gravity.START
|
|
1308
|
-
title.ellipsize = null
|
|
1309
|
-
title.maxWidth = Int.MAX_VALUE
|
|
1310
|
-
title.setHorizontallyScrolling(false)
|
|
1311
|
-
title.text = ""
|
|
1312
|
-
subtitle.text = ""
|
|
1313
|
-
tertiary.text = ""
|
|
1314
|
-
status.text = ""
|
|
1315
|
-
metricSubtitle.text = ""
|
|
1316
|
-
badgeLine.text = ""
|
|
1317
|
-
title.setLineSpacing(0f, 1f)
|
|
1318
|
-
subtitle.setLineSpacing(0f, 1f)
|
|
1319
|
-
tertiary.setLineSpacing(0f, 1f)
|
|
1320
|
-
tertiary.maxWidth = Int.MAX_VALUE
|
|
1321
|
-
tertiary.ellipsize = TextUtils.TruncateAt.END
|
|
1322
|
-
tertiary.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1323
|
-
title.letterSpacing = 0f
|
|
1324
|
-
title.showsDottedUnderline = false
|
|
1325
|
-
title.setPadding(0, 0, 0, 0)
|
|
1326
|
-
subtitle.letterSpacing = 0f
|
|
1327
|
-
status.letterSpacing = 0f
|
|
1328
|
-
badgeLine.letterSpacing = 0f
|
|
1329
|
-
status.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1330
|
-
subtitle.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1331
|
-
title.layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
|
1332
|
-
badgeLine.layoutParams = wrap()
|
|
1333
|
-
titleLine.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1334
|
-
title.visibility = GONE
|
|
1335
|
-
subtitle.visibility = GONE
|
|
1336
|
-
tertiary.visibility = GONE
|
|
1337
|
-
status.visibility = GONE
|
|
1338
|
-
metricSubtitle.visibility = GONE
|
|
1339
|
-
badgeLine.visibility = GONE
|
|
1340
|
-
badgeLine.background = null
|
|
1341
|
-
badgeLine.setPadding(0, 0, 0, 0)
|
|
1342
|
-
actionLine.visibility = GONE
|
|
1343
|
-
actionViews.forEach {
|
|
1344
|
-
it.visibility = GONE
|
|
1345
|
-
it.background = null
|
|
1346
|
-
it.setOnClickListener(null)
|
|
1347
|
-
}
|
|
1348
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1349
|
-
trailingColumn.orientation = VERTICAL
|
|
1350
|
-
trailingColumn.layoutParams = wrap()
|
|
1351
|
-
trailingColumn.clipChildren = true
|
|
1352
|
-
trailingViews.forEach {
|
|
1353
|
-
it.visibility = GONE
|
|
1354
|
-
it.setTag(com.facebook.react.R.id.react_test_id, null)
|
|
1355
|
-
it.gravity = Gravity.END
|
|
1356
|
-
it.maxLines = 1
|
|
1357
|
-
it.layoutParams = wrap()
|
|
1358
|
-
it.background = null
|
|
1359
|
-
it.setPadding(0, 0, 0, 0)
|
|
1360
|
-
it.setOnClickListener(null)
|
|
1361
|
-
}
|
|
1362
|
-
trailingIcons.forEach {
|
|
1363
|
-
it.visibility = GONE
|
|
1364
|
-
it.layoutParams = LayoutParams(dp(24), dp(24)).apply { gravity = Gravity.END }
|
|
1365
|
-
it.glyphSizeDp = null
|
|
1366
|
-
it.alpha = 1f
|
|
1367
|
-
it.isEnabled = true
|
|
1368
|
-
it.background = null
|
|
1369
|
-
it.setPadding(0, 0, 0, 0)
|
|
1370
|
-
it.setOnClickListener(null)
|
|
1371
|
-
}
|
|
1372
|
-
checkbox.visibility = GONE
|
|
1373
|
-
checkbox.layoutParams = LayoutParams(dp(20), dp(20)).apply { gravity = Gravity.END }
|
|
1374
|
-
checkbox.alpha = 1f
|
|
1375
|
-
checkbox.setOnClickListener(null)
|
|
1376
|
-
spinner.visibility = GONE
|
|
1377
|
-
spinner.layoutParams = LayoutParams(dp(20), dp(20)).apply { gravity = Gravity.END }
|
|
1378
|
-
spinner.alpha = 1f
|
|
1379
|
-
marketLeadingUsesSourceClip = false
|
|
1380
|
-
leadingFrame.visibility = GONE
|
|
1381
|
-
leadingFrame.background = null
|
|
1382
|
-
leadingFrame.clipChildren = false
|
|
1383
|
-
leadingFrame.clipToPadding = false
|
|
1384
|
-
secondaryImage.visibility = GONE
|
|
1385
|
-
secondaryImage.foreground = null
|
|
1386
|
-
mediaNetworkImage.visibility = GONE
|
|
1387
|
-
metricVisualImages.forEach {
|
|
1388
|
-
(it.parent as? ViewGroup)?.removeView(it)
|
|
1389
|
-
it.visibility = GONE
|
|
1390
|
-
it.background = null
|
|
1391
|
-
it.clipToOutline = false
|
|
1392
|
-
it.outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
1393
|
-
}
|
|
1394
|
-
unreadDot.visibility = GONE
|
|
1395
|
-
mediaBadge.visibility = GONE
|
|
1396
|
-
mediaBadge.text = ""
|
|
1397
|
-
leadingIcon.visibility = GONE
|
|
1398
|
-
leadingIcon.iconName = ""
|
|
1399
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(18), dp(18), Gravity.CENTER)
|
|
1400
|
-
leadingIcon.glyphSizeDp = null
|
|
1401
|
-
favoriteIcon.visibility = GONE
|
|
1402
|
-
favoriteIcon.iconName = ""
|
|
1403
|
-
headerTitleIcon.visibility = GONE
|
|
1404
|
-
headerTitleIcon.iconName = ""
|
|
1405
|
-
headerValueIcon.visibility = GONE
|
|
1406
|
-
headerValueIcon.iconName = ""
|
|
1407
|
-
leadingActionIcon.visibility = GONE
|
|
1408
|
-
leadingActionIcon.iconName = ""
|
|
1409
|
-
leadingActionIcon.glyphSizeDp = 24
|
|
1410
|
-
leadingActionIcon.setOnClickListener(null)
|
|
1411
|
-
leadingActionIcon.setTag(com.facebook.react.R.id.react_test_id, null)
|
|
1412
|
-
leadingActionIcon.contentDescription = null
|
|
1413
|
-
mainColumn.visibility = VISIBLE
|
|
1414
|
-
dataContainer.visibility = GONE
|
|
1415
|
-
dataColumns.forEach { it.visibility = GONE }
|
|
1416
|
-
tableDataContainer.visibility = GONE
|
|
1417
|
-
tableDataColumns.forEach {
|
|
1418
|
-
it.reset()
|
|
1419
|
-
it.visibility = GONE
|
|
1420
|
-
}
|
|
1421
|
-
leadingOverlayBackground.visibility = GONE
|
|
1422
|
-
leadingCornerIconFrame.visibility = GONE
|
|
1423
|
-
leadingCornerIcon.iconName = ""
|
|
1424
|
-
leadingImages.forEach {
|
|
1425
|
-
it.visibility = GONE
|
|
1426
|
-
it.clipToOutline = false
|
|
1427
|
-
it.outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
1428
|
-
}
|
|
1429
|
-
leadingFallback.text = ""
|
|
1430
|
-
leadingFallback.textSize = sp(13f)
|
|
1431
|
-
leadingFallback.typeface = NativeListFonts.bold(context)
|
|
1432
|
-
leadingFallback.visibility = GONE
|
|
1433
|
-
leadingFallback.background = null
|
|
1434
|
-
showsSeparator = false
|
|
1435
|
-
mainColumn.removeView(skeletonPrimary)
|
|
1436
|
-
mainColumn.removeView(skeletonSecondary)
|
|
1437
|
-
setOnClickListener { view ->
|
|
1438
|
-
(view.tag as? NativeListItem)?.let { item ->
|
|
1439
|
-
if (item.type == "market" && marketLongPressFired) {
|
|
1440
|
-
marketLongPressFired = false
|
|
1441
|
-
return@let
|
|
1442
|
-
}
|
|
1443
|
-
// OneKey patch: allow create-address accessories when whole-row press is gated.
|
|
1444
|
-
if (item.isRowPressEnabled) onRowPress?.invoke(item, actionOrigin(view, "row"))
|
|
1445
|
-
}
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
private fun cancelMarketLongPress() {
|
|
1450
|
-
marketLongPressRunnable?.let(marketLongPressHandler::removeCallbacks)
|
|
1451
|
-
marketLongPressRunnable = null
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
private fun restoreRestingBackground() {
|
|
1455
|
-
background = if (reorderActive) pressedRowBackground else restingRowBackground
|
|
1456
|
-
leadingFrame.alpha = 1f
|
|
1457
|
-
}
|
|
1458
|
-
|
|
1459
|
-
fun setReorderActive(active: Boolean) {
|
|
1460
|
-
if ((tag as? NativeListItem)?.type == "walletGroup" && reorderActive == active) return
|
|
1461
|
-
reorderActive = active
|
|
1462
|
-
if ((tag as? NativeListItem)?.type == "walletGroup") {
|
|
1463
|
-
walletGroupExpandAnimator?.removeAllListeners()
|
|
1464
|
-
walletGroupExpandAnimator?.cancel()
|
|
1465
|
-
walletGroupExpandAnimator = null
|
|
1466
|
-
walletGroupRows.forEachIndexed { index, row ->
|
|
1467
|
-
row.alpha = 1f
|
|
1468
|
-
row.visibility = if (active && index > 0) GONE else VISIBLE
|
|
1469
|
-
if (index == 0) row.setReorderActive(active)
|
|
1470
|
-
}
|
|
1471
|
-
minimumHeight = if (active) dp(68) else walletGroupExpandedHeightPx
|
|
1472
|
-
layoutParams = layoutParams?.apply {
|
|
1473
|
-
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
1474
|
-
}
|
|
1475
|
-
background = restingRowBackground
|
|
1476
|
-
requestLayout()
|
|
1477
|
-
invalidate()
|
|
1478
|
-
return
|
|
1479
|
-
}
|
|
1480
|
-
restoreRestingBackground()
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
fun canStartWalletGroupReorder(localY: Float): Boolean {
|
|
1484
|
-
if ((tag as? NativeListItem)?.type != "walletGroup") return true
|
|
1485
|
-
walletGroupRows.forEach { row ->
|
|
1486
|
-
if (localY >= row.top && localY < row.bottom) {
|
|
1487
|
-
return (row.tag as? NativeListItem)?.json?.optBoolean("draggable", true) != false
|
|
1488
|
-
}
|
|
1489
|
-
}
|
|
1490
|
-
return true
|
|
1491
|
-
}
|
|
1492
|
-
|
|
1493
|
-
fun finishWalletGroupReorder(
|
|
1494
|
-
durationMs: Long,
|
|
1495
|
-
interpolator: TimeInterpolator,
|
|
1496
|
-
completion: (() -> Unit)? = null,
|
|
1497
|
-
) {
|
|
1498
|
-
if ((tag as? NativeListItem)?.type != "walletGroup") {
|
|
1499
|
-
setReorderActive(false)
|
|
1500
|
-
completion?.invoke()
|
|
1501
|
-
return
|
|
1502
|
-
}
|
|
1503
|
-
reorderActive = false
|
|
1504
|
-
walletGroupRows.firstOrNull()?.setReorderActive(false)
|
|
1505
|
-
val startHeight = height.coerceAtLeast(dp(68))
|
|
1506
|
-
val targetHeight = walletGroupExpandedHeightPx
|
|
1507
|
-
minimumHeight = startHeight
|
|
1508
|
-
layoutParams = layoutParams?.apply { height = startHeight }
|
|
1509
|
-
walletGroupRows.drop(1).forEach { row ->
|
|
1510
|
-
row.visibility = VISIBLE
|
|
1511
|
-
row.alpha = 0f
|
|
1512
|
-
}
|
|
1513
|
-
background = restingRowBackground
|
|
1514
|
-
if (targetHeight <= startHeight) {
|
|
1515
|
-
walletGroupRows.forEach { row ->
|
|
1516
|
-
row.visibility = VISIBLE
|
|
1517
|
-
row.alpha = 1f
|
|
1518
|
-
}
|
|
1519
|
-
minimumHeight = targetHeight
|
|
1520
|
-
layoutParams = layoutParams?.apply { height = ViewGroup.LayoutParams.WRAP_CONTENT }
|
|
1521
|
-
requestLayout()
|
|
1522
|
-
invalidate()
|
|
1523
|
-
completion?.invoke()
|
|
1524
|
-
return
|
|
1525
|
-
}
|
|
1526
|
-
walletGroupExpandAnimator = ValueAnimator.ofInt(startHeight, targetHeight).apply {
|
|
1527
|
-
duration = durationMs
|
|
1528
|
-
this.interpolator = interpolator
|
|
1529
|
-
addUpdateListener { animator ->
|
|
1530
|
-
val progress = animator.animatedFraction
|
|
1531
|
-
layoutParams = layoutParams?.apply { height = animator.animatedValue as Int }
|
|
1532
|
-
walletGroupRows.drop(1).forEach { it.alpha = progress }
|
|
1533
|
-
requestLayout()
|
|
1534
|
-
invalidate()
|
|
1535
|
-
}
|
|
1536
|
-
addListener(object : AnimatorListenerAdapter() {
|
|
1537
|
-
override fun onAnimationEnd(animation: Animator) {
|
|
1538
|
-
walletGroupExpandAnimator = null
|
|
1539
|
-
walletGroupRows.forEach { row ->
|
|
1540
|
-
row.visibility = VISIBLE
|
|
1541
|
-
row.alpha = 1f
|
|
1542
|
-
}
|
|
1543
|
-
minimumHeight = targetHeight
|
|
1544
|
-
layoutParams = layoutParams?.apply { height = ViewGroup.LayoutParams.WRAP_CONTENT }
|
|
1545
|
-
requestLayout()
|
|
1546
|
-
invalidate()
|
|
1547
|
-
postOnAnimation {
|
|
1548
|
-
if (!reorderActive) {
|
|
1549
|
-
walletGroupRows.forEach { row ->
|
|
1550
|
-
row.visibility = VISIBLE
|
|
1551
|
-
row.alpha = 1f
|
|
1552
|
-
}
|
|
1553
|
-
requestLayout()
|
|
1554
|
-
invalidate()
|
|
1555
|
-
}
|
|
1556
|
-
}
|
|
1557
|
-
completion?.invoke()
|
|
1558
|
-
}
|
|
1559
|
-
})
|
|
1560
|
-
start()
|
|
1561
|
-
}
|
|
1562
|
-
}
|
|
1563
|
-
|
|
1564
|
-
private fun bindWalletGroup(
|
|
1565
|
-
item: NativeListItem,
|
|
1566
|
-
theme: JSONObject?,
|
|
1567
|
-
layout: String,
|
|
1568
|
-
listOrientation: String,
|
|
1569
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1570
|
-
) {
|
|
1571
|
-
orientation = VERTICAL
|
|
1572
|
-
gravity = Gravity.CENTER_HORIZONTAL
|
|
1573
|
-
setPadding(0, 0, 0, 0)
|
|
1574
|
-
isClickable = false
|
|
1575
|
-
setOnClickListener(null)
|
|
1576
|
-
val members = mutableListOf<JSONObject>()
|
|
1577
|
-
members.add(item.json.getJSONObject("parent"))
|
|
1578
|
-
item.json.getJSONArray("children").let { children ->
|
|
1579
|
-
for (index in 0 until children.length()) {
|
|
1580
|
-
members.add(children.getJSONObject(index))
|
|
1581
|
-
}
|
|
1582
|
-
}
|
|
1583
|
-
if (members.first().has("height")) setPadding(dp(1), dp(1), dp(1), dp(1))
|
|
1584
|
-
val childCount = members.size - 1
|
|
1585
|
-
walletGroupDragChildCount = members.drop(1).count {
|
|
1586
|
-
it.optBoolean("draggable", true)
|
|
1587
|
-
}
|
|
1588
|
-
walletGroupExpandedHeightPx =
|
|
1589
|
-
// OneKey patch: expanded groups include individual wallet badge heights.
|
|
1590
|
-
// dp(members.size * 68 + walletGroupDragChildCount * 12)
|
|
1591
|
-
dp(members.sumOf { it.optInt("height", if ((it.optJSONArray("badges")?.length() ?: 0) > 0) 92 else 68) } + childCount * 12 + if (members.first().has("height")) 2 else 0)
|
|
1592
|
-
walletGroupDragBadgeBackgroundPaint.color = color(
|
|
1593
|
-
theme,
|
|
1594
|
-
"inverseBackground",
|
|
1595
|
-
"#000000DF",
|
|
1596
|
-
)
|
|
1597
|
-
walletGroupDragBadgeBorderPaint.color = color(
|
|
1598
|
-
theme,
|
|
1599
|
-
"rowBackground",
|
|
1600
|
-
"#FFFFFF",
|
|
1601
|
-
)
|
|
1602
|
-
walletGroupDragBadgeBorderPaint.strokeWidth = dp(1).toFloat()
|
|
1603
|
-
walletGroupDragBadgeTextPaint.color = color(
|
|
1604
|
-
theme,
|
|
1605
|
-
"inverseText",
|
|
1606
|
-
"#FCFCFC",
|
|
1607
|
-
)
|
|
1608
|
-
walletGroupDragBadgeTextPaint.textSize = TypedValue.applyDimension(
|
|
1609
|
-
TypedValue.COMPLEX_UNIT_SP,
|
|
1610
|
-
sp(12f),
|
|
1611
|
-
resources.displayMetrics,
|
|
1612
|
-
)
|
|
1613
|
-
while (walletGroupRows.size < members.size) {
|
|
1614
|
-
walletGroupRows.add(NativeListRowView(reactContext))
|
|
1615
|
-
}
|
|
1616
|
-
members.forEachIndexed { index, memberJson ->
|
|
1617
|
-
val member = NativeListItem.parse(memberJson)
|
|
1618
|
-
val memberRow = walletGroupRows[index]
|
|
1619
|
-
memberRow.onRowPress = { item, origin -> onRowPress?.invoke(item, origin) }
|
|
1620
|
-
memberRow.onAction = { source, actionKey, target, origin ->
|
|
1621
|
-
onAction?.invoke(source, actionKey, target, origin)
|
|
1622
|
-
}
|
|
1623
|
-
memberRow.onBindingInvalidated = { row, epoch ->
|
|
1624
|
-
onBindingInvalidated?.invoke(row, epoch)
|
|
1625
|
-
}
|
|
1626
|
-
memberRow.bind(
|
|
1627
|
-
member,
|
|
1628
|
-
theme,
|
|
1629
|
-
layout,
|
|
1630
|
-
listOrientation,
|
|
1631
|
-
null,
|
|
1632
|
-
memberJson.optBoolean("selected", false),
|
|
1633
|
-
checkboxState,
|
|
1634
|
-
useSourceScale = selectorUsesSourceScale,
|
|
1635
|
-
)
|
|
1636
|
-
// OneKey patch: member geometry matches the outer group height calculation.
|
|
1637
|
-
// memberRow.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, dp(68)).apply {
|
|
1638
|
-
memberRow.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, dp(memberJson.optInt("height", if ((memberJson.optJSONArray("badges")?.length() ?: 0) > 0) 92 else 68))).apply {
|
|
1639
|
-
if (index > 0) topMargin = dp(12)
|
|
1640
|
-
}
|
|
1641
|
-
addView(memberRow)
|
|
1642
|
-
}
|
|
1643
|
-
val fill = color(theme, "subduedBackground", "#F9F9F9")
|
|
1644
|
-
val stroke = color(theme, "separator", "#0000001F")
|
|
1645
|
-
restingRowBackground = GradientDrawable().apply {
|
|
1646
|
-
setColor(fill)
|
|
1647
|
-
setStroke(dp(1), stroke)
|
|
1648
|
-
cornerRadius = dp(12).toFloat()
|
|
1649
|
-
}
|
|
1650
|
-
pressedRowBackground = restingRowBackground
|
|
1651
|
-
background = restingRowBackground
|
|
1652
|
-
}
|
|
1653
|
-
|
|
1654
|
-
private fun bindIdentity(
|
|
1655
|
-
item: NativeListItem,
|
|
1656
|
-
theme: JSONObject?,
|
|
1657
|
-
selected: Boolean,
|
|
1658
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1659
|
-
) {
|
|
1660
|
-
if (item.json.optString("presentation") == "walletSidebar") {
|
|
1661
|
-
orientation = VERTICAL
|
|
1662
|
-
gravity = Gravity.CENTER
|
|
1663
|
-
setPadding(dp(4), dp(4), dp(4), dp(4))
|
|
1664
|
-
addLeading(item.json.optJSONObject("leading"), 40, spacingDp = 0)
|
|
1665
|
-
leadingFallback.textSize = sp(28f)
|
|
1666
|
-
leadingFallback.typeface = NativeListFonts.regular(context)
|
|
1667
|
-
mainColumn.gravity = Gravity.CENTER
|
|
1668
|
-
titleLine.gravity = Gravity.CENTER
|
|
1669
|
-
titleLine.packsChildrenAtStart = false
|
|
1670
|
-
titleLine.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1671
|
-
// OneKey patch: the original wallet title ellipsizes within the full inner row width.
|
|
1672
|
-
title.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1673
|
-
title.gravity = Gravity.CENTER
|
|
1674
|
-
// OneKey patch: this branch returns before the common identity ellipsis setup.
|
|
1675
|
-
if (item.json.has("height")) title.ellipsize = TextUtils.TruncateAt.END
|
|
1676
|
-
showText(title, item.json.optString("title"), 1)
|
|
1677
|
-
title.setTextColor(
|
|
1678
|
-
color(
|
|
1679
|
-
theme,
|
|
1680
|
-
if (selected) "primaryText" else "secondaryText",
|
|
1681
|
-
if (selected) "#FFFFFFED" else "#FFFFFFAF",
|
|
1682
|
-
),
|
|
1683
|
-
)
|
|
1684
|
-
addView(
|
|
1685
|
-
mainColumn,
|
|
1686
|
-
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1687
|
-
// OneKey patch: snap the source 4 + 40 + 4 sequence once instead of rounding each gap.
|
|
1688
|
-
topMargin = if (item.json.has("height")) dp(48) - dp(44) else dp(4)
|
|
1689
|
-
},
|
|
1690
|
-
)
|
|
1691
|
-
// OneKey patch: wallet tags are a centered line below the wallet name.
|
|
1692
|
-
item.json.optJSONArray("badges")?.takeIf { it.length() > 0 }?.let { badges ->
|
|
1693
|
-
val isSelector = item.json.has("height")
|
|
1694
|
-
val badgeLineHeight = if (isSelector) 14 else 16
|
|
1695
|
-
val badgeHeight = badgeLineHeight + 4
|
|
1696
|
-
val line = LinearLayout(context).apply { orientation = HORIZONTAL; gravity = Gravity.CENTER }
|
|
1697
|
-
for (index in 0 until badges.length()) {
|
|
1698
|
-
val badge = TextView(context).apply {
|
|
1699
|
-
text = badges.getJSONObject(index).optString("text")
|
|
1700
|
-
textSize = sp(if (isSelector) 11f else 12f)
|
|
1701
|
-
typeface = NativeListFonts.regular(context)
|
|
1702
|
-
includeFontPadding = false
|
|
1703
|
-
maxLines = 1
|
|
1704
|
-
ellipsize = TextUtils.TruncateAt.END
|
|
1705
|
-
val warning = isSelector && badges.getJSONObject(index).optString("tone") == "warning"
|
|
1706
|
-
setTextColor(color(theme, if (warning) "caution" else "secondaryText", if (warning) "#AB6400" else "#0000009B"))
|
|
1707
|
-
background = roundedFill(color(theme, if (warning) "cautionBackground" else if (isSelector) "subduedBackground" else "strongBackground", if (warning) "#FFF4D5" else "#00000006"), 4f)
|
|
1708
|
-
setPadding(dp(if (isSelector) 6 else 4), dp(2), dp(if (isSelector) 6 else 4), dp(2))
|
|
1709
|
-
}
|
|
1710
|
-
TextViewCompat.setLineHeight(badge, dp(badgeLineHeight))
|
|
1711
|
-
line.addView(badge, LayoutParams(LayoutParams.WRAP_CONTENT, dp(badgeHeight)).apply { if (index > 0) marginStart = dp(4) })
|
|
1712
|
-
}
|
|
1713
|
-
mainColumn.addView(line, LayoutParams(LayoutParams.WRAP_CONTENT, dp(badgeHeight)).apply { topMargin = dp(4) })
|
|
1714
|
-
selectorViews.add(line)
|
|
1715
|
-
}
|
|
1716
|
-
return
|
|
1717
|
-
}
|
|
1718
|
-
if (item.json.optString("presentation") == "networkSelector") {
|
|
1719
|
-
// OneKey patch: the explicit 48-point row centers its 32-point network icon.
|
|
1720
|
-
// setPadding(dp(12), dp(7), dp(12), dp(8))
|
|
1721
|
-
setPadding(dp(12), dp(if (item.json.has("height")) 8 else 7), dp(12), dp(8))
|
|
1722
|
-
}
|
|
1723
|
-
val leading = item.json.optJSONObject("leading")
|
|
1724
|
-
item.json.optJSONObject("leadingAction")?.let { action ->
|
|
1725
|
-
leadingActionIcon.visibility = VISIBLE
|
|
1726
|
-
leadingActionIcon.iconName = action.optString("name")
|
|
1727
|
-
leadingActionIcon.glyphSizeDp = 24
|
|
1728
|
-
leadingActionIcon.tintColor = safeColor(
|
|
1729
|
-
action.optString("tintColor"),
|
|
1730
|
-
color(theme, "icon", "#0000009B"),
|
|
1731
|
-
)
|
|
1732
|
-
leadingActionIcon.isEnabled = !action.optBoolean("disabled", false)
|
|
1733
|
-
leadingActionIcon.alpha = if (leadingActionIcon.isEnabled) 1f else 0.4f
|
|
1734
|
-
leadingActionIcon.setOnClickListener {
|
|
1735
|
-
emitAction(
|
|
1736
|
-
item,
|
|
1737
|
-
action.optString("actionKey"),
|
|
1738
|
-
null,
|
|
1739
|
-
leadingActionIcon,
|
|
1740
|
-
"leadingAction",
|
|
1741
|
-
)
|
|
1742
|
-
}
|
|
1743
|
-
// ListItem.IconButton is 36dp with 6dp inner padding and m=-7. Keep the
|
|
1744
|
-
// full button frame but absorb its leading negative margin into the row
|
|
1745
|
-
// padding and its trailing margin into the following gap.
|
|
1746
|
-
setPadding(dp(5), paddingTop, paddingRight, paddingBottom)
|
|
1747
|
-
addView(leadingActionIcon, LayoutParams(dp(36), dp(36)).apply { marginEnd = dp(5) })
|
|
1748
|
-
}
|
|
1749
|
-
addLeading(
|
|
1750
|
-
leading,
|
|
1751
|
-
if (
|
|
1752
|
-
leading?.optString("kind") == "network" ||
|
|
1753
|
-
item.json.optString("presentation") == "accountSelector"
|
|
1754
|
-
) 32 else 40,
|
|
1755
|
-
)
|
|
1756
|
-
// OneKey patch: custom network initials retain LetterAvatar typography.
|
|
1757
|
-
if (item.json.optString("presentation") == "networkSelector" && leading?.optJSONObject("image") == null && leading?.optJSONObject("fallbackIcon") == null && !leading?.optString("fallbackText").isNullOrEmpty()) {
|
|
1758
|
-
leadingFallback.textSize = sp(19f)
|
|
1759
|
-
leadingFallback.typeface = NativeListFonts.semibold(context)
|
|
1760
|
-
leadingFallback.setTextColor(color(theme, "inverseText", "#FCFCFC"))
|
|
1761
|
-
TextViewCompat.setLineHeight(leadingFallback, dp(27))
|
|
1762
|
-
}
|
|
1763
|
-
addView(mainColumn, weighted())
|
|
1764
|
-
titleLine.packsChildrenAtStart = true
|
|
1765
|
-
title.ellipsize = TextUtils.TruncateAt.END
|
|
1766
|
-
showText(title, item.json.optString("title"), item.json.optInt("titleLines", 1))
|
|
1767
|
-
if (item.json.optString("presentation") == "accountSelector") {
|
|
1768
|
-
title.typeface = NativeListFonts.regular(context)
|
|
1769
|
-
}
|
|
1770
|
-
showText(subtitle, item.json.optString("subtitle"), item.json.optInt("subtitleLines", 2))
|
|
1771
|
-
// OneKey patch: use separate labels for independently truncated balance/address.
|
|
1772
|
-
item.json.optJSONArray("subtitleSegments")?.takeIf { it.length() > 0 }?.let { segments ->
|
|
1773
|
-
subtitle.visibility = GONE
|
|
1774
|
-
val line = SelectorSubtitleLayout(context).apply { orientation = HORIZONTAL; gravity = Gravity.CENTER_VERTICAL }
|
|
1775
|
-
for (index in 0 until segments.length()) {
|
|
1776
|
-
val segment = segments.getJSONObject(index)
|
|
1777
|
-
if (segment.optBoolean("separatorBefore", false)) {
|
|
1778
|
-
val dot = View(context).apply { background = roundedFill(color(theme, "disabledText", "#00000072"), 2f) }
|
|
1779
|
-
line.addView(dot, LayoutParams(dp(4), dp(4)).apply { marginStart = dp(6); marginEnd = dp(6) })
|
|
1780
|
-
}
|
|
1781
|
-
val label = TextView(context).apply {
|
|
1782
|
-
text = segment.optString("text")
|
|
1783
|
-
typeface = NativeListFonts.regular(context)
|
|
1784
|
-
textSize = sp(14f)
|
|
1785
|
-
includeFontPadding = false
|
|
1786
|
-
maxLines = 1
|
|
1787
|
-
ellipsize = TextUtils.TruncateAt.END
|
|
1788
|
-
val toneKey = when (segment.optString("tone")) { "primary" -> "primaryText"; "disabled" -> "disabledText"; "caution" -> "caution"; "positive" -> "positive"; "negative" -> "negative"; else -> "secondaryText" }
|
|
1789
|
-
setTextColor(color(theme, toneKey, if (toneKey == "caution") "#AB6400" else "#0000009B"))
|
|
1790
|
-
}
|
|
1791
|
-
TextViewCompat.setLineHeight(label, dp(20))
|
|
1792
|
-
applyValueSegments(label, segment.optJSONArray("textSegments"), 14, 20, false)
|
|
1793
|
-
line.addView(label, LayoutParams(LayoutParams.WRAP_CONTENT, dp(20)))
|
|
1794
|
-
}
|
|
1795
|
-
mainColumn.addView(line, 2, LayoutParams(LayoutParams.MATCH_PARENT, dp(20)))
|
|
1796
|
-
selectorViews.add(line)
|
|
1797
|
-
}
|
|
1798
|
-
item.json.optJSONArray("titleMatch")?.takeIf { it.length() > 0 }?.let { matches ->
|
|
1799
|
-
val highlighted = SpannableStringBuilder(title.text)
|
|
1800
|
-
for (index in 0 until matches.length()) {
|
|
1801
|
-
val match = matches.getJSONObject(index)
|
|
1802
|
-
val start = match.optInt("start")
|
|
1803
|
-
val end = match.optInt("end")
|
|
1804
|
-
if (start >= 0 && end > start && end <= highlighted.length) highlighted.setSpan(ForegroundColorSpan(color(theme, "info", "#0D74CE")), start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
1805
|
-
}
|
|
1806
|
-
title.text = highlighted
|
|
1807
|
-
}
|
|
1808
|
-
showText(tertiary, item.json.optString("tertiary"), 1)
|
|
1809
|
-
tertiary.setTextColor(
|
|
1810
|
-
color(
|
|
1811
|
-
theme,
|
|
1812
|
-
if (item.json.optString("tertiaryTone") == "info") "info" else "secondaryText",
|
|
1813
|
-
if (item.json.optString("tertiaryTone") == "info") "#006DCBF2" else "#0000009B",
|
|
1814
|
-
),
|
|
1815
|
-
)
|
|
1816
|
-
val badges = item.json.optJSONArray("badges")
|
|
1817
|
-
if (badges != null && badges.length() > 0) {
|
|
1818
|
-
val texts = (0 until minOf(2, badges.length())).map { badges.getJSONObject(it).optString("text") }
|
|
1819
|
-
showText(badgeLine, texts.joinToString(" "), 1)
|
|
1820
|
-
badgeLine.layoutParams = wrap().apply { marginStart = dp(8) }
|
|
1821
|
-
badgeLine.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
1822
|
-
badgeLine.background = roundedFill(color(theme, "strongBackground", "#0000000F"), 4f)
|
|
1823
|
-
badgeLine.setPadding(dp(8), dp(2), dp(8), dp(2))
|
|
1824
|
-
TextViewCompat.setLineHeight(badgeLine, dp(16))
|
|
1825
|
-
}
|
|
1826
|
-
addView(trailingColumn, wrap())
|
|
1827
|
-
val accessories = item.json.optJSONArray("trailing")
|
|
1828
|
-
if (item.json.has("height") && item.json.optString("presentation") == "networkSelector" && accessories.hasAccessory("checkbox")) {
|
|
1829
|
-
// OneKey patch: retain ListItem's title-to-accessory gap when measuring truncation.
|
|
1830
|
-
(mainColumn.layoutParams as LayoutParams).marginEnd = dp(12)
|
|
1831
|
-
}
|
|
1832
|
-
if (accessories.hasAccessory("checkbox") && accessories.hasAccessory("value")) {
|
|
1833
|
-
trailingColumn.orientation = HORIZONTAL
|
|
1834
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1835
|
-
checkbox.layoutParams = LayoutParams(dp(20), dp(20)).apply { marginStart = dp(12) }
|
|
1836
|
-
} else if (accessories.isValuePairMenu()) {
|
|
1837
|
-
trailingColumn.orientation = HORIZONTAL
|
|
1838
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1839
|
-
} else if (accessories.isBookmarkEditActions()) {
|
|
1840
|
-
trailingColumn.orientation = HORIZONTAL
|
|
1841
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1842
|
-
}
|
|
1843
|
-
bindAccessories(item, accessories, theme, checkboxState)
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
private fun bindRail(item: NativeListItem, theme: JSONObject?) {
|
|
1847
|
-
setPadding(dp(4), dp(4), dp(4), dp(4))
|
|
1848
|
-
addLeading(item.json.optJSONObject("visual"), 20, spacingDp = 6)
|
|
1849
|
-
showText(title, item.json.optString("title"), 1)
|
|
1850
|
-
TextViewCompat.setLineHeight(title, dp(16))
|
|
1851
|
-
title.fontFeatureSettings = "tnum"
|
|
1852
|
-
title.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
|
1853
|
-
titleLine.layoutParams = wrap()
|
|
1854
|
-
mainColumn.orientation = HORIZONTAL
|
|
1855
|
-
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
1856
|
-
mainColumn.layoutParams = wrap()
|
|
1857
|
-
title.setHorizontallyScrolling(true)
|
|
1858
|
-
item.json.optJSONObject("badge")?.let { badge ->
|
|
1859
|
-
showText(badgeLine, badge.optString("text"), 1)
|
|
1860
|
-
badgeLine.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1861
|
-
marginStart = dp(6)
|
|
1862
|
-
}
|
|
1863
|
-
badgeLine.setTextColor(
|
|
1864
|
-
when (badge.optString("tone")) {
|
|
1865
|
-
"success" -> color(theme, "positive", "#00713FDE")
|
|
1866
|
-
"danger" -> color(theme, "negative", "#C40006D3")
|
|
1867
|
-
else -> color(theme, "secondaryText", "#0000009B")
|
|
1868
|
-
},
|
|
1869
|
-
)
|
|
1870
|
-
badgeLine.typeface = NativeListFonts.medium(context)
|
|
1871
|
-
badgeLine.fontFeatureSettings = "tnum"
|
|
1872
|
-
TextViewCompat.setLineHeight(badgeLine, dp(16))
|
|
1873
|
-
}
|
|
1874
|
-
item.json.optString("status").takeUnless { it.isEmpty() || it == "none" }
|
|
1875
|
-
?.let { showText(status, it, 1) }
|
|
1876
|
-
addView(mainColumn, wrap())
|
|
1877
|
-
}
|
|
1878
|
-
|
|
1879
|
-
private fun bindActivity(item: NativeListItem, theme: JSONObject?) {
|
|
1880
|
-
addLeading(
|
|
1881
|
-
item.json.optJSONObject("leading"),
|
|
1882
|
-
secondaryVisual = item.json.optJSONObject("secondaryLeading"),
|
|
1883
|
-
)
|
|
1884
|
-
addView(mainColumn, weighted())
|
|
1885
|
-
showText(title, item.json.optString("title"), 1)
|
|
1886
|
-
showText(subtitle, item.json.optString("description"), 2)
|
|
1887
|
-
TextViewCompat.setLineHeight(title, dp(24))
|
|
1888
|
-
TextViewCompat.setLineHeight(subtitle, dp(20))
|
|
1889
|
-
if (item.json.optString("status") == "Failed") {
|
|
1890
|
-
showText(badgeLine, "Failed", 1)
|
|
1891
|
-
badgeLine.setTextColor(color(theme, "negative", "#C40006D3"))
|
|
1892
|
-
badgeLine.background = roundedFill(
|
|
1893
|
-
color(theme, "criticalBackground", "#F3000D14"),
|
|
1894
|
-
4f,
|
|
1895
|
-
)
|
|
1896
|
-
badgeLine.setPadding(dp(8), dp(2), dp(8), dp(2))
|
|
1897
|
-
} else {
|
|
1898
|
-
showText(status, item.json.optString("status"), 1)
|
|
1899
|
-
}
|
|
1900
|
-
addView(trailingColumn, wrap())
|
|
1901
|
-
showTrailing(0, item.json.optString("primaryAmount"), true)
|
|
1902
|
-
showTrailing(1, item.json.optString("secondaryAmount"), false)
|
|
1903
|
-
if (item.json.optString("primaryAmount").startsWith("+")) {
|
|
1904
|
-
trailingViews[0].setTextColor(color(theme, "positive", "#00713FDE"))
|
|
1905
|
-
}
|
|
1906
|
-
val actions = item.json.optJSONArray("footerActions")
|
|
1907
|
-
if (actions != null && actions.length() > 0) {
|
|
1908
|
-
actionLine.visibility = VISIBLE
|
|
1909
|
-
for (index in 0 until minOf(3, actions.length())) {
|
|
1910
|
-
val action = actions.getJSONObject(index)
|
|
1911
|
-
val actionView = actionViews[index]
|
|
1912
|
-
actionView.text = action.optString("label")
|
|
1913
|
-
actionView.visibility = VISIBLE
|
|
1914
|
-
actionView.isEnabled = isEnabled && !action.optBoolean("disabled", false)
|
|
1915
|
-
actionView.background = roundedFill(color(theme, "strongBackground", "#0000000F"), 8f)
|
|
1916
|
-
actionView.setTextColor(
|
|
1917
|
-
if (action.optString("tone") == "danger") {
|
|
1918
|
-
color(theme, "negative", "#C40006D3")
|
|
1919
|
-
} else {
|
|
1920
|
-
color(theme, "primaryText", "#000000DF")
|
|
1921
|
-
},
|
|
1922
|
-
)
|
|
1923
|
-
actionView.layoutParams = LayoutParams(
|
|
1924
|
-
LayoutParams.WRAP_CONTENT,
|
|
1925
|
-
LayoutParams.WRAP_CONTENT,
|
|
1926
|
-
).apply { marginEnd = dp(8) }
|
|
1927
|
-
actionView.setOnClickListener {
|
|
1928
|
-
emitAction(item, action.optString("key"), null, actionView, "footerAction", index)
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
// TxActionCommonListView is one column ListItem with an 8dp gap between
|
|
1932
|
-
// its content XStack and the pending action footer.
|
|
1933
|
-
removeView(leadingFrame)
|
|
1934
|
-
removeView(mainColumn)
|
|
1935
|
-
removeView(trailingColumn)
|
|
1936
|
-
mainColumn.removeView(actionLine)
|
|
1937
|
-
activityContentRow.orientation = HORIZONTAL
|
|
1938
|
-
activityContentRow.gravity = Gravity.CENTER_VERTICAL
|
|
1939
|
-
activityContentRow.addView(leadingFrame)
|
|
1940
|
-
activityContentRow.addView(mainColumn)
|
|
1941
|
-
activityContentRow.addView(trailingColumn)
|
|
1942
|
-
orientation = VERTICAL
|
|
1943
|
-
gravity = Gravity.START
|
|
1944
|
-
addView(activityContentRow, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
1945
|
-
addView(actionLine, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1946
|
-
marginStart = if (leadingFrame.visibility == VISIBLE) dp(52) else 0
|
|
1947
|
-
topMargin = dp(8)
|
|
1948
|
-
bottomMargin = dp(4)
|
|
1949
|
-
})
|
|
1950
|
-
}
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
private fun bindMessage(item: NativeListItem, theme: JSONObject?) {
|
|
1954
|
-
gravity = Gravity.TOP
|
|
1955
|
-
setPadding(dp(12), dp(16), dp(12), dp(16))
|
|
1956
|
-
item.json.optJSONObject("leading")?.let { addLeading(it, 28) }
|
|
1957
|
-
unreadDot.visibility = if (item.json.optBoolean("unread", false)) VISIBLE else GONE
|
|
1958
|
-
addView(mainColumn, weighted())
|
|
1959
|
-
showText(title, item.json.optString("title"), 2)
|
|
1960
|
-
showText(subtitle, item.json.optString("body"), item.json.optInt("bodyLines", 3).coerceIn(1, 3))
|
|
1961
|
-
showText(status, item.json.optString("time"), 1)
|
|
1962
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
1963
|
-
TextViewCompat.setLineHeight(subtitle, dp(20))
|
|
1964
|
-
TextViewCompat.setLineHeight(status, dp(16))
|
|
1965
|
-
subtitle.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1966
|
-
topMargin = dp(2)
|
|
1967
|
-
}
|
|
1968
|
-
status.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1969
|
-
topMargin = dp(4)
|
|
1970
|
-
}
|
|
1971
|
-
status.setTextColor(color(theme, "disabledText", "#00000072"))
|
|
1972
|
-
item.json.optJSONObject("thumbnail")?.let { source ->
|
|
1973
|
-
secondaryImage.visibility = VISIBLE
|
|
1974
|
-
secondaryImage.foreground = roundedHairlineStroke(
|
|
1975
|
-
color(theme, "strongBackground", "#0000000F"),
|
|
1976
|
-
6f,
|
|
1977
|
-
)
|
|
1978
|
-
addView(secondaryImage, LayoutParams(dp(64), dp(64)).apply { marginStart = dp(12) })
|
|
1979
|
-
bindImage(source, secondaryImage, item.key, 0, "generic")
|
|
1980
|
-
}
|
|
1981
|
-
}
|
|
1982
|
-
|
|
1983
|
-
private fun bindDataRow(
|
|
1984
|
-
item: NativeListItem,
|
|
1985
|
-
theme: JSONObject?,
|
|
1986
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1987
|
-
) {
|
|
1988
|
-
if (
|
|
1989
|
-
item.json.optBoolean("favorite", false) ||
|
|
1990
|
-
item.json.optBoolean("favoriteActive", false)
|
|
1991
|
-
) {
|
|
1992
|
-
val favoriteActive = item.json.optBoolean("favoriteActive", false)
|
|
1993
|
-
favoriteIcon.visibility = VISIBLE
|
|
1994
|
-
favoriteIcon.iconName = if (favoriteActive) "StarSolid" else "StarOutline"
|
|
1995
|
-
favoriteIcon.tintColor = color(
|
|
1996
|
-
theme,
|
|
1997
|
-
if (favoriteActive) "icon" else "iconSubdued",
|
|
1998
|
-
if (favoriteActive) "#0000009B" else "#00000072",
|
|
1999
|
-
)
|
|
2000
|
-
addView(favoriteIcon, LayoutParams(dp(20), dp(20)).apply {
|
|
2001
|
-
marginEnd = dp(if (currentLayout == "table") 8 else 12)
|
|
2002
|
-
})
|
|
2003
|
-
}
|
|
2004
|
-
item.json.optJSONObject("leading")?.let {
|
|
2005
|
-
addLeading(it, 40, spacingDp = if (currentLayout == "table") 10 else 12)
|
|
2006
|
-
}
|
|
2007
|
-
var hasLeadingAccessory = false
|
|
2008
|
-
item.json.optJSONObject("checkbox")?.let {
|
|
2009
|
-
bindCheckbox(item, it, checkboxState)
|
|
2010
|
-
hasLeadingAccessory = true
|
|
2011
|
-
}
|
|
2012
|
-
if (item.json.has("index")) {
|
|
2013
|
-
val indexView = trailingViews[0]
|
|
2014
|
-
indexView.text = item.json.optInt("index").toString()
|
|
2015
|
-
indexView.visibility = VISIBLE
|
|
2016
|
-
hasLeadingAccessory = true
|
|
2017
|
-
}
|
|
2018
|
-
if (hasLeadingAccessory) {
|
|
2019
|
-
addView(trailingColumn, LayoutParams(dp(32), LayoutParams.WRAP_CONTENT).apply { marginEnd = dp(10) })
|
|
2020
|
-
}
|
|
2021
|
-
val columns = item.json.getJSONArray("columns")
|
|
2022
|
-
val rowBadges = item.json.optJSONArray("badges")
|
|
2023
|
-
if (currentLayout == "table") {
|
|
2024
|
-
tableDataContainer.visibility = VISIBLE
|
|
2025
|
-
for (index in 0 until minOf(4, columns.length())) {
|
|
2026
|
-
val column = columns.getJSONObject(index)
|
|
2027
|
-
tableDataColumns[index].visibility = VISIBLE
|
|
2028
|
-
tableDataColumns[index].bind(
|
|
2029
|
-
column = column,
|
|
2030
|
-
rowBadges = if (index == 0) rowBadges else null,
|
|
2031
|
-
primaryColor = dataTextColor(column.optString("tone"), theme),
|
|
2032
|
-
secondaryColor = dataTextColor(
|
|
2033
|
-
column.optString("secondaryTone", "secondary"),
|
|
2034
|
-
theme,
|
|
2035
|
-
),
|
|
2036
|
-
infoColor = color(theme, "info", "#006DCBF2"),
|
|
2037
|
-
)
|
|
2038
|
-
tableDataColumns[index].layoutParams = LayoutParams(
|
|
2039
|
-
0,
|
|
2040
|
-
dp(40),
|
|
2041
|
-
column.optInt("weight", 1).toFloat(),
|
|
2042
|
-
)
|
|
2043
|
-
}
|
|
2044
|
-
addView(tableDataContainer, weighted())
|
|
2045
|
-
return
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
dataContainer.visibility = VISIBLE
|
|
2049
|
-
for (index in 0 until minOf(4, columns.length())) {
|
|
2050
|
-
val column = columns.getJSONObject(index)
|
|
2051
|
-
val view = dataColumns[index]
|
|
2052
|
-
view.text = styledDataText(column, if (index == 0) rowBadges else null, theme)
|
|
2053
|
-
view.maxLines = if (column.optString("secondaryText").isEmpty()) 1 else 2
|
|
2054
|
-
view.gravity = when (column.optString("alignment", "start")) {
|
|
2055
|
-
"center" -> Gravity.CENTER
|
|
2056
|
-
"end" -> Gravity.END
|
|
2057
|
-
else -> Gravity.START
|
|
2058
|
-
}
|
|
2059
|
-
view.visibility = VISIBLE
|
|
2060
|
-
view.setTextColor(
|
|
2061
|
-
when (column.optString("tone", "primary")) {
|
|
2062
|
-
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
2063
|
-
"positive" -> color(theme, "positive", "#00713FDE")
|
|
2064
|
-
"negative" -> color(theme, "negative", "#C40006D3")
|
|
2065
|
-
else -> color(theme, "primaryText", "#000000DF")
|
|
2066
|
-
},
|
|
2067
|
-
)
|
|
2068
|
-
view.layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, column.optInt("weight", 1).toFloat())
|
|
2069
|
-
}
|
|
2070
|
-
addView(dataContainer, weighted())
|
|
2071
|
-
}
|
|
2072
|
-
|
|
2073
|
-
private fun marketTypeface(weight: String, fallback: String): Typeface = when (
|
|
2074
|
-
weight.ifEmpty { fallback }
|
|
2075
|
-
) {
|
|
2076
|
-
"regular" -> NativeListFonts.regular(context)
|
|
2077
|
-
"semibold" -> NativeListFonts.semibold(context)
|
|
2078
|
-
"bold" -> NativeListFonts.bold(context)
|
|
2079
|
-
else -> NativeListFonts.medium(context)
|
|
2080
|
-
}
|
|
2081
|
-
|
|
2082
|
-
private fun applyMarketTextStyle(
|
|
2083
|
-
view: TextView,
|
|
2084
|
-
style: JSONObject?,
|
|
2085
|
-
defaultSize: Float,
|
|
2086
|
-
defaultLineHeight: Int,
|
|
2087
|
-
defaultWeight: String,
|
|
2088
|
-
defaultColor: Int,
|
|
2089
|
-
defaultAlignment: String,
|
|
2090
|
-
) {
|
|
2091
|
-
view.includeFontPadding = false
|
|
2092
|
-
view.fontFeatureSettings = "tnum"
|
|
2093
|
-
view.textSize = sp(style?.optDouble("fontSize", defaultSize.toDouble())?.toFloat() ?: defaultSize)
|
|
2094
|
-
view.typeface = marketTypeface(style?.optString("fontWeight").orEmpty(), defaultWeight)
|
|
2095
|
-
view.setTextColor(safeColor(style?.optString("color"), defaultColor))
|
|
2096
|
-
val alignment = style?.optString("alignment", defaultAlignment) ?: defaultAlignment
|
|
2097
|
-
view.gravity = Gravity.CENTER_VERTICAL or when (alignment) {
|
|
2098
|
-
"center" -> Gravity.CENTER_HORIZONTAL
|
|
2099
|
-
"end" -> Gravity.END
|
|
2100
|
-
else -> Gravity.START
|
|
2101
|
-
}
|
|
2102
|
-
view.maxLines = style?.optInt("lines", 1)?.coerceIn(1, 2) ?: 1
|
|
2103
|
-
view.ellipsize = TextUtils.TruncateAt.END
|
|
2104
|
-
TextViewCompat.setLineHeight(
|
|
2105
|
-
view,
|
|
2106
|
-
dp(style?.optDouble("lineHeight", defaultLineHeight.toDouble())?.roundToInt() ?: defaultLineHeight),
|
|
2107
|
-
)
|
|
2108
|
-
}
|
|
2109
|
-
|
|
2110
|
-
// OneKey patch: opt-in Market text uses the same pixel rounding and line box as RN.
|
|
2111
|
-
private fun applyMarketTextMetrics(view: TextView, style: JSONObject?) {
|
|
2112
|
-
if (style == null || view.visibility != VISIBLE || view.text.isEmpty()) return
|
|
2113
|
-
marketOriginalPaintFlags.putIfAbsent(view, view.paintFlags)
|
|
2114
|
-
view.paintFlags = view.paintFlags or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG
|
|
2115
|
-
if (style.has("fontSize")) {
|
|
2116
|
-
val sourceSize = sp(style.optDouble("fontSize").toFloat()) * resources.displayMetrics.density
|
|
2117
|
-
view.setTextSize(TypedValue.COMPLEX_UNIT_PX, kotlin.math.ceil(sourceSize.toDouble()).toFloat())
|
|
2118
|
-
}
|
|
2119
|
-
val text = SpannableStringBuilder(view.text)
|
|
2120
|
-
text.getSpans(0, text.length, SelectorLineHeightSpan::class.java).forEach(text::removeSpan)
|
|
2121
|
-
if (style.has("lineHeight")) {
|
|
2122
|
-
val lineHeight = kotlin.math.ceil(style.optDouble("lineHeight") * (if (selectorUsesSourceScale) 1f else NativeListScale.factor(resources)) * resources.displayMetrics.density).toInt()
|
|
2123
|
-
text.setSpan(SelectorLineHeightSpan(lineHeight), 0, text.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
2124
|
-
view.setLineSpacing(0f, 1f)
|
|
2125
|
-
}
|
|
2126
|
-
view.text = text
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
private fun marketText(value: String, segments: JSONArray?, fontSize: Float): CharSequence {
|
|
2130
|
-
if (segments == null || segments.length() == 0) return value
|
|
2131
|
-
val result = SpannableStringBuilder()
|
|
2132
|
-
for (index in 0 until segments.length()) {
|
|
2133
|
-
val segment = segments.getJSONObject(index)
|
|
2134
|
-
val start = result.length
|
|
2135
|
-
result.append(segment.optString("text"))
|
|
2136
|
-
if (segment.optString("style") == "subscript") {
|
|
2137
|
-
result.setSpan(
|
|
2138
|
-
AbsoluteSizeSpan(sp(kotlin.math.ceil(fontSize * 0.6f)).roundToInt(), true),
|
|
2139
|
-
start,
|
|
2140
|
-
result.length,
|
|
2141
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2142
|
-
)
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
return result
|
|
2146
|
-
}
|
|
2147
|
-
|
|
2148
|
-
private fun bindMarket(item: NativeListItem, theme: JSONObject?) {
|
|
2149
|
-
// Network badges extend beyond the leading image's frame.
|
|
2150
|
-
clipChildren = false
|
|
2151
|
-
val variant = item.json.optString("variant")
|
|
2152
|
-
val style = item.json.optJSONObject("style")
|
|
2153
|
-
val imageStyle = style?.optJSONObject("image")
|
|
2154
|
-
val imageWidth = imageStyle?.optDouble("width", if (variant == "stock") 40.0 else 32.0)?.roundToInt()
|
|
2155
|
-
?: if (variant == "stock") 40 else 32
|
|
2156
|
-
val imageHeight = imageStyle?.optDouble("height", if (variant == "stock") 40.0 else 32.0)?.roundToInt()
|
|
2157
|
-
?: if (variant == "stock") 40 else 32
|
|
2158
|
-
val horizontalPadding = style?.optDouble("horizontalPadding", if (variant == "perp") 16.0 else 20.0)?.roundToInt()
|
|
2159
|
-
?: if (variant == "perp") 16 else 20
|
|
2160
|
-
val verticalPadding = style?.optDouble("verticalPadding", 12.0)?.roundToInt() ?: 12
|
|
2161
|
-
val leadingGap = style?.optDouble("leadingGap", if (variant == "perp") 8.0 else 14.0)?.roundToInt()
|
|
2162
|
-
?: if (variant == "perp") 8 else 14
|
|
2163
|
-
setPadding(dp(horizontalPadding), dp(verticalPadding), dp(horizontalPadding), dp(verticalPadding))
|
|
2164
|
-
|
|
2165
|
-
item.json.optJSONObject("leadingAction")?.let { action ->
|
|
2166
|
-
leadingActionIcon.visibility = VISIBLE
|
|
2167
|
-
leadingActionIcon.iconName = action.optString("name")
|
|
2168
|
-
leadingActionIcon.glyphSizeDp = 24
|
|
2169
|
-
leadingActionIcon.tintColor = safeColor(
|
|
2170
|
-
action.optString("tintColor"),
|
|
2171
|
-
color(theme, "icon", "#0000009B"),
|
|
2172
|
-
)
|
|
2173
|
-
leadingActionIcon.isEnabled = !action.optBoolean("disabled", false)
|
|
2174
|
-
leadingActionIcon.alpha = if (leadingActionIcon.isEnabled) 1f else 0.4f
|
|
2175
|
-
leadingActionIcon.setTag(
|
|
2176
|
-
com.facebook.react.R.id.react_test_id,
|
|
2177
|
-
action.optString("testID").takeIf(String::isNotEmpty),
|
|
2178
|
-
)
|
|
2179
|
-
leadingActionIcon.contentDescription = action.optString("accessibilityLabel")
|
|
2180
|
-
leadingActionIcon.setOnClickListener {
|
|
2181
|
-
emitAction(
|
|
2182
|
-
item,
|
|
2183
|
-
action.optString("actionKey"),
|
|
2184
|
-
null,
|
|
2185
|
-
leadingActionIcon,
|
|
2186
|
-
"leadingAction",
|
|
2187
|
-
)
|
|
2188
|
-
}
|
|
2189
|
-
addView(leadingActionIcon, LayoutParams(dp(36), dp(36)).apply { marginEnd = dp(5) })
|
|
2190
|
-
}
|
|
2191
|
-
|
|
2192
|
-
val leading = JSONObject(item.json.getJSONObject("leading").toString())
|
|
2193
|
-
imageStyle?.optString("shape")?.takeIf(String::isNotEmpty)?.let { leading.put("shape", it) }
|
|
2194
|
-
imageStyle?.optString("contentFit")?.takeIf(String::isNotEmpty)?.let { contentFit ->
|
|
2195
|
-
leading.optJSONObject("image")?.put("contentFit", contentFit)
|
|
2196
|
-
}
|
|
2197
|
-
val shape = imageStyle?.optString("shape", leading.optString("shape", "circle"))
|
|
2198
|
-
?: leading.optString("shape", "circle")
|
|
2199
|
-
val cornerRadius = imageStyle?.takeIf { it.has("cornerRadius") }?.optDouble("cornerRadius")?.toFloat()
|
|
2200
|
-
?: when (shape) {
|
|
2201
|
-
"square" -> 0f
|
|
2202
|
-
"rounded" -> 8f
|
|
2203
|
-
else -> minOf(imageWidth, imageHeight) / 2f
|
|
2204
|
-
}
|
|
2205
|
-
addLeading(
|
|
2206
|
-
leading,
|
|
2207
|
-
sizeDp = imageWidth,
|
|
2208
|
-
spacingDp = leadingGap,
|
|
2209
|
-
heightDp = imageHeight,
|
|
2210
|
-
cornerRadiusDp = cornerRadius,
|
|
2211
|
-
)
|
|
2212
|
-
|
|
2213
|
-
// OneKey patch: retain the source gap without changing other templates.
|
|
2214
|
-
// addView(mainColumn, weighted())
|
|
2215
|
-
addView(mainColumn, weighted().apply {
|
|
2216
|
-
marginEnd = dp(style?.optDouble("contentTrailingGap", 0.0)?.roundToInt() ?: 0)
|
|
2217
|
-
})
|
|
2218
|
-
titleLine.packsChildrenAtStart = true
|
|
2219
|
-
showText(title, item.json.optString("title"), style?.optJSONObject("title")?.optInt("lines", 1) ?: 1)
|
|
2220
|
-
applyMarketTextStyle(
|
|
2221
|
-
title,
|
|
2222
|
-
style?.optJSONObject("title"),
|
|
2223
|
-
16f,
|
|
2224
|
-
24,
|
|
2225
|
-
"medium",
|
|
2226
|
-
color(theme, "primaryText", "#202020"),
|
|
2227
|
-
"start",
|
|
2228
|
-
)
|
|
2229
|
-
val badges = item.json.optJSONArray("badges")
|
|
2230
|
-
val titleBadgeGap = style?.optDouble("titleBadgeGap", 4.0)?.roundToInt() ?: 4
|
|
2231
|
-
if (badges != null) {
|
|
2232
|
-
for (index in 0 until minOf(marketBadgeViews.size, badges.length())) {
|
|
2233
|
-
val badge = badges.getJSONObject(index)
|
|
2234
|
-
val badgeView = marketBadgeViews[index]
|
|
2235
|
-
val badgeLabel = marketBadgeLabels[index]
|
|
2236
|
-
val badgeStyle = badge.optJSONObject("style")
|
|
2237
|
-
val hasGlyph = badge.optString("iconName") == "verified"
|
|
2238
|
-
val remoteIcon = badge.optJSONObject("icon")
|
|
2239
|
-
val hasIcon = hasGlyph || remoteIcon != null
|
|
2240
|
-
val text = badge.optString("text")
|
|
2241
|
-
val toneColor = when (badge.optString("tone")) {
|
|
2242
|
-
"success" -> color(theme, "positive", "#218358")
|
|
2243
|
-
"danger" -> color(theme, "negative", "#CE2C31")
|
|
2244
|
-
"info" -> color(theme, "info", "#0D74CE")
|
|
2245
|
-
"warning" -> color(theme, "primaryText", "#202020")
|
|
2246
|
-
else -> color(theme, "secondaryText", "#646464")
|
|
2247
|
-
}
|
|
2248
|
-
val foreground = safeColor(badge.optString("textColor"), toneColor)
|
|
2249
|
-
badgeView.visibility = VISIBLE
|
|
2250
|
-
val iconOnly = hasIcon && text.isEmpty()
|
|
2251
|
-
// OneKey patch: Market callers can request the original badge padding.
|
|
2252
|
-
val padding = badgeStyle?.optDouble("horizontalPadding", 5.0)?.roundToInt() ?: 5
|
|
2253
|
-
val leftPadding = if (badgeStyle?.has("horizontalPadding") == true) padding else if (hasIcon) 2 else 5
|
|
2254
|
-
// badgeView.setPadding(dp(if (iconOnly) 0 else if (hasIcon) 2 else 5), 0, dp(if (iconOnly) 0 else 5), 0)
|
|
2255
|
-
badgeView.setPadding(dp(if (iconOnly) 0 else leftPadding), 0, dp(if (iconOnly) 0 else padding), 0)
|
|
2256
|
-
badgeView.background = roundedFill(
|
|
2257
|
-
safeColor(
|
|
2258
|
-
badge.optString("backgroundColor"),
|
|
2259
|
-
if (hasIcon && text.isEmpty()) Color.TRANSPARENT else color(theme, "strongBackground", "#0000000F"),
|
|
2260
|
-
),
|
|
2261
|
-
4f,
|
|
2262
|
-
).apply {
|
|
2263
|
-
if (badgeStyle != null) this.cornerRadius = 4f * resources.displayMetrics.density
|
|
2264
|
-
}
|
|
2265
|
-
badgeLabel.text = text
|
|
2266
|
-
// OneKey patch: match SizableText's tabular numerals only for explicit Market metrics.
|
|
2267
|
-
badgeLabel.fontFeatureSettings = if (badgeStyle != null) "tnum" else null
|
|
2268
|
-
badgeLabel.textSize = sp(badgeStyle?.optDouble("fontSize", 11.0)?.toFloat() ?: 11f)
|
|
2269
|
-
badgeLabel.typeface = marketTypeface(badgeStyle?.optString("fontWeight").orEmpty(), "medium")
|
|
2270
|
-
if (badgeStyle?.has("lineHeight") == true) {
|
|
2271
|
-
TextViewCompat.setLineHeight(badgeLabel, dp(badgeStyle.optDouble("lineHeight").roundToInt()))
|
|
2272
|
-
}
|
|
2273
|
-
badgeLabel.setTextColor(foreground)
|
|
2274
|
-
badgeLabel.visibility = if (text.isEmpty()) GONE else VISIBLE
|
|
2275
|
-
if (hasGlyph) {
|
|
2276
|
-
marketBadgeGlyphs[index].apply {
|
|
2277
|
-
iconName = "BadgeVerifiedSolid"
|
|
2278
|
-
tintColor = foreground
|
|
2279
|
-
visibility = VISIBLE
|
|
2280
|
-
}
|
|
2281
|
-
}
|
|
2282
|
-
remoteIcon?.let { icon ->
|
|
2283
|
-
marketBadgeImages[index].visibility = VISIBLE
|
|
2284
|
-
marketBadgeImages[index].outlineProvider = circleOutlineProvider
|
|
2285
|
-
marketBadgeImages[index].clipToOutline = true
|
|
2286
|
-
bindImage(icon, marketBadgeImages[index], item.key, 20 + index, "generic")
|
|
2287
|
-
}
|
|
2288
|
-
val actionKey = badge.optString("actionKey")
|
|
2289
|
-
badgeView.isClickable = actionKey.isNotEmpty()
|
|
2290
|
-
if (actionKey.isNotEmpty()) {
|
|
2291
|
-
badgeView.setOnClickListener {
|
|
2292
|
-
emitAction(item, actionKey, null, badgeView, "marketBadge", index)
|
|
2293
|
-
}
|
|
2294
|
-
}
|
|
2295
|
-
badgeView.contentDescription = badge.optString("accessibilityLabel", text)
|
|
2296
|
-
titleLine.addView(
|
|
2297
|
-
badgeView,
|
|
2298
|
-
// OneKey patch: preserve 18dp unless the Market row opts into source metrics.
|
|
2299
|
-
// LayoutParams(LayoutParams.WRAP_CONTENT, dp(18)).apply { marginStart = dp(titleBadgeGap) },
|
|
2300
|
-
LayoutParams(LayoutParams.WRAP_CONTENT, dp(badgeStyle?.optDouble("height", 18.0)?.roundToInt() ?: 18)).apply { marginStart = dp(titleBadgeGap) },
|
|
2301
|
-
)
|
|
2302
|
-
}
|
|
2303
|
-
}
|
|
2304
|
-
subtitle.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
2305
|
-
topMargin = dp(style?.optDouble("lineGap", 0.0)?.roundToInt() ?: 0)
|
|
2306
|
-
}
|
|
2307
|
-
val subtitleText = item.json.optString("subtitle")
|
|
2308
|
-
val subtitleSegments = item.json.optJSONArray("subtitleSegments")
|
|
2309
|
-
if (subtitleText.isNotEmpty() || (subtitleSegments?.length() ?: 0) > 0) {
|
|
2310
|
-
subtitle.visibility = VISIBLE
|
|
2311
|
-
applyMarketTextStyle(
|
|
2312
|
-
subtitle,
|
|
2313
|
-
style?.optJSONObject("subtitle"),
|
|
2314
|
-
14f,
|
|
2315
|
-
20,
|
|
2316
|
-
"regular",
|
|
2317
|
-
color(theme, "secondaryText", "#646464"),
|
|
2318
|
-
"start",
|
|
2319
|
-
)
|
|
2320
|
-
subtitle.text = marketText(
|
|
2321
|
-
subtitleText,
|
|
2322
|
-
subtitleSegments,
|
|
2323
|
-
style?.optJSONObject("subtitle")?.optDouble("fontSize", 14.0)?.toFloat() ?: 14f,
|
|
2324
|
-
)
|
|
2325
|
-
}
|
|
2326
|
-
// OneKey patch: preserve independent name metrics, truncation, and volume.
|
|
2327
|
-
val subtitlePrefix = item.json.optJSONObject("subtitlePrefix")
|
|
2328
|
-
val subtitlePadding = style?.optDouble("subtitleTrailingPadding", 0.0)?.roundToInt() ?: 0
|
|
2329
|
-
if (subtitlePrefix != null || subtitlePadding > 0) {
|
|
2330
|
-
mainColumn.removeView(subtitle)
|
|
2331
|
-
mainColumn.removeView(tertiary)
|
|
2332
|
-
marketSubtitleLine.orientation = HORIZONTAL
|
|
2333
|
-
marketSubtitleLine.gravity = Gravity.CENTER_VERTICAL
|
|
2334
|
-
marketSubtitleLine.packsChildrenAtStart = true
|
|
2335
|
-
marketSubtitleLine.leadingTextMaxWidth = subtitlePrefix?.takeIf { it.has("maxWidth") }
|
|
2336
|
-
?.optDouble("maxWidth")?.roundToInt()?.let(::dp) ?: Int.MAX_VALUE
|
|
2337
|
-
marketSubtitleLine.setPadding(0, 0, dp(subtitlePadding), 0)
|
|
2338
|
-
showText(tertiary, subtitlePrefix?.optString("text") ?: "", 1)
|
|
2339
|
-
applyMarketTextStyle(tertiary, subtitlePrefix?.optJSONObject("style"), 12f, 16, "regular", color(theme, "secondaryText", "#646464"), "start")
|
|
2340
|
-
marketSubtitleLine.addView(tertiary, wrap())
|
|
2341
|
-
marketSubtitleLine.addView(subtitle, wrap().apply {
|
|
2342
|
-
if (tertiary.visibility == VISIBLE && subtitle.visibility == VISIBLE) {
|
|
2343
|
-
marginStart = dp(subtitlePrefix?.optDouble("gap", 4.0)?.roundToInt() ?: 4)
|
|
2344
|
-
}
|
|
2345
|
-
})
|
|
2346
|
-
mainColumn.addView(marketSubtitleLine, 1, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
2347
|
-
topMargin = dp(style?.optDouble("lineGap", 0.0)?.roundToInt() ?: 0)
|
|
2348
|
-
})
|
|
2349
|
-
marketSubtitleLine.visibility = if (tertiary.visibility == VISIBLE || subtitle.visibility == VISIBLE) VISIBLE else GONE
|
|
2350
|
-
}
|
|
2351
|
-
trailingColumn.orientation = HORIZONTAL
|
|
2352
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
2353
|
-
addView(trailingColumn, wrap())
|
|
2354
|
-
bindMarketQuote(item, theme)
|
|
2355
|
-
item.json.optJSONObject("diagnostics")?.optString("imageBindActionKey")
|
|
2356
|
-
?.takeIf(String::isNotEmpty)
|
|
2357
|
-
?.takeIf { leading.optJSONObject("image") != null || leading.optJSONObject("networkImage") != null }
|
|
2358
|
-
?.let { actionKey -> onAction?.invoke(item, actionKey, null, null) }
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
fun bindMarketQuote(item: NativeListItem, theme: JSONObject?) {
|
|
2362
|
-
if (boundKey != item.key || item.type != "market") return
|
|
2363
|
-
tag = item
|
|
2364
|
-
val style = item.json.optJSONObject("style")
|
|
2365
|
-
val priceStyle = style?.optJSONObject("price")
|
|
2366
|
-
val price = trailingViews[0]
|
|
2367
|
-
price.isClickable = false
|
|
2368
|
-
price.isLongClickable = false
|
|
2369
|
-
price.visibility = VISIBLE
|
|
2370
|
-
price.maxWidth = dp(112)
|
|
2371
|
-
applyMarketTextStyle(
|
|
2372
|
-
price,
|
|
2373
|
-
priceStyle,
|
|
2374
|
-
16f,
|
|
2375
|
-
24,
|
|
2376
|
-
"medium",
|
|
2377
|
-
color(theme, "primaryText", "#202020"),
|
|
2378
|
-
"end",
|
|
2379
|
-
)
|
|
2380
|
-
price.text = marketText(
|
|
2381
|
-
item.json.optString("price"),
|
|
2382
|
-
item.json.optJSONArray("priceSegments"),
|
|
2383
|
-
priceStyle?.optDouble("fontSize", 16.0)?.toFloat() ?: 16f,
|
|
2384
|
-
)
|
|
2385
|
-
val trailingGap = style?.optDouble("trailingGap", 8.0)?.roundToInt() ?: 8
|
|
2386
|
-
price.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
2387
|
-
marginEnd = dp(trailingGap)
|
|
2388
|
-
}
|
|
2389
|
-
|
|
2390
|
-
val changeData = item.json.getJSONObject("change")
|
|
2391
|
-
val changeStyle = style?.optJSONObject("change")
|
|
2392
|
-
val change = trailingViews[1]
|
|
2393
|
-
change.isClickable = false
|
|
2394
|
-
change.isLongClickable = false
|
|
2395
|
-
val toneColor = when (changeData.optString("tone")) {
|
|
2396
|
-
"positive" -> color(theme, "positive", "#218358")
|
|
2397
|
-
"negative" -> color(theme, "negative", "#CE2C31")
|
|
2398
|
-
else -> color(theme, "secondaryText", "#8D8D8D")
|
|
2399
|
-
}
|
|
2400
|
-
val textColor = safeColor(
|
|
2401
|
-
changeData.optString("textColor"),
|
|
2402
|
-
color(theme, "inverseText", "#FFFFFF"),
|
|
2403
|
-
)
|
|
2404
|
-
change.visibility = VISIBLE
|
|
2405
|
-
applyMarketTextStyle(change, changeStyle, 14f, 20, "medium", textColor, "center")
|
|
2406
|
-
change.text = marketText(
|
|
2407
|
-
changeData.optString("text"),
|
|
2408
|
-
changeData.optJSONArray("textSegments"),
|
|
2409
|
-
changeStyle?.optDouble("fontSize", 14.0)?.toFloat() ?: 14f,
|
|
2410
|
-
)
|
|
2411
|
-
change.background = roundedFill(
|
|
2412
|
-
safeColor(changeData.optString("backgroundColor"), toneColor),
|
|
2413
|
-
style?.optDouble("changeCornerRadius", 8.0)?.toFloat() ?: 8f,
|
|
2414
|
-
)
|
|
2415
|
-
change.layoutParams = LayoutParams(
|
|
2416
|
-
dp(style?.optDouble("changeWidth", 80.0)?.roundToInt() ?: 80),
|
|
2417
|
-
dp(style?.optDouble("changeHeight", 32.0)?.roundToInt() ?: 32),
|
|
2418
|
-
)
|
|
2419
|
-
applyMarketTextMetrics(title, style?.optJSONObject("title"))
|
|
2420
|
-
applyMarketTextMetrics(subtitle, style?.optJSONObject("subtitle"))
|
|
2421
|
-
applyMarketTextMetrics(tertiary, item.json.optJSONObject("subtitlePrefix")?.optJSONObject("style"))
|
|
2422
|
-
applyMarketTextMetrics(price, priceStyle)
|
|
2423
|
-
applyMarketTextMetrics(change, changeStyle)
|
|
2424
|
-
val badges = item.json.optJSONArray("badges")
|
|
2425
|
-
marketBadgeLabels.forEachIndexed { index, label ->
|
|
2426
|
-
val badge = badges?.optJSONObject(index)
|
|
2427
|
-
val badgeStyle = badge?.optJSONObject("style")
|
|
2428
|
-
applyMarketTextMetrics(label, badgeStyle)
|
|
2429
|
-
if (badge != null && label.visibility == VISIBLE && badgeStyle?.has("horizontalPadding") == true &&
|
|
2430
|
-
badge.optJSONObject("icon") == null && badge.optString("iconName").isEmpty()) {
|
|
2431
|
-
// OneKey patch: match RN's intrinsic text width and one rounded padding pair.
|
|
2432
|
-
label.layoutParams = wrap().apply { width = label.paint.measureText(label.text.toString()).roundToInt() }
|
|
2433
|
-
val padding = badgeStyle.optDouble("horizontalPadding") * resources.displayMetrics.density
|
|
2434
|
-
marketBadgeViews[index].setPadding(kotlin.math.floor(padding).toInt(), 0, (padding * 2).roundToInt() - kotlin.math.floor(padding).toInt(), 0)
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
contentDescription = item.json.optString(
|
|
2438
|
-
"accessibilityLabel",
|
|
2439
|
-
listOf(
|
|
2440
|
-
item.json.optString("title"),
|
|
2441
|
-
item.json.optString("subtitle"),
|
|
2442
|
-
item.json.optString("price"),
|
|
2443
|
-
changeData.optString("text"),
|
|
2444
|
-
).filter(String::isNotEmpty).joinToString(", "),
|
|
2445
|
-
)
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
private fun styledDataText(
|
|
2449
|
-
column: JSONObject,
|
|
2450
|
-
badges: JSONArray?,
|
|
2451
|
-
theme: JSONObject?,
|
|
2452
|
-
): CharSequence {
|
|
2453
|
-
val result = SpannableStringBuilder(column.optString("text"))
|
|
2454
|
-
result.setSpan(
|
|
2455
|
-
ForegroundColorSpan(dataTextColor(column.optString("tone"), theme)),
|
|
2456
|
-
0,
|
|
2457
|
-
result.length,
|
|
2458
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2459
|
-
)
|
|
2460
|
-
if (badges != null) {
|
|
2461
|
-
for (index in 0 until minOf(2, badges.length())) {
|
|
2462
|
-
val start = result.length
|
|
2463
|
-
result.append(" ${badges.getJSONObject(index).optString("text")} ")
|
|
2464
|
-
result.setSpan(
|
|
2465
|
-
ForegroundColorSpan(color(theme, "info", "#006DCBF2")),
|
|
2466
|
-
start,
|
|
2467
|
-
result.length,
|
|
2468
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2469
|
-
)
|
|
2470
|
-
result.setSpan(
|
|
2471
|
-
BackgroundColorSpan(parseNativeListColor("#008FF519")),
|
|
2472
|
-
start,
|
|
2473
|
-
result.length,
|
|
2474
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2475
|
-
)
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
column.optString("secondaryText").takeIf(String::isNotEmpty)?.let { secondary ->
|
|
2479
|
-
val start = result.length
|
|
2480
|
-
result.append("\n$secondary")
|
|
2481
|
-
result.setSpan(
|
|
2482
|
-
ForegroundColorSpan(dataTextColor(column.optString("secondaryTone", "secondary"), theme)),
|
|
2483
|
-
start,
|
|
2484
|
-
result.length,
|
|
2485
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2486
|
-
)
|
|
2487
|
-
}
|
|
2488
|
-
return result
|
|
2489
|
-
}
|
|
2490
|
-
|
|
2491
|
-
private fun dataTextColor(tone: String, theme: JSONObject?): Int = when (tone.ifEmpty { "primary" }) {
|
|
2492
|
-
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
2493
|
-
"positive" -> color(theme, "positive", "#00713FDE")
|
|
2494
|
-
"negative" -> color(theme, "negative", "#C40006D3")
|
|
2495
|
-
else -> color(theme, "primaryText", "#000000DF")
|
|
2496
|
-
}
|
|
2497
|
-
|
|
2498
|
-
private fun bindMediaTile(item: NativeListItem, theme: JSONObject?) {
|
|
2499
|
-
isMediaTile = true
|
|
2500
|
-
orientation = VERTICAL
|
|
2501
|
-
gravity = Gravity.START
|
|
2502
|
-
setPadding(dp(10), dp(10), dp(10), dp(10))
|
|
2503
|
-
val imageState = item.json.optString("imageState", "loaded")
|
|
2504
|
-
when (imageState) {
|
|
2505
|
-
"empty" -> {
|
|
2506
|
-
addLeading(null, 160)
|
|
2507
|
-
leadingFrame.background = roundedFill(Color.WHITE, 10f)
|
|
2508
|
-
}
|
|
2509
|
-
"error" -> {
|
|
2510
|
-
addLeading(null, 160)
|
|
2511
|
-
leadingFallback.visibility = VISIBLE
|
|
2512
|
-
leadingFallback.background = roundedFill(
|
|
2513
|
-
color(theme, "strongBackground", "#0000000F"),
|
|
2514
|
-
10f,
|
|
2515
|
-
)
|
|
2516
|
-
leadingIcon.visibility = VISIBLE
|
|
2517
|
-
leadingIcon.iconName = "ImageSquareWavesOutline"
|
|
2518
|
-
leadingIcon.tintColor = parseNativeListColor("#00000044")
|
|
2519
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(24), dp(24), Gravity.CENTER)
|
|
2520
|
-
}
|
|
2521
|
-
else -> {
|
|
2522
|
-
addLeading(
|
|
2523
|
-
JSONObject().put("kind", "image").put("image", item.json.getJSONObject("image")),
|
|
2524
|
-
160,
|
|
2525
|
-
)
|
|
2526
|
-
}
|
|
2527
|
-
}
|
|
2528
|
-
leadingFrame.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, dp(160))
|
|
2529
|
-
leadingFallback.layoutParams = FrameLayout.LayoutParams(
|
|
2530
|
-
LayoutParams.MATCH_PARENT,
|
|
2531
|
-
LayoutParams.MATCH_PARENT,
|
|
2532
|
-
)
|
|
2533
|
-
leadingImages[0].layoutParams = FrameLayout.LayoutParams(
|
|
2534
|
-
LayoutParams.MATCH_PARENT,
|
|
2535
|
-
LayoutParams.MATCH_PARENT,
|
|
2536
|
-
)
|
|
2537
|
-
leadingImages[0].outlineProvider = object : ViewOutlineProvider() {
|
|
2538
|
-
override fun getOutline(view: View, outline: Outline) {
|
|
2539
|
-
outline.setRoundRect(0, 0, view.width, view.height, dp(10).toFloat())
|
|
2540
|
-
}
|
|
2541
|
-
}
|
|
2542
|
-
leadingImages[0].clipToOutline = true
|
|
2543
|
-
mainColumn.removeView(titleLine)
|
|
2544
|
-
mainColumn.removeView(subtitle)
|
|
2545
|
-
mediaMetadataRow.orientation = HORIZONTAL
|
|
2546
|
-
mediaMetadataRow.gravity = Gravity.CENTER_VERTICAL
|
|
2547
|
-
mediaMetadataRow.addView(subtitle, weighted().apply { marginEnd = dp(8) })
|
|
2548
|
-
item.json.optJSONObject("networkImage")?.let { networkImage ->
|
|
2549
|
-
// OneKey patch: Preserve badge layout without exposing a loading/error tile.
|
|
2550
|
-
// mediaNetworkImage.visibility = VISIBLE
|
|
2551
|
-
mediaNetworkImage.outlineProvider = circleOutlineProvider
|
|
2552
|
-
mediaNetworkImage.clipToOutline = true
|
|
2553
|
-
mediaMetadataRow.addView(mediaNetworkImage, LayoutParams(dp(14), dp(14)))
|
|
2554
|
-
bindImage(networkImage, mediaNetworkImage, item.key, 2, "network", hideUntilLoaded = true)
|
|
2555
|
-
}
|
|
2556
|
-
mainColumn.addView(mediaMetadataRow, 0)
|
|
2557
|
-
mainColumn.addView(titleLine, 1)
|
|
2558
|
-
showText(subtitle, item.json.optString("subtitle"), 1)
|
|
2559
|
-
showText(title, item.json.optString("title"), 1)
|
|
2560
|
-
item.json.optJSONObject("badge")?.optString("text")?.let { value ->
|
|
2561
|
-
mediaBadge.text = value
|
|
2562
|
-
mediaBadge.setTextColor(parseNativeListColor("#FCFCFC"))
|
|
2563
|
-
mediaBadge.textSize = sp(14f)
|
|
2564
|
-
mediaBadge.background = roundedStroke(
|
|
2565
|
-
Color.WHITE,
|
|
2566
|
-
parseNativeListColor("#000000DF"),
|
|
2567
|
-
10f,
|
|
2568
|
-
)
|
|
2569
|
-
mediaBadge.setPadding(dp(8), 0, dp(8), 0)
|
|
2570
|
-
mediaBadge.layoutParams = FrameLayout.LayoutParams(
|
|
2571
|
-
LayoutParams.WRAP_CONTENT,
|
|
2572
|
-
dp(24),
|
|
2573
|
-
Gravity.END or Gravity.BOTTOM,
|
|
2574
|
-
)
|
|
2575
|
-
mediaBadge.visibility = VISIBLE
|
|
2576
|
-
}
|
|
2577
|
-
addView(mainColumn, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
2578
|
-
topMargin = dp(8)
|
|
2579
|
-
})
|
|
2580
|
-
item.json.optString("closeActionKey").takeIf { it.isNotEmpty() }?.let { actionKey ->
|
|
2581
|
-
val close = trailingViews[0]
|
|
2582
|
-
close.text = "×"
|
|
2583
|
-
close.textSize = sp(22f)
|
|
2584
|
-
close.gravity = Gravity.END
|
|
2585
|
-
close.visibility = VISIBLE
|
|
2586
|
-
close.setOnClickListener {
|
|
2587
|
-
emitAction(item, actionKey, null, close, "mediaClose")
|
|
2588
|
-
}
|
|
2589
|
-
addView(trailingColumn, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
2590
|
-
}
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
private fun bindMetricCard(item: NativeListItem, theme: JSONObject?) {
|
|
2594
|
-
orientation = VERTICAL
|
|
2595
|
-
gravity = Gravity.START
|
|
2596
|
-
setPadding(dp(14), dp(14), dp(14), dp(14))
|
|
2597
|
-
val variant = item.json.optString("variant", "standard")
|
|
2598
|
-
if (variant == "activity" || variant == "performance") {
|
|
2599
|
-
bindCompositeMetricCard(item, theme, variant)
|
|
2600
|
-
return
|
|
2601
|
-
}
|
|
2602
|
-
item.json.optJSONObject("visual")?.let {
|
|
2603
|
-
addLeading(it, 32)
|
|
2604
|
-
leadingFrame.clipChildren = true
|
|
2605
|
-
leadingFrame.clipToPadding = true
|
|
2606
|
-
}
|
|
2607
|
-
showText(subtitle, item.json.optString("title"), 1)
|
|
2608
|
-
subtitle.setTextColor(color(theme, "disabledText", "#00000072"))
|
|
2609
|
-
showText(title, item.json.optString("value"), 1)
|
|
2610
|
-
title.textSize = sp(if (item.json.optString("size") == "large") 24f else 18f)
|
|
2611
|
-
title.typeface = NativeListFonts.semibold(context)
|
|
2612
|
-
showText(status, item.json.optString("trend"), 1)
|
|
2613
|
-
status.setTextColor(
|
|
2614
|
-
when (item.json.optString("trendTone", "neutral")) {
|
|
2615
|
-
"positive" -> color(theme, "positive", "#00713FDE")
|
|
2616
|
-
"negative" -> color(theme, "negative", "#C40006D3")
|
|
2617
|
-
else -> color(theme, "secondaryText", "#0000009B")
|
|
2618
|
-
},
|
|
2619
|
-
)
|
|
2620
|
-
showText(metricSubtitle, item.json.optString("subtitle"), 1)
|
|
2621
|
-
item.json.optJSONObject("badge")?.optString("text")?.let { showText(badgeLine, it, 1) }
|
|
2622
|
-
addView(mainColumn, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
2623
|
-
}
|
|
2624
|
-
|
|
2625
|
-
private fun bindCompositeMetricCard(item: NativeListItem, theme: JSONObject?, variant: String) {
|
|
2626
|
-
val metrics = item.json.optJSONArray("metrics") ?: JSONArray()
|
|
2627
|
-
addView(
|
|
2628
|
-
metricText(
|
|
2629
|
-
value = item.json.optString("title").uppercase(),
|
|
2630
|
-
size = 11f,
|
|
2631
|
-
lineHeight = 14,
|
|
2632
|
-
typeface = NativeListFonts.regular(context),
|
|
2633
|
-
textColor = color(theme, "disabledText", "#00000072"),
|
|
2634
|
-
letterSpacingDp = 1.2f,
|
|
2635
|
-
),
|
|
2636
|
-
weightedWidth(),
|
|
2637
|
-
)
|
|
2638
|
-
if (variant == "activity") {
|
|
2639
|
-
addView(activityHeroMetrics(metrics, theme), weightedWidth().apply {
|
|
2640
|
-
topMargin = dp(14)
|
|
2641
|
-
})
|
|
2642
|
-
addView(View(context).apply {
|
|
2643
|
-
setBackgroundColor(color(theme, "separator", "#0000001F"))
|
|
2644
|
-
}, LayoutParams(LayoutParams.MATCH_PARENT, 1).apply {
|
|
2645
|
-
topMargin = dp(14)
|
|
2646
|
-
bottomMargin = dp(14)
|
|
2647
|
-
})
|
|
2648
|
-
addView(activityCompactMetrics(metrics, theme), weightedWidth())
|
|
2649
|
-
} else {
|
|
2650
|
-
val winRateRow = LinearLayout(context).apply {
|
|
2651
|
-
orientation = HORIZONTAL
|
|
2652
|
-
gravity = Gravity.BOTTOM
|
|
2653
|
-
if (metrics.length() > 0) {
|
|
2654
|
-
addView(
|
|
2655
|
-
makeMetricColumn(
|
|
2656
|
-
metric = metrics.getJSONObject(0),
|
|
2657
|
-
visualSlot = 0,
|
|
2658
|
-
theme = theme,
|
|
2659
|
-
alignment = Gravity.START,
|
|
2660
|
-
valueSize = 18f,
|
|
2661
|
-
valueLineHeight = 24,
|
|
2662
|
-
valueTypeface = NativeListFonts.semibold(context),
|
|
2663
|
-
),
|
|
2664
|
-
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
2665
|
-
)
|
|
2666
|
-
}
|
|
2667
|
-
if (metrics.length() > 1) {
|
|
2668
|
-
addView(
|
|
2669
|
-
makeMetricColumn(
|
|
2670
|
-
metric = metrics.getJSONObject(1),
|
|
2671
|
-
visualSlot = 1,
|
|
2672
|
-
theme = theme,
|
|
2673
|
-
alignment = Gravity.END,
|
|
2674
|
-
valueSize = 14f,
|
|
2675
|
-
valueLineHeight = 20,
|
|
2676
|
-
valueTypeface = NativeListFonts.semibold(context),
|
|
2677
|
-
),
|
|
2678
|
-
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
2679
|
-
)
|
|
2680
|
-
}
|
|
2681
|
-
}
|
|
2682
|
-
addView(winRateRow, weightedWidth().apply { topMargin = dp(14) })
|
|
2683
|
-
val progressValue = item.json.optDouble("progress", 0.0).coerceIn(0.0, 1.0)
|
|
2684
|
-
val progress = LinearLayout(context).apply {
|
|
2685
|
-
orientation = HORIZONTAL
|
|
2686
|
-
clipToOutline = true
|
|
2687
|
-
outlineProvider = object : ViewOutlineProvider() {
|
|
2688
|
-
override fun getOutline(view: View, outline: Outline) {
|
|
2689
|
-
outline.setRoundRect(0, 0, view.width, view.height, dp(2).toFloat())
|
|
2690
|
-
}
|
|
2691
|
-
}
|
|
2692
|
-
if (progressValue > 0.0) {
|
|
2693
|
-
addView(View(context).apply {
|
|
2694
|
-
setBackgroundColor(parseNativeListColor("#22AB15"))
|
|
2695
|
-
}, LayoutParams(0, LayoutParams.MATCH_PARENT, progressValue.toFloat()))
|
|
2696
|
-
}
|
|
2697
|
-
if (progressValue < 1.0) {
|
|
2698
|
-
addView(View(context).apply {
|
|
2699
|
-
setBackgroundColor(parseNativeListColor("#E5484D"))
|
|
2700
|
-
}, LayoutParams(0, LayoutParams.MATCH_PARENT, (1.0 - progressValue).toFloat()))
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
addView(progress, LayoutParams(LayoutParams.MATCH_PARENT, dp(4)).apply {
|
|
2704
|
-
topMargin = dp(8)
|
|
2705
|
-
})
|
|
2706
|
-
addView(performanceAverageMetrics(metrics, theme), weightedWidth().apply {
|
|
2707
|
-
topMargin = dp(14)
|
|
2708
|
-
})
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
|
|
2712
|
-
private fun activityHeroMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
2713
|
-
LinearLayout(context).apply {
|
|
2714
|
-
orientation = HORIZONTAL
|
|
2715
|
-
gravity = Gravity.BOTTOM
|
|
2716
|
-
if (metrics.length() > 0) {
|
|
2717
|
-
addView(
|
|
2718
|
-
makeMetricColumn(
|
|
2719
|
-
metric = metrics.getJSONObject(0),
|
|
2720
|
-
visualSlot = 0,
|
|
2721
|
-
theme = theme,
|
|
2722
|
-
alignment = Gravity.START,
|
|
2723
|
-
valueSize = 16f,
|
|
2724
|
-
valueLineHeight = 24,
|
|
2725
|
-
valueTypeface = NativeListFonts.semibold(context),
|
|
2726
|
-
),
|
|
2727
|
-
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
2728
|
-
)
|
|
2729
|
-
}
|
|
2730
|
-
if (metrics.length() > 1) {
|
|
2731
|
-
addView(
|
|
2732
|
-
makeMetricColumn(
|
|
2733
|
-
metric = metrics.getJSONObject(1),
|
|
2734
|
-
visualSlot = 1,
|
|
2735
|
-
theme = theme,
|
|
2736
|
-
alignment = Gravity.END,
|
|
2737
|
-
valueSize = 14f,
|
|
2738
|
-
valueLineHeight = 20,
|
|
2739
|
-
valueTypeface = NativeListFonts.semibold(context),
|
|
2740
|
-
),
|
|
2741
|
-
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
2742
|
-
)
|
|
2743
|
-
}
|
|
2744
|
-
}
|
|
2745
|
-
|
|
2746
|
-
private fun activityCompactMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
2747
|
-
LinearLayout(context).apply {
|
|
2748
|
-
orientation = HORIZONTAL
|
|
2749
|
-
gravity = Gravity.CENTER_VERTICAL
|
|
2750
|
-
for (index in 2 until metrics.length()) {
|
|
2751
|
-
val alignment = when (index) {
|
|
2752
|
-
2 -> Gravity.START
|
|
2753
|
-
metrics.length() - 1 -> Gravity.END
|
|
2754
|
-
else -> Gravity.CENTER_HORIZONTAL
|
|
2755
|
-
}
|
|
2756
|
-
addView(
|
|
2757
|
-
makeMetricColumn(
|
|
2758
|
-
metric = metrics.getJSONObject(index),
|
|
2759
|
-
visualSlot = index,
|
|
2760
|
-
theme = theme,
|
|
2761
|
-
alignment = alignment,
|
|
2762
|
-
valueSize = 14f,
|
|
2763
|
-
valueLineHeight = 20,
|
|
2764
|
-
valueTypeface = NativeListFonts.medium(context),
|
|
2765
|
-
),
|
|
2766
|
-
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
2767
|
-
)
|
|
2768
|
-
}
|
|
2769
|
-
}
|
|
2770
|
-
|
|
2771
|
-
private fun performanceAverageMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
2772
|
-
LinearLayout(context).apply {
|
|
2773
|
-
orientation = HORIZONTAL
|
|
2774
|
-
val indices = listOf(2, metrics.length() - 1).distinct().filter { it in 0 until metrics.length() }
|
|
2775
|
-
indices.forEachIndexed { position, index ->
|
|
2776
|
-
val metric = metrics.getJSONObject(index)
|
|
2777
|
-
val card = makeMetricColumn(
|
|
2778
|
-
metric = metric,
|
|
2779
|
-
visualSlot = index,
|
|
2780
|
-
theme = theme,
|
|
2781
|
-
alignment = Gravity.START,
|
|
2782
|
-
valueSize = 14f,
|
|
2783
|
-
valueLineHeight = 20,
|
|
2784
|
-
valueTypeface = NativeListFonts.medium(context),
|
|
2785
|
-
).apply {
|
|
2786
|
-
setPadding(dp(10), dp(10), dp(10), dp(10))
|
|
2787
|
-
background = roundedFill(color(theme, "strongBackground", "#0000000F"), 8f)
|
|
2788
|
-
}
|
|
2789
|
-
addView(card, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f).apply {
|
|
2790
|
-
if (position > 0) marginStart = dp(8)
|
|
2791
|
-
})
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
|
|
2795
|
-
private fun makeMetricColumn(
|
|
2796
|
-
metric: JSONObject,
|
|
2797
|
-
visualSlot: Int,
|
|
2798
|
-
theme: JSONObject?,
|
|
2799
|
-
alignment: Int,
|
|
2800
|
-
valueSize: Float,
|
|
2801
|
-
valueLineHeight: Int,
|
|
2802
|
-
valueTypeface: Typeface,
|
|
2803
|
-
) = LinearLayout(context).apply {
|
|
2804
|
-
orientation = VERTICAL
|
|
2805
|
-
gravity = alignment
|
|
2806
|
-
addView(
|
|
2807
|
-
metricText(
|
|
2808
|
-
value = metric.optString("label"),
|
|
2809
|
-
size = 11f,
|
|
2810
|
-
lineHeight = 14,
|
|
2811
|
-
typeface = NativeListFonts.regular(context),
|
|
2812
|
-
textColor = color(theme, "disabledText", "#00000072"),
|
|
2813
|
-
gravity = alignment,
|
|
2814
|
-
),
|
|
2815
|
-
)
|
|
2816
|
-
val valueText = metricText(
|
|
2817
|
-
value = metric.optString("value"),
|
|
2818
|
-
size = valueSize,
|
|
2819
|
-
lineHeight = valueLineHeight,
|
|
2820
|
-
typeface = valueTypeface,
|
|
2821
|
-
textColor = dataTextColor(metric.optString("tone"), theme),
|
|
2822
|
-
gravity = alignment,
|
|
2823
|
-
)
|
|
2824
|
-
val valueVisual = metric.optJSONObject("visual")
|
|
2825
|
-
val valueView = if (valueVisual != null && visualSlot in metricVisualImages.indices) {
|
|
2826
|
-
LinearLayout(context).apply {
|
|
2827
|
-
orientation = HORIZONTAL
|
|
2828
|
-
gravity = Gravity.CENTER_VERTICAL
|
|
2829
|
-
val image = metricVisualImages[visualSlot]
|
|
2830
|
-
image.visibility = VISIBLE
|
|
2831
|
-
image.outlineProvider = circleOutlineProvider
|
|
2832
|
-
image.clipToOutline = true
|
|
2833
|
-
valueVisual.optString("backgroundColor").takeIf(String::isNotEmpty)?.let { backgroundColor ->
|
|
2834
|
-
image.background = roundedFill(
|
|
2835
|
-
safeColor(backgroundColor, Color.WHITE),
|
|
2836
|
-
8f,
|
|
2837
|
-
)
|
|
2838
|
-
}
|
|
2839
|
-
visualSources(valueVisual).firstOrNull()?.let { (source, variant) ->
|
|
2840
|
-
bindImage(source, image, boundKey ?: "metric", visualSlot, variant)
|
|
2841
|
-
}
|
|
2842
|
-
addView(image, LayoutParams(dp(16), dp(16)).apply { marginEnd = dp(6) })
|
|
2843
|
-
addView(valueText, wrap())
|
|
2844
|
-
}
|
|
2845
|
-
} else {
|
|
2846
|
-
valueText
|
|
2847
|
-
}
|
|
2848
|
-
addView(
|
|
2849
|
-
valueView,
|
|
2850
|
-
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
2851
|
-
topMargin = dp(2)
|
|
2852
|
-
},
|
|
2853
|
-
)
|
|
2854
|
-
}
|
|
2855
|
-
|
|
2856
|
-
private fun metricText(
|
|
2857
|
-
value: String,
|
|
2858
|
-
size: Float,
|
|
2859
|
-
lineHeight: Int,
|
|
2860
|
-
typeface: Typeface,
|
|
2861
|
-
textColor: Int,
|
|
2862
|
-
letterSpacingDp: Float = 0f,
|
|
2863
|
-
gravity: Int = Gravity.START,
|
|
2864
|
-
) = TextView(context).apply {
|
|
2865
|
-
includeFontPadding = false
|
|
2866
|
-
text = value
|
|
2867
|
-
textSize = sp(size)
|
|
2868
|
-
this.typeface = typeface
|
|
2869
|
-
setTextColor(textColor)
|
|
2870
|
-
this.gravity = gravity
|
|
2871
|
-
maxLines = 1
|
|
2872
|
-
ellipsize = TextUtils.TruncateAt.END
|
|
2873
|
-
fontFeatureSettings = "tnum"
|
|
2874
|
-
TextViewCompat.setLineHeight(this, dp(lineHeight))
|
|
2875
|
-
if (letterSpacingDp > 0f) {
|
|
2876
|
-
letterSpacing = letterSpacingDp / sp(size)
|
|
2877
|
-
}
|
|
2878
|
-
}
|
|
2879
|
-
|
|
2880
|
-
private fun weightedWidth() = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
2881
|
-
|
|
2882
|
-
private fun bindSectionHeader(
|
|
2883
|
-
item: NativeListItem,
|
|
2884
|
-
theme: JSONObject?,
|
|
2885
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
2886
|
-
) {
|
|
2887
|
-
val variant = item.json.optString("variant")
|
|
2888
|
-
val isSummary = variant == "summary"
|
|
2889
|
-
val isGallery = variant == "gallery"
|
|
2890
|
-
val isTable = currentLayout == "table"
|
|
2891
|
-
// OneKey patch: title help emits its own frame instead of toggling the section.
|
|
2892
|
-
item.json.optString("titleActionKey").takeIf { it.isNotEmpty() }?.let { key ->
|
|
2893
|
-
title.setOnClickListener { emitAction(item, key, null, title, "leadingAction") }
|
|
2894
|
-
}
|
|
2895
|
-
val isNetworkSelector = item.json.optString("presentation") == "networkSelector"
|
|
2896
|
-
val isHistory = variant == "history" || item.sectionKey?.startsWith("history-") == true
|
|
2897
|
-
val isTokenManager = item.sectionKey in setOf("linear-tokens", "action-tokens")
|
|
2898
|
-
val isExplicitNetworkHeader = isNetworkSelector && item.json.has("height")
|
|
2899
|
-
val hasDottedTitle = isSummary || (isNetworkSelector && (!isExplicitNetworkHeader || item.json.optString("titleActionKey").isNotEmpty())) ||
|
|
2900
|
-
(item.json.optString("value").isNotEmpty() && item.json.optJSONObject("checkbox") != null)
|
|
2901
|
-
// Linear/sectioned snapshots reserve the ListItem mx=8 at RecyclerView
|
|
2902
|
-
// level, so header-local insets below are source px minus that outer inset.
|
|
2903
|
-
val headerHorizontalInset = 12
|
|
2904
|
-
addView(mainColumn, weighted())
|
|
2905
|
-
showText(title, item.json.optString("title"), 1)
|
|
2906
|
-
if (hasDottedTitle) {
|
|
2907
|
-
title.showsDottedUnderline = true
|
|
2908
|
-
title.dottedUnderlineColor = color(theme, "secondaryText", "#0000009B")
|
|
2909
|
-
title.setPadding(0, 0, 0, dp(3))
|
|
2910
|
-
}
|
|
2911
|
-
title.setTextColor(
|
|
2912
|
-
color(
|
|
2913
|
-
theme,
|
|
2914
|
-
if (isSummary || isGallery) "primaryText" else "secondaryText",
|
|
2915
|
-
if (isSummary || isGallery) "#000000DF" else "#0000009B",
|
|
2916
|
-
),
|
|
2917
|
-
)
|
|
2918
|
-
if (isNetworkSelector) {
|
|
2919
|
-
val verticalInset = if (isExplicitNetworkHeader && item.json.optString("titleActionKey").isEmpty()) 8 else 12
|
|
2920
|
-
setPadding(dp(headerHorizontalInset), dp(verticalInset), dp(headerHorizontalInset), dp(verticalInset))
|
|
2921
|
-
title.textSize = sp(14f)
|
|
2922
|
-
title.typeface = NativeListFonts.medium(context)
|
|
2923
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
2924
|
-
} else if (isHistory) {
|
|
2925
|
-
setPadding(0, 0, 0, 0)
|
|
2926
|
-
title.text = item.json.optString("title").uppercase()
|
|
2927
|
-
title.textSize = sp(12f)
|
|
2928
|
-
title.typeface = NativeListFonts.semibold(context)
|
|
2929
|
-
title.letterSpacing = 0.8f / sp(12f)
|
|
2930
|
-
TextViewCompat.setLineHeight(title, dp(16))
|
|
2931
|
-
} else if (isTokenManager) {
|
|
2932
|
-
setPadding(dp(headerHorizontalInset), dp(10), dp(headerHorizontalInset), 0)
|
|
2933
|
-
title.textSize = sp(14f)
|
|
2934
|
-
title.typeface = NativeListFonts.regular(context)
|
|
2935
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
2936
|
-
} else if (!isTable && !isGallery && !isSummary) {
|
|
2937
|
-
// SectionList.SectionHeader: h=36, px=20, headingSm 14/20 semibold.
|
|
2938
|
-
setPadding(dp(headerHorizontalInset), dp(8), dp(headerHorizontalInset), dp(8))
|
|
2939
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
2940
|
-
} else if (isTable) {
|
|
2941
|
-
setPadding(dp(16), dp(12), dp(16), dp(2))
|
|
2942
|
-
title.includeFontPadding = false
|
|
2943
|
-
title.layoutParams = wrap()
|
|
2944
|
-
titleLine.layoutParams = wrap()
|
|
2945
|
-
item.json.optJSONObject("titleIcon")?.let { icon ->
|
|
2946
|
-
headerTitleIcon.visibility = VISIBLE
|
|
2947
|
-
headerTitleIcon.iconName = icon.optString("name")
|
|
2948
|
-
headerTitleIcon.tintColor = safeColor(
|
|
2949
|
-
icon.optString("tintColor"),
|
|
2950
|
-
color(theme, "iconSubdued", "#00000072"),
|
|
2951
|
-
)
|
|
2952
|
-
titleLine.addView(headerTitleIcon, LayoutParams(dp(12), dp(12)).apply {
|
|
2953
|
-
marginStart = dp(4)
|
|
2954
|
-
})
|
|
2955
|
-
if (icon.optString("name") != "ChevronGrabberVerOutline") {
|
|
2956
|
-
title.setTextColor(color(theme, "primaryText", "#000000DF"))
|
|
2957
|
-
}
|
|
2958
|
-
}
|
|
2959
|
-
}
|
|
2960
|
-
showText(subtitle, item.json.optString("subtitle"), 1)
|
|
2961
|
-
if (isGallery) {
|
|
2962
|
-
setPadding(dp(12), 0, dp(12), dp(8))
|
|
2963
|
-
} else if (isSummary) {
|
|
2964
|
-
addView(trailingColumn, wrap())
|
|
2965
|
-
// NetworkListHeader: outer mt=16/pb=12, inner XStack px=20/py=8.
|
|
2966
|
-
setPadding(dp(headerHorizontalInset), dp(24), dp(headerHorizontalInset), dp(20))
|
|
2967
|
-
showTrailing(
|
|
2968
|
-
0,
|
|
2969
|
-
item.json.optString("value"),
|
|
2970
|
-
true,
|
|
2971
|
-
item.json.optString("valueActionKey"),
|
|
2972
|
-
color(theme, "secondaryText", "#0000009B"),
|
|
2973
|
-
)
|
|
2974
|
-
trailingViews[0].setTag(com.facebook.react.R.id.react_test_id, item.json.optString("valueActionTestID").takeIf { it.isNotEmpty() })
|
|
2975
|
-
trailingViews[0].accessibilityDelegate = selectorAccessibilityDelegate
|
|
2976
|
-
trailingViews[0].textSize = sp(16f)
|
|
2977
|
-
trailingViews[0].typeface = NativeListFonts.medium(context)
|
|
2978
|
-
TextViewCompat.setLineHeight(trailingViews[0], dp(24))
|
|
2979
|
-
// OneKey patch: the migrated media button shares the original 24-point text box.
|
|
2980
|
-
if (isExplicitNetworkHeader) trailingViews[0].setPadding(0, 0, 0, 0)
|
|
2981
|
-
else trailingViews[0].setPadding(dp(14), dp(6), dp(14), dp(6))
|
|
2982
|
-
trailingViews[0].layoutParams = wrap()
|
|
2983
|
-
} else if (!isGallery && !isHistory && !isTokenManager) {
|
|
2984
|
-
val value = item.json.optString("value")
|
|
2985
|
-
val checkboxData = item.json.optJSONObject("checkbox")
|
|
2986
|
-
if (isExplicitNetworkHeader && checkboxData != null) {
|
|
2987
|
-
// OneKey patch: the asset section title reserves its original 8dp trailing margin.
|
|
2988
|
-
(mainColumn.layoutParams as LayoutParams).marginEnd = dp(8)
|
|
2989
|
-
}
|
|
2990
|
-
if (checkboxData != null && value.isNotEmpty()) {
|
|
2991
|
-
// Value and checkbox share the trailing edge as one compound accessory.
|
|
2992
|
-
trailingColumn.orientation = HORIZONTAL
|
|
2993
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
2994
|
-
trailingViews[0].layoutParams = wrap()
|
|
2995
|
-
checkbox.layoutParams = LayoutParams(dp(20), dp(20)).apply { marginStart = dp(12) }
|
|
2996
|
-
}
|
|
2997
|
-
addView(trailingColumn, wrap())
|
|
2998
|
-
showTrailing(0, value, true)
|
|
2999
|
-
if (isTable) {
|
|
3000
|
-
trailingColumn.orientation = HORIZONTAL
|
|
3001
|
-
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
3002
|
-
trailingViews[0].includeFontPadding = false
|
|
3003
|
-
trailingViews[0].textSize = sp(11f)
|
|
3004
|
-
trailingViews[0].typeface = NativeListFonts.regular(context)
|
|
3005
|
-
trailingViews[0].setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
3006
|
-
item.json.optJSONObject("valueIcon")?.let { icon ->
|
|
3007
|
-
headerValueIcon.visibility = VISIBLE
|
|
3008
|
-
headerValueIcon.iconName = icon.optString("name")
|
|
3009
|
-
headerValueIcon.tintColor = safeColor(
|
|
3010
|
-
icon.optString("tintColor"),
|
|
3011
|
-
color(theme, "iconSubdued", "#00000072"),
|
|
3012
|
-
)
|
|
3013
|
-
trailingColumn.addView(
|
|
3014
|
-
headerValueIcon,
|
|
3015
|
-
LayoutParams(dp(12), dp(12)).apply { marginStart = dp(4) },
|
|
3016
|
-
)
|
|
3017
|
-
if (icon.optString("name") != "ChevronGrabberVerOutline") {
|
|
3018
|
-
trailingViews[0].setTextColor(color(theme, "primaryText", "#000000DF"))
|
|
3019
|
-
}
|
|
3020
|
-
}
|
|
3021
|
-
}
|
|
3022
|
-
checkboxData?.let { bindCheckbox(item, it, checkboxState) }
|
|
3023
|
-
}
|
|
3024
|
-
applyValueSegments(trailingViews[0], item.json.optJSONArray("valueSegments"))
|
|
3025
|
-
}
|
|
3026
|
-
|
|
3027
|
-
// OneKey patch: preserve compact zero-count digits without changing their baseline.
|
|
3028
|
-
private fun applyValueSegments(view: TextView, segments: JSONArray?, fontSize: Int = 16, lineHeight: Int = 24, medium: Boolean = true) {
|
|
3029
|
-
if (segments == null || segments.length() == 0) return
|
|
3030
|
-
val value = SpannableStringBuilder()
|
|
3031
|
-
for (index in 0 until segments.length()) {
|
|
3032
|
-
val segment = segments.getJSONObject(index)
|
|
3033
|
-
val start = value.length
|
|
3034
|
-
value.append(segment.optString("text"))
|
|
3035
|
-
if (segment.optString("style") == "subscript") {
|
|
3036
|
-
val size = kotlin.math.ceil(fontSize * 0.6).toFloat()
|
|
3037
|
-
val span = if (selectorUsesSourceScale) AbsoluteSizeSpan(kotlin.math.ceil((size * resources.displayMetrics.density).toDouble()).toInt(), false) else AbsoluteSizeSpan(sp(size).roundToInt(), true)
|
|
3038
|
-
value.setSpan(span, start, value.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
|
|
3039
|
-
}
|
|
3040
|
-
}
|
|
3041
|
-
view.textSize = sp(fontSize.toFloat())
|
|
3042
|
-
view.typeface = if (medium) NativeListFonts.medium(context) else NativeListFonts.regular(context)
|
|
3043
|
-
TextViewCompat.setLineHeight(view, dp(lineHeight))
|
|
3044
|
-
view.text = value
|
|
3045
|
-
}
|
|
3046
|
-
|
|
3047
|
-
private fun bindAction(
|
|
3048
|
-
item: NativeListItem,
|
|
3049
|
-
theme: JSONObject?,
|
|
3050
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
3051
|
-
) {
|
|
3052
|
-
val isAccountSelector = item.json.optString("presentation") == "accountSelector"
|
|
3053
|
-
item.json.optJSONObject("icon")?.let { icon ->
|
|
3054
|
-
addLeading(icon, if (isAccountSelector) 32 else 40)
|
|
3055
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(24), dp(24), Gravity.CENTER)
|
|
3056
|
-
if (!icon.has("backgroundColor")) leadingFrame.background = null
|
|
3057
|
-
if (isAccountSelector) leadingFrame.background = roundedFill(safeColor(icon.optString("backgroundColor"), color(theme, "strongBackground", "#0000000F")), 8f)
|
|
3058
|
-
}
|
|
3059
|
-
addView(mainColumn, weighted())
|
|
3060
|
-
showText(title, item.json.optString("title"), 1)
|
|
3061
|
-
if (isAccountSelector) {
|
|
3062
|
-
title.typeface = if (item.json.has("icon")) NativeListFonts.medium(context) else NativeListFonts.regular(context)
|
|
3063
|
-
title.setTextColor(color(theme, if (item.json.optString("tone") == "primary") "primaryText" else "secondaryText", "#0000009B"))
|
|
3064
|
-
} else if (item.json.optString("tone") == "danger") {
|
|
3065
|
-
title.setTextColor(color(theme, "negative", "#C40006D3"))
|
|
3066
|
-
}
|
|
3067
|
-
item.json.optJSONObject("checkbox")?.let {
|
|
3068
|
-
bindCheckbox(item, it, checkboxState)
|
|
3069
|
-
addView(trailingColumn, wrap())
|
|
3070
|
-
} ?: item.json.optJSONArray("trailing")?.let { accessories ->
|
|
3071
|
-
if (accessories.length() > 0) {
|
|
3072
|
-
addView(trailingColumn, wrap())
|
|
3073
|
-
bindAccessories(item, accessories, theme, checkboxState)
|
|
3074
|
-
}
|
|
3075
|
-
}
|
|
3076
|
-
setOnClickListener {
|
|
3077
|
-
onRowPress?.invoke(item, actionOrigin(this, "row"))
|
|
3078
|
-
}
|
|
3079
|
-
}
|
|
3080
|
-
|
|
3081
|
-
private fun bindSystem(item: NativeListItem, theme: JSONObject?) {
|
|
3082
|
-
val variant = item.json.optString("variant")
|
|
3083
|
-
val isMarket = item.json.optString("presentation") == "market"
|
|
3084
|
-
if (isMarket) setPadding(dp(20), dp(12), dp(20), dp(12))
|
|
3085
|
-
if (variant == "loading" && item.json.optString("loadingStyle") == "skeleton") {
|
|
3086
|
-
setPadding(dp(20), dp(12), dp(20), dp(12))
|
|
3087
|
-
addView(
|
|
3088
|
-
NativeListMarketSkeleton(context, color(theme, "background", "#FFFFFF")),
|
|
3089
|
-
LayoutParams(LayoutParams.MATCH_PARENT, dp(32)),
|
|
3090
|
-
)
|
|
3091
|
-
return
|
|
3092
|
-
}
|
|
3093
|
-
if (variant == "loading" && item.json.optString("loadingStyle") == "spinner") {
|
|
3094
|
-
gravity = Gravity.CENTER
|
|
3095
|
-
setPadding(0, dp(16), 0, dp(16))
|
|
3096
|
-
addView(
|
|
3097
|
-
ProgressBar(context, null, android.R.attr.progressBarStyleSmall).apply {
|
|
3098
|
-
isIndeterminate = true
|
|
3099
|
-
indeterminateTintList = android.content.res.ColorStateList.valueOf(color(theme, "icon", "#0000009B"))
|
|
3100
|
-
},
|
|
3101
|
-
LayoutParams(dp(20), dp(20)),
|
|
3102
|
-
)
|
|
3103
|
-
return
|
|
3104
|
-
}
|
|
3105
|
-
if (isMarket && variant == "retry") {
|
|
3106
|
-
val message = item.json.optString("message")
|
|
3107
|
-
orientation = VERTICAL
|
|
3108
|
-
gravity = Gravity.CENTER
|
|
3109
|
-
setPadding(dp(32), dp(if (message.isEmpty()) 11 else 32), dp(32), dp(if (message.isEmpty()) 11 else 27))
|
|
3110
|
-
if (message.isNotEmpty()) {
|
|
3111
|
-
showText(title, message, 2)
|
|
3112
|
-
val style = JSONObject().put("fontSize", 16).put("lineHeight", 24)
|
|
3113
|
-
applyMarketTextStyle(title, style, 16f, 24, "regular", color(theme, "secondaryText", "#0000009B"), "center")
|
|
3114
|
-
applyMarketTextMetrics(title, style)
|
|
3115
|
-
addView(mainColumn, wrap().apply { bottomMargin = kotlin.math.ceil(7.0 * resources.displayMetrics.density).toInt() })
|
|
3116
|
-
}
|
|
3117
|
-
showTrailing(0, item.json.optString("actionText", "Retry"), true, item.json.optString("actionKey"))
|
|
3118
|
-
val button = trailingViews[0]
|
|
3119
|
-
val style = JSONObject().put("fontSize", 14).put("lineHeight", 20)
|
|
3120
|
-
applyMarketTextStyle(button, style, 14f, 20, "medium", color(theme, "secondaryText", "#0000009B"), "center")
|
|
3121
|
-
applyMarketTextMetrics(button, style)
|
|
3122
|
-
button.background = null
|
|
3123
|
-
// Match Yoga's text rounding before adding the tertiary Button's border/padding.
|
|
3124
|
-
val density = resources.displayMetrics.density
|
|
3125
|
-
val buttonWidth = (kotlin.math.ceil(button.paint.measureText(button.text.toString()).toDouble()) + 18 * density).roundToInt()
|
|
3126
|
-
val buttonHeight = kotlin.math.ceil(kotlin.math.ceil(20.0 * density) + 10 * density).toInt()
|
|
3127
|
-
button.setPadding(0, 0, 0, 0)
|
|
3128
|
-
button.layoutParams = LayoutParams(buttonWidth, buttonHeight)
|
|
3129
|
-
addView(trailingColumn, wrap())
|
|
3130
|
-
return
|
|
3131
|
-
}
|
|
3132
|
-
if (isMarket && variant == "noMatch") {
|
|
3133
|
-
gravity = Gravity.CENTER
|
|
3134
|
-
setPadding(dp(32), dp(32), dp(32), dp(32))
|
|
3135
|
-
showText(title, item.json.optString("message"), 1)
|
|
3136
|
-
val style = JSONObject().put("fontSize", 16).put("lineHeight", 24)
|
|
3137
|
-
applyMarketTextStyle(title, style, 16f, 24, "regular", color(theme, "secondaryText", "#0000009B"), "center")
|
|
3138
|
-
applyMarketTextMetrics(title, style)
|
|
3139
|
-
addView(mainColumn, wrap())
|
|
3140
|
-
return
|
|
3141
|
-
}
|
|
3142
|
-
// OneKey patch: warning title/description wrap inside the actual scroll content.
|
|
3143
|
-
if (variant == "warning") {
|
|
3144
|
-
setPadding(dp(12), dp(14), dp(12), dp(14))
|
|
3145
|
-
addView(mainColumn, weighted())
|
|
3146
|
-
showText(title, item.json.optString("title"), Int.MAX_VALUE)
|
|
3147
|
-
showText(subtitle, item.json.optString("message"), Int.MAX_VALUE)
|
|
3148
|
-
title.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
3149
|
-
subtitle.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply { topMargin = dp(4) }
|
|
3150
|
-
titleLine.packsChildrenAtStart = false
|
|
3151
|
-
return
|
|
3152
|
-
}
|
|
3153
|
-
if (variant == "spacer") {
|
|
3154
|
-
minimumHeight = dp(item.json.optInt("height", 0))
|
|
3155
|
-
return
|
|
3156
|
-
}
|
|
3157
|
-
if (variant == "end") {
|
|
3158
|
-
// components/ListEndIndicator: py=16, gap=8, 80x1 lines and a 4dp dot.
|
|
3159
|
-
gravity = Gravity.CENTER
|
|
3160
|
-
setPadding(0, dp(16), 0, dp(16))
|
|
3161
|
-
val indicatorColor = color(theme, "separator", "#0000001F")
|
|
3162
|
-
addView(View(context).apply { setBackgroundColor(indicatorColor) }, LayoutParams(dp(80), 1))
|
|
3163
|
-
addView(
|
|
3164
|
-
View(context).apply { background = roundedFill(indicatorColor, 2f) },
|
|
3165
|
-
LayoutParams(dp(4), dp(4)).apply {
|
|
3166
|
-
marginStart = dp(8)
|
|
3167
|
-
marginEnd = dp(8)
|
|
3168
|
-
},
|
|
3169
|
-
)
|
|
3170
|
-
addView(View(context).apply { setBackgroundColor(indicatorColor) }, LayoutParams(dp(80), 1))
|
|
3171
|
-
return
|
|
3172
|
-
}
|
|
3173
|
-
gravity = Gravity.CENTER
|
|
3174
|
-
if (variant == "loading") {
|
|
3175
|
-
addLeading(JSONObject().put("kind", "skeleton"), if (isMarket) 32 else 40)
|
|
3176
|
-
leadingFallback.text = ""
|
|
3177
|
-
leadingFallback.background = roundedFill(
|
|
3178
|
-
color(theme, "strongBackground", "#0000000F"),
|
|
3179
|
-
if (isMarket) 16f else 20f,
|
|
3180
|
-
)
|
|
3181
|
-
addSkeleton(skeletonPrimary, 120, 12, theme, bottomMarginDp = 8)
|
|
3182
|
-
addSkeleton(skeletonSecondary, 80, 12, theme)
|
|
3183
|
-
}
|
|
3184
|
-
showText(title, item.json.optString("message"), 2)
|
|
3185
|
-
title.gravity = if (variant == "retry" || variant == "noMatch") {
|
|
3186
|
-
Gravity.START
|
|
3187
|
-
} else {
|
|
3188
|
-
Gravity.CENTER
|
|
3189
|
-
}
|
|
3190
|
-
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
3191
|
-
if (variant == "noMatch") {
|
|
3192
|
-
setPadding(dp(12), 0, dp(12), 0)
|
|
3193
|
-
title.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
3194
|
-
} else if (variant == "retry") {
|
|
3195
|
-
setPadding(dp(12), dp(8), dp(12), dp(8))
|
|
3196
|
-
title.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
3197
|
-
}
|
|
3198
|
-
addView(
|
|
3199
|
-
mainColumn,
|
|
3200
|
-
if (variant == "retry" || variant == "noMatch") weighted() else wrap(),
|
|
3201
|
-
)
|
|
3202
|
-
if (variant == "retry") {
|
|
3203
|
-
setOnClickListener {
|
|
3204
|
-
onRowPress?.invoke(item, actionOrigin(this, "row"))
|
|
3205
|
-
}
|
|
3206
|
-
showTrailing(0, "Retry", true, item.json.optString("actionKey"))
|
|
3207
|
-
trailingViews[0].textSize = sp(14f)
|
|
3208
|
-
trailingViews[0].background = roundedFill(color(theme, "strongBackground", "#0000000F"), 16f)
|
|
3209
|
-
trailingViews[0].setPadding(dp(10), dp(4), dp(10), dp(4))
|
|
3210
|
-
TextViewCompat.setLineHeight(trailingViews[0], dp(20))
|
|
3211
|
-
addView(trailingColumn, wrap().apply { marginStart = dp(12) })
|
|
3212
|
-
}
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
private fun addLeading(
|
|
3216
|
-
visual: JSONObject?,
|
|
3217
|
-
sizeDp: Int = 40,
|
|
3218
|
-
secondaryVisual: JSONObject? = null,
|
|
3219
|
-
spacingDp: Int = 12,
|
|
3220
|
-
heightDp: Int = sizeDp,
|
|
3221
|
-
cornerRadiusDp: Float? = null,
|
|
3222
|
-
) {
|
|
3223
|
-
leadingFrame.visibility = VISIBLE
|
|
3224
|
-
leadingFrame.layoutParams = LayoutParams(dp(sizeDp), dp(heightDp)).apply {
|
|
3225
|
-
val item = tag as? NativeListItem
|
|
3226
|
-
// OneKey patch: Yoga rounds cumulative selector edges, not each 12dp gap separately.
|
|
3227
|
-
marginEnd = if (item?.json?.has("height") == true && item.json.optString("presentation") in setOf("accountSelector", "networkSelector")) dp(12 + sizeDp + spacingDp) - dp(12) - dp(sizeDp) else dp(spacingDp)
|
|
3228
|
-
}
|
|
3229
|
-
addView(leadingFrame)
|
|
3230
|
-
leadingFallback.layoutParams = FrameLayout.LayoutParams(dp(sizeDp), dp(heightDp))
|
|
3231
|
-
if (visual == null) return
|
|
3232
|
-
val kind = visual.optString("kind")
|
|
3233
|
-
val shape = visual.optString(
|
|
3234
|
-
"shape",
|
|
3235
|
-
if (kind == "image" || isMediaTile) "rounded" else "circle",
|
|
3236
|
-
)
|
|
3237
|
-
val sources = visualSources(visual).toMutableList()
|
|
3238
|
-
secondaryVisual?.let { sources.addAll(visualSources(it).take(1)) }
|
|
3239
|
-
val isIcon = kind == "icon"
|
|
3240
|
-
val cornerIconData = visual.optJSONObject("cornerIcon")
|
|
3241
|
-
val fallback = visual.optString("fallbackText").take(2)
|
|
3242
|
-
val fallbackIconData = visual.optJSONObject("fallbackIcon")
|
|
3243
|
-
val fallbackIconSizeDp = fallbackIconData?.let {
|
|
3244
|
-
val slotSizeDp = minOf(sizeDp, heightDp)
|
|
3245
|
-
if (it.optString("name") == "GlobusOutline") {
|
|
3246
|
-
(slotSizeDp * 1.2f).roundToInt()
|
|
3247
|
-
} else {
|
|
3248
|
-
slotSizeDp
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
val sourceLoadingStrategy = sources.firstOrNull()?.first?.optString("loadingStrategy", "none") ?: "none"
|
|
3252
|
-
val showsSourcePlaceholder = !isIcon && sources.isNotEmpty() && sourceLoadingStrategy != "none"
|
|
3253
|
-
val handlesSourceFallback =
|
|
3254
|
-
!isIcon && sources.isNotEmpty() &&
|
|
3255
|
-
showsSourcePlaceholder &&
|
|
3256
|
-
(visual.has("fallbackText") || fallbackIconData != null)
|
|
3257
|
-
val sourceFallbackKey = if (handlesSourceFallback) {
|
|
3258
|
-
sources.firstOrNull()?.first?.let(::sourceFallbackStateKey)
|
|
3259
|
-
} else null
|
|
3260
|
-
val restoresSourceFallback =
|
|
3261
|
-
sourceFallbackKey?.let(NativeListSourceFallbackState::has) == true
|
|
3262
|
-
leadingFallback.text =
|
|
3263
|
-
if (handlesSourceFallback && !restoresSourceFallback) "" else fallback
|
|
3264
|
-
leadingFallback.setTextColor(parseNativeListColor("#00000072"))
|
|
3265
|
-
val visualBackground = safeColor(
|
|
3266
|
-
visual.optString("backgroundColor"),
|
|
3267
|
-
// OneKey patch: source-backed visuals default to no placeholder/background.
|
|
3268
|
-
if (!isIcon && sources.isNotEmpty()) Color.TRANSPARENT else parseNativeListColor(imagePlaceholderColor),
|
|
3269
|
-
)
|
|
3270
|
-
if (!isIcon && visual.optString("backgroundColor").isNotEmpty()) {
|
|
3271
|
-
leadingFrame.background = roundedFill(
|
|
3272
|
-
visualBackground,
|
|
3273
|
-
cornerRadiusDp ?: leadingCornerRadius(shape, minOf(sizeDp, heightDp)),
|
|
3274
|
-
)
|
|
3275
|
-
}
|
|
3276
|
-
leadingFallback.background = roundedFill(
|
|
3277
|
-
if (handlesSourceFallback) parseNativeListColor(imagePlaceholderColor) else visualBackground,
|
|
3278
|
-
cornerRadiusDp ?: leadingCornerRadius(shape, minOf(sizeDp, heightDp)),
|
|
3279
|
-
)
|
|
3280
|
-
leadingFallback.visibility =
|
|
3281
|
-
if (!isIcon && (sources.isEmpty() || handlesSourceFallback)) VISIBLE else GONE
|
|
3282
|
-
if (handlesSourceFallback && fallbackIconData != null) {
|
|
3283
|
-
leadingIcon.iconName = fallbackIconData.optString("name")
|
|
3284
|
-
leadingIcon.tintColor = safeColor(
|
|
3285
|
-
fallbackIconData.optString("tintColor"),
|
|
3286
|
-
parseNativeListColor("#0000009B"),
|
|
3287
|
-
)
|
|
3288
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(
|
|
3289
|
-
dp(fallbackIconSizeDp ?: sizeDp),
|
|
3290
|
-
dp(fallbackIconSizeDp ?: heightDp),
|
|
3291
|
-
Gravity.CENTER,
|
|
3292
|
-
)
|
|
3293
|
-
leadingIcon.glyphSizeDp = fallbackIconSizeDp
|
|
3294
|
-
// Keep the fallback measured while the source loads so an asynchronous
|
|
3295
|
-
// failure can reveal it without waiting for another RecyclerView layout.
|
|
3296
|
-
leadingFallback.visibility = if (restoresSourceFallback) GONE else VISIBLE
|
|
3297
|
-
leadingIcon.visibility = if (restoresSourceFallback) VISIBLE else INVISIBLE
|
|
3298
|
-
}
|
|
3299
|
-
if (isIcon) {
|
|
3300
|
-
leadingFrame.background = GradientDrawable().apply {
|
|
3301
|
-
setColor(visualBackground)
|
|
3302
|
-
setStroke(1, parseNativeListColor("#0000001F"))
|
|
3303
|
-
cornerRadius = scaledDp(cornerRadiusDp ?: leadingCornerRadius(shape, minOf(sizeDp, heightDp)))
|
|
3304
|
-
}
|
|
3305
|
-
leadingIcon.iconName = visual.optString("name")
|
|
3306
|
-
leadingIcon.tintColor = safeColor(
|
|
3307
|
-
visual.optString("tintColor"),
|
|
3308
|
-
parseNativeListColor("#0000009B"),
|
|
3309
|
-
)
|
|
3310
|
-
leadingIcon.visibility = VISIBLE
|
|
3311
|
-
} else if (sources.isEmpty() && fallbackIconData != null) {
|
|
3312
|
-
leadingFallback.visibility = GONE
|
|
3313
|
-
leadingIcon.iconName = fallbackIconData.optString("name")
|
|
3314
|
-
leadingIcon.tintColor = safeColor(
|
|
3315
|
-
fallbackIconData.optString("tintColor"),
|
|
3316
|
-
parseNativeListColor("#0000009B"),
|
|
3317
|
-
)
|
|
3318
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(
|
|
3319
|
-
dp(fallbackIconSizeDp ?: sizeDp),
|
|
3320
|
-
dp(fallbackIconSizeDp ?: heightDp),
|
|
3321
|
-
Gravity.CENTER,
|
|
3322
|
-
)
|
|
3323
|
-
leadingIcon.glyphSizeDp = fallbackIconSizeDp
|
|
3324
|
-
leadingIcon.visibility = VISIBLE
|
|
3325
|
-
}
|
|
3326
|
-
val visibleSources = sources.take(leadingImages.size)
|
|
3327
|
-
val tokenPair = kind == "token" && visibleSources.size > 1
|
|
3328
|
-
val hasAccountSelectorBadge =
|
|
3329
|
-
kind == "account" &&
|
|
3330
|
-
(tag as? NativeListItem)?.json?.optString("presentation") == "accountSelector" &&
|
|
3331
|
-
(visual.optJSONArray("overlays")?.length() ?: 0) > 0
|
|
3332
|
-
if (tokenPair || hasAccountSelectorBadge) {
|
|
3333
|
-
// Network badges intentionally extend past the avatar frame.
|
|
3334
|
-
clipChildren = false
|
|
3335
|
-
}
|
|
3336
|
-
if (tokenPair) {
|
|
3337
|
-
leadingOverlayBackground.visibility = VISIBLE
|
|
3338
|
-
leadingOverlayBackground.background = roundedFill(visualBackdropColor, 10f)
|
|
3339
|
-
val item = tag as? NativeListItem
|
|
3340
|
-
val marketPadding = if (item?.type == "market") item.json.optJSONObject("style")?.optDouble("horizontalPadding", 20.0) ?: 20.0 else null
|
|
3341
|
-
val density = resources.displayMetrics.density
|
|
3342
|
-
// Yoga rounds the absolute horizontal edges, including the avatar's fractional origin.
|
|
3343
|
-
val badgeRight = marketPadding?.let { ((it + sizeDp + 4) * density).roundToInt() }
|
|
3344
|
-
val badgeLeft = marketPadding?.let { ((it + sizeDp - 16) * density).roundToInt() }
|
|
3345
|
-
val avatarRight = marketPadding?.let { ((it + sizeDp) * density).roundToInt() }
|
|
3346
|
-
leadingOverlayBackground.layoutParams = FrameLayout.LayoutParams(
|
|
3347
|
-
if (badgeRight != null && badgeLeft != null) badgeRight - badgeLeft else dp(20),
|
|
3348
|
-
dp(20),
|
|
3349
|
-
Gravity.END or Gravity.BOTTOM,
|
|
3350
|
-
).apply {
|
|
3351
|
-
marginEnd = if (badgeRight != null && avatarRight != null) avatarRight - badgeRight else -dp(4)
|
|
3352
|
-
bottomMargin = -dp(4)
|
|
3353
|
-
}
|
|
3354
|
-
}
|
|
3355
|
-
if (cornerIconData != null) {
|
|
3356
|
-
leadingCornerIconFrame.visibility = VISIBLE
|
|
3357
|
-
leadingCornerIconFrame.background = roundedFill(
|
|
3358
|
-
safeColor(cornerIconData.optString("backgroundColor"), visualBackdropColor),
|
|
3359
|
-
10f,
|
|
3360
|
-
)
|
|
3361
|
-
leadingCornerIconFrame.layoutParams = FrameLayout.LayoutParams(
|
|
3362
|
-
dp(20),
|
|
3363
|
-
dp(20),
|
|
3364
|
-
Gravity.END or Gravity.BOTTOM,
|
|
3365
|
-
).apply {
|
|
3366
|
-
marginEnd = -dp(4)
|
|
3367
|
-
bottomMargin = -dp(4)
|
|
3368
|
-
}
|
|
3369
|
-
leadingCornerIcon.iconName = cornerIconData.optString("name")
|
|
3370
|
-
leadingCornerIcon.tintColor = safeColor(
|
|
3371
|
-
cornerIconData.optString("tintColor"),
|
|
3372
|
-
parseNativeListColor("#0000009B"),
|
|
3373
|
-
)
|
|
3374
|
-
}
|
|
3375
|
-
visibleSources.forEachIndexed { index, (source, variant) ->
|
|
3376
|
-
val image = leadingImages[index]
|
|
3377
|
-
// OneKey patch: Decorative badges and source-owned fallbacks stay hidden until loaded.
|
|
3378
|
-
// image.visibility = VISIBLE
|
|
3379
|
-
val ownsSourceFallback = index == 0 && handlesSourceFallback
|
|
3380
|
-
image.visibility = if (index > 0 || ownsSourceFallback) INVISIBLE else VISIBLE
|
|
3381
|
-
image.layoutParams = leadingImageLayout(
|
|
3382
|
-
index = index,
|
|
3383
|
-
count = visibleSources.size,
|
|
3384
|
-
sizeDp = sizeDp,
|
|
3385
|
-
heightDp = heightDp,
|
|
3386
|
-
tokenPair = tokenPair,
|
|
3387
|
-
)
|
|
3388
|
-
image.outlineProvider = when {
|
|
3389
|
-
tokenPair && index == 1 -> circleOutlineProvider
|
|
3390
|
-
cornerRadiusDp != null -> roundedOutlineProvider(cornerRadiusDp)
|
|
3391
|
-
else -> leadingOutlineProvider(shape)
|
|
3392
|
-
}
|
|
3393
|
-
image.clipToOutline = true
|
|
3394
|
-
if ((tag as? NativeListItem)?.type == "market" && index == 0 && visual.optString("borderColor").isNotEmpty()) {
|
|
3395
|
-
val inset = dp(1)
|
|
3396
|
-
val density = resources.displayMetrics.density
|
|
3397
|
-
val sourceRadius = cornerRadiusDp ?: leadingCornerRadius(shape, minOf(sizeDp, heightDp))
|
|
3398
|
-
// OneKey patch: retain the source's fractional border and background rendering.
|
|
3399
|
-
leadingFrame.background = null
|
|
3400
|
-
BackgroundStyleApplicator.setBackgroundColor(leadingFrame, visualBackground)
|
|
3401
|
-
// Match the source's physical edges: React Native rasterizes ALL as a different stroke path.
|
|
3402
|
-
for (edge in arrayOf(LogicalEdge.LEFT, LogicalEdge.TOP, LogicalEdge.RIGHT, LogicalEdge.BOTTOM)) {
|
|
3403
|
-
BackgroundStyleApplicator.setBorderWidth(leadingFrame, edge, 1f)
|
|
3404
|
-
BackgroundStyleApplicator.setBorderColor(leadingFrame, edge, safeColor(visual.optString("borderColor"), Color.TRANSPARENT))
|
|
3405
|
-
}
|
|
3406
|
-
BackgroundStyleApplicator.setBorderRadius(leadingFrame, BorderRadiusProp.BORDER_RADIUS, LengthPercentage(sourceRadius, LengthPercentageType.POINT))
|
|
3407
|
-
// OneKey patch: round the source image's absolute edges inside its border.
|
|
3408
|
-
val sourceLeft = ((tag as? NativeListItem)?.json?.optJSONObject("style")?.optDouble("horizontalPadding", 20.0) ?: 20.0) * density
|
|
3409
|
-
val imageWidth = (sourceLeft + (sizeDp - 1) * density).roundToInt() - (sourceLeft + density).roundToInt()
|
|
3410
|
-
image.layoutParams = FrameLayout.LayoutParams(imageWidth, dp(heightDp - 2)).apply {
|
|
3411
|
-
leftMargin = inset
|
|
3412
|
-
topMargin = inset
|
|
3413
|
-
}
|
|
3414
|
-
marketLeadingUsesSourceClip = true
|
|
3415
|
-
image.clipToOutline = false
|
|
3416
|
-
}
|
|
3417
|
-
val fallbackIcon = if (index == 0) fallbackIconData else null
|
|
3418
|
-
val expectedEpoch = bindingEpoch
|
|
3419
|
-
bindImage(source, image, boundKey ?: "", index, variant,
|
|
3420
|
-
round = shape == "circle",
|
|
3421
|
-
onLoad = if (!ownsSourceFallback) null else ({
|
|
3422
|
-
if (bindingEpoch == expectedEpoch) {
|
|
3423
|
-
sourceFallbackKey?.let(NativeListSourceFallbackState::forget)
|
|
3424
|
-
image.visibility = VISIBLE
|
|
3425
|
-
leadingFallback.visibility = GONE
|
|
3426
|
-
leadingIcon.visibility = GONE
|
|
3427
|
-
}
|
|
3428
|
-
}),
|
|
3429
|
-
onError = if (!ownsSourceFallback) null else ({
|
|
3430
|
-
if (bindingEpoch == expectedEpoch) {
|
|
3431
|
-
sourceFallbackKey?.let(NativeListSourceFallbackState::remember)
|
|
3432
|
-
image.visibility = GONE
|
|
3433
|
-
if (fallbackIcon != null) {
|
|
3434
|
-
leadingFallback.visibility = GONE
|
|
3435
|
-
leadingIcon.iconName = fallbackIcon.optString("name")
|
|
3436
|
-
leadingIcon.tintColor = safeColor(fallbackIcon.optString("tintColor"), parseNativeListColor("#0000009B"))
|
|
3437
|
-
leadingIcon.visibility = VISIBLE
|
|
3438
|
-
} else {
|
|
3439
|
-
leadingIcon.visibility = GONE
|
|
3440
|
-
leadingFallback.text = fallback
|
|
3441
|
-
leadingFallback.visibility = VISIBLE
|
|
3442
|
-
}
|
|
3443
|
-
}
|
|
3444
|
-
}),
|
|
3445
|
-
hideUntilLoaded = index > 0 || ownsSourceFallback,
|
|
3446
|
-
)
|
|
3447
|
-
}
|
|
3448
|
-
// OneKey patch: wallet overlays retain source images, provider colors and QR text.
|
|
3449
|
-
visual.optJSONArray("overlays")?.let { overlays ->
|
|
3450
|
-
for (index in 0 until overlays.length()) {
|
|
3451
|
-
val overlay = overlays.getJSONObject(index)
|
|
3452
|
-
val size = overlay.optInt("size", 20)
|
|
3453
|
-
val inset = dp(overlay.optInt("padding", 0))
|
|
3454
|
-
val isWalletText = selectorUsesSourceScale && (tag as? NativeListItem)?.json?.optString("presentation") == "walletSidebar" && overlay.optString("text").isNotEmpty() && overlay.optJSONObject("image") == null && overlay.optString("name").isEmpty()
|
|
3455
|
-
val width = overlay.optInt("width", size)
|
|
3456
|
-
val height = overlay.optInt("height", if (isWalletText) 16 else size)
|
|
3457
|
-
val offsetX = dp(overlay.optInt("offsetX", overlay.optInt("offset", 2)))
|
|
3458
|
-
val offsetY = dp(overlay.optInt("offsetY", overlay.optInt("offset", 2)))
|
|
3459
|
-
val frame = FrameLayout(context).apply {
|
|
3460
|
-
setPadding(if (isWalletText) dp(2) else inset, if (isWalletText) 0 else inset, if (isWalletText) dp(2) else inset, if (isWalletText) 0 else inset)
|
|
3461
|
-
background = roundedFill(safeColor(overlay.optString("backgroundColor"), Color.TRANSPARENT), minOf(width, height) / 2f)
|
|
3462
|
-
outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
3463
|
-
clipToOutline = true
|
|
3464
|
-
}
|
|
3465
|
-
val image = overlay.optJSONObject("image")
|
|
3466
|
-
val view = when {
|
|
3467
|
-
image != null -> (selectorImages.getOrNull(index) ?: OneKeyImageReusableView(reactContext).also(selectorImages::add)).also {
|
|
3468
|
-
bindImage(image, it, boundKey ?: "", 10 + index, "generic", hideUntilLoaded = true)
|
|
3469
|
-
}
|
|
3470
|
-
overlay.optString("text").isNotEmpty() -> TextView(context).apply {
|
|
3471
|
-
text = overlay.optString("text")
|
|
3472
|
-
textSize = sp(if (isWalletText) 12f else 10f)
|
|
3473
|
-
typeface = if (isWalletText) NativeListFonts.regular(context) else NativeListFonts.medium(context)
|
|
3474
|
-
includeFontPadding = false
|
|
3475
|
-
gravity = Gravity.CENTER
|
|
3476
|
-
if (isWalletText) TextViewCompat.setLineHeight(this, dp(16))
|
|
3477
|
-
setTextColor(safeColor(overlay.optString("tintColor"), color(null, "secondaryText", "#0000009B")))
|
|
3478
|
-
}
|
|
3479
|
-
else -> OneKeyIconView(context).apply {
|
|
3480
|
-
iconName = overlay.optString("name")
|
|
3481
|
-
tintColor = safeColor(overlay.optString("tintColor"), parseNativeListColor("#0000009B"))
|
|
3482
|
-
}
|
|
3483
|
-
}
|
|
3484
|
-
frame.addView(view, FrameLayout.LayoutParams(if (isWalletText && !overlay.has("width")) FrameLayout.LayoutParams.WRAP_CONTENT else FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT))
|
|
3485
|
-
val topLeft = overlay.optString("position") == "topLeft"
|
|
3486
|
-
leadingFrame.addView(frame, FrameLayout.LayoutParams(if (isWalletText && !overlay.has("width")) FrameLayout.LayoutParams.WRAP_CONTENT else dp(width), dp(height), if (topLeft) Gravity.START or Gravity.TOP else Gravity.END or Gravity.BOTTOM).apply {
|
|
3487
|
-
if (topLeft) { marginStart = -offsetX; topMargin = -offsetY } else { marginEnd = -offsetX; bottomMargin = -offsetY }
|
|
3488
|
-
})
|
|
3489
|
-
selectorViews.add(frame)
|
|
3490
|
-
}
|
|
3491
|
-
}
|
|
3492
|
-
visual.optJSONObject("fallbackIcon")?.takeIf { sources.isEmpty() }?.let { fallbackIcon ->
|
|
3493
|
-
leadingFallback.visibility = GONE
|
|
3494
|
-
leadingIcon.visibility = VISIBLE
|
|
3495
|
-
leadingIcon.iconName = fallbackIcon.optString("name")
|
|
3496
|
-
leadingIcon.tintColor = safeColor(fallbackIcon.optString("tintColor"), parseNativeListColor("#0000009B"))
|
|
3497
|
-
if (selectorUsesSourceScale && (tag as? NativeListItem)?.json?.optString("presentation") == "walletSidebar" && leadingIcon.iconName == "PlusSmallOutline") {
|
|
3498
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(24), dp(24), Gravity.CENTER)
|
|
3499
|
-
leadingIcon.glyphSizeDp = 24
|
|
3500
|
-
}
|
|
3501
|
-
if (selectorUsesSourceScale && (tag as? NativeListItem)?.json?.optString("presentation") == "walletSidebar" && leadingIcon.iconName == "LockSolid") {
|
|
3502
|
-
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(40), dp(40), Gravity.CENTER)
|
|
3503
|
-
leadingIcon.glyphSizeDp = 40
|
|
3504
|
-
}
|
|
3505
|
-
}
|
|
3506
|
-
if (visual.optString("borderStyle") == "dashed") {
|
|
3507
|
-
leadingFrame.background = GradientDrawable().apply {
|
|
3508
|
-
setColor(visualBackground)
|
|
3509
|
-
cornerRadius = dp(sizeDp / 2).toFloat()
|
|
3510
|
-
setStroke(dp(if (selectorUsesSourceScale && (tag as? NativeListItem)?.json?.optString("presentation") == "walletSidebar") 1 else 2), safeColor(visual.optString("borderColor"), parseNativeListColor("#00000072")), dp(4).toFloat(), dp(4).toFloat())
|
|
3511
|
-
}
|
|
3512
|
-
leadingFallback.background = null
|
|
3513
|
-
}
|
|
3514
|
-
}
|
|
3515
|
-
|
|
3516
|
-
private fun visualSources(visual: JSONObject): List<Pair<JSONObject, String>> {
|
|
3517
|
-
return when (val kind = visual.optString("kind")) {
|
|
3518
|
-
"stackedImages" -> {
|
|
3519
|
-
val images = visual.optJSONArray("images") ?: return emptyList()
|
|
3520
|
-
(0 until minOf(3, images.length())).mapNotNull { index ->
|
|
3521
|
-
images.optJSONObject(index)?.let { it to "generic" }
|
|
3522
|
-
}
|
|
3523
|
-
}
|
|
3524
|
-
"icon" -> emptyList()
|
|
3525
|
-
else -> buildList {
|
|
3526
|
-
visual.optJSONObject("image")?.let {
|
|
3527
|
-
val variant = when (kind) {
|
|
3528
|
-
"token" -> "token"
|
|
3529
|
-
"network" -> "network"
|
|
3530
|
-
"account", "wallet" -> "avatar"
|
|
3531
|
-
else -> "generic"
|
|
3532
|
-
}
|
|
3533
|
-
add(it to variant)
|
|
3534
|
-
}
|
|
3535
|
-
if (kind == "token") {
|
|
3536
|
-
visual.optJSONObject("networkImage")?.let { add(it to "network") }
|
|
3537
|
-
}
|
|
3538
|
-
}
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
|
|
3542
|
-
private fun sourceFallbackStateKey(source: JSONObject): String? =
|
|
3543
|
-
nativeListSourceFallbackStateKey(
|
|
3544
|
-
uri = source.optString("uri"),
|
|
3545
|
-
headers = source.optJSONObject("headers")?.let { headers ->
|
|
3546
|
-
headers.keys().asSequence().associateWith { headers.optString(it) }
|
|
3547
|
-
}.orEmpty(),
|
|
3548
|
-
)
|
|
3549
|
-
|
|
3550
|
-
private fun leadingImageLayout(
|
|
3551
|
-
index: Int,
|
|
3552
|
-
count: Int,
|
|
3553
|
-
sizeDp: Int,
|
|
3554
|
-
heightDp: Int,
|
|
3555
|
-
tokenPair: Boolean,
|
|
3556
|
-
): FrameLayout.LayoutParams {
|
|
3557
|
-
if (count == 1 || tokenPair && index == 0) {
|
|
3558
|
-
return FrameLayout.LayoutParams(dp(sizeDp), dp(heightDp))
|
|
3559
|
-
}
|
|
3560
|
-
if (tokenPair && index == 1) {
|
|
3561
|
-
return FrameLayout.LayoutParams(dp(16), dp(16), Gravity.END or Gravity.BOTTOM).apply {
|
|
3562
|
-
marginEnd = -dp(2)
|
|
3563
|
-
bottomMargin = -dp(2)
|
|
3564
|
-
}
|
|
3565
|
-
}
|
|
3566
|
-
val imageSize = (sizeDp * 0.72f).toInt()
|
|
3567
|
-
return FrameLayout.LayoutParams(dp(imageSize), dp(imageSize), Gravity.START or Gravity.CENTER_VERTICAL).apply {
|
|
3568
|
-
marginStart = dp(index * (sizeDp - imageSize) / maxOf(1, count - 1))
|
|
3569
|
-
}
|
|
3570
|
-
}
|
|
3571
|
-
|
|
3572
|
-
private fun bindAccessories(
|
|
3573
|
-
item: NativeListItem,
|
|
3574
|
-
accessories: JSONArray?,
|
|
3575
|
-
theme: JSONObject?,
|
|
3576
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
3577
|
-
) {
|
|
3578
|
-
if (accessories == null) return
|
|
3579
|
-
var textIndex = 0
|
|
3580
|
-
for (index in 0 until minOf(2, accessories.length())) {
|
|
3581
|
-
val accessory = accessories.getJSONObject(index)
|
|
3582
|
-
when (accessory.optString("kind")) {
|
|
3583
|
-
"value" -> {
|
|
3584
|
-
showTrailing(textIndex, accessory.optString("text"), !accessory.optBoolean("secondary", false))
|
|
3585
|
-
applyValueSegments(trailingViews[textIndex], accessory.optJSONArray("textSegments"))
|
|
3586
|
-
textIndex++
|
|
3587
|
-
}
|
|
3588
|
-
"valuePair" -> showTrailingValuePair(textIndex++, accessory, theme)
|
|
3589
|
-
"checkbox" -> bindCheckbox(item, accessory, checkboxState)
|
|
3590
|
-
"radio" -> showTrailing(
|
|
3591
|
-
textIndex++,
|
|
3592
|
-
if (accessory.optBoolean("checked")) "●" else "○",
|
|
3593
|
-
true,
|
|
3594
|
-
accessory.optString("actionKey").takeUnless { accessory.optBoolean("disabled", false) },
|
|
3595
|
-
)
|
|
3596
|
-
"switch" -> showTrailing(
|
|
3597
|
-
textIndex++,
|
|
3598
|
-
if (accessory.optBoolean("value")) "ON" else "OFF",
|
|
3599
|
-
true,
|
|
3600
|
-
accessory.optString("actionKey").takeUnless { accessory.optBoolean("disabled", false) },
|
|
3601
|
-
)
|
|
3602
|
-
"chevron" -> showTrailingIcon(
|
|
3603
|
-
textIndex++,
|
|
3604
|
-
item,
|
|
3605
|
-
JSONObject(accessory.toString()).apply {
|
|
3606
|
-
put("name", "ChevronRightSmallOutline")
|
|
3607
|
-
if (optString("actionKey").isEmpty()) put("actionKey", "press")
|
|
3608
|
-
},
|
|
3609
|
-
theme,
|
|
3610
|
-
)
|
|
3611
|
-
"menu" -> showTrailingMenu(textIndex++, accessory.optString("actionKey"))
|
|
3612
|
-
"drag" -> showTrailingIcon(
|
|
3613
|
-
textIndex++,
|
|
3614
|
-
item,
|
|
3615
|
-
JSONObject(accessory.toString()).apply { put("name", "DragOutline") },
|
|
3616
|
-
theme,
|
|
3617
|
-
)
|
|
3618
|
-
"icon" -> showTrailingIcon(textIndex++, item, accessory, theme)
|
|
3619
|
-
"spinner" -> spinner.visibility = VISIBLE
|
|
3620
|
-
"progress" -> showTrailing(textIndex++, "${(accessory.optDouble("value") * 100).toInt()}%", false)
|
|
3621
|
-
}
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
|
|
3625
|
-
private fun bindCheckbox(
|
|
3626
|
-
item: NativeListItem,
|
|
3627
|
-
json: JSONObject,
|
|
3628
|
-
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
3629
|
-
) {
|
|
3630
|
-
boundCheckboxData = json
|
|
3631
|
-
val targetJson = json.optJSONObject("target")
|
|
3632
|
-
val scope = targetJson?.optString("scope", "row") ?: "row"
|
|
3633
|
-
val target = NativeSelectionTarget(
|
|
3634
|
-
scope,
|
|
3635
|
-
when (scope) {
|
|
3636
|
-
"section" -> targetJson?.optString("sectionKey")
|
|
3637
|
-
"row" -> item.key
|
|
3638
|
-
else -> null
|
|
3639
|
-
},
|
|
3640
|
-
)
|
|
3641
|
-
val state = checkboxState(item, target, json.optString("state", "unchecked"))
|
|
3642
|
-
val accessoryDisabled = json.optBoolean("disabled", false)
|
|
3643
|
-
if (json.optBoolean("loading", false)) {
|
|
3644
|
-
checkbox.visibility = GONE
|
|
3645
|
-
checkbox.setOnClickListener(null)
|
|
3646
|
-
spinner.visibility = VISIBLE
|
|
3647
|
-
spinner.alpha = if (!item.json.optBoolean("disabled", false) && accessoryDisabled) 0.5f else 1f
|
|
3648
|
-
return
|
|
3649
|
-
}
|
|
3650
|
-
checkbox.visibility = VISIBLE
|
|
3651
|
-
checkbox.setState(state, checkboxIconColor)
|
|
3652
|
-
val usesSourceCheckboxGeometry = checkboxUsesSelectorStyle && item.json.has("height")
|
|
3653
|
-
checkbox.usesSelectorGeometry = usesSourceCheckboxGeometry
|
|
3654
|
-
if (usesSourceCheckboxGeometry) {
|
|
3655
|
-
// OneKey patch: source Yoga children may round into padding; retain their complete border.
|
|
3656
|
-
clipToPadding = false
|
|
3657
|
-
// OneKey patch: even a transparent source border changes RN's background clipping path.
|
|
3658
|
-
checkbox.background = null
|
|
3659
|
-
BackgroundStyleApplicator.setBackgroundColor(checkbox, if (state == "unchecked") checkboxUncheckedColor else checkboxCheckedColor)
|
|
3660
|
-
BackgroundStyleApplicator.setBorderWidth(checkbox, LogicalEdge.ALL, 2f)
|
|
3661
|
-
BackgroundStyleApplicator.setBorderColor(checkbox, LogicalEdge.ALL, if (state == "unchecked") checkboxBorderColor else Color.TRANSPARENT)
|
|
3662
|
-
BackgroundStyleApplicator.setBorderRadius(checkbox, BorderRadiusProp.BORDER_RADIUS, LengthPercentage(4f, LengthPercentageType.POINT))
|
|
3663
|
-
} else {
|
|
3664
|
-
checkbox.background = if (state == "unchecked") {
|
|
3665
|
-
if (checkboxUsesSelectorStyle) GradientDrawable().apply { setColor(checkboxUncheckedColor); setStroke(dp(2), checkboxBorderColor); cornerRadius = scaledDp(4f) }
|
|
3666
|
-
else roundedStroke(checkboxBorderColor, checkboxUncheckedColor, 4f)
|
|
3667
|
-
} else {
|
|
3668
|
-
roundedFill(checkboxCheckedColor, 4f)
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3671
|
-
// Row-level disabled opacity already applies to this child. Only apply a
|
|
3672
|
-
// local 0.5 when the accessory alone is disabled, never 0.5 * 0.5.
|
|
3673
|
-
checkbox.alpha = if (!item.json.optBoolean("disabled", false) && accessoryDisabled) 0.5f else 1f
|
|
3674
|
-
checkbox.isEnabled =
|
|
3675
|
-
!item.json.optBoolean("disabled", false) &&
|
|
3676
|
-
!accessoryDisabled &&
|
|
3677
|
-
!json.optBoolean("loading", false)
|
|
3678
|
-
checkbox.setOnClickListener {
|
|
3679
|
-
emitAction(
|
|
3680
|
-
item,
|
|
3681
|
-
json.optString("actionKey", "selection"),
|
|
3682
|
-
target,
|
|
3683
|
-
checkbox,
|
|
3684
|
-
"trailingAccessory",
|
|
3685
|
-
)
|
|
3686
|
-
}
|
|
3687
|
-
}
|
|
3688
|
-
|
|
3689
|
-
private fun applySelectionState(
|
|
3690
|
-
item: NativeListItem,
|
|
3691
|
-
theme: JSONObject?,
|
|
3692
|
-
layout: String,
|
|
3693
|
-
itemIndex: Int?,
|
|
3694
|
-
selected: Boolean,
|
|
3695
|
-
) {
|
|
3696
|
-
var rowBackground = color(
|
|
3697
|
-
theme,
|
|
3698
|
-
if (selected) "rowSelectedBackground" else "rowBackground",
|
|
3699
|
-
if (selected) "#0000000F" else "#FFFFFF",
|
|
3700
|
-
)
|
|
3701
|
-
if (item.type == "rail" || item.type == "mediaTile") {
|
|
3702
|
-
// The source FavoriteTokenItem has no active/resting fill, and the NFT
|
|
3703
|
-
// tile changes only the image opacity while pressed.
|
|
3704
|
-
rowBackground = color(theme, "rowBackground", "#FFFFFF")
|
|
3705
|
-
} else if (item.type == "metricCard" && !selected) {
|
|
3706
|
-
rowBackground = color(theme, "subduedBackground", "#F9F9F9")
|
|
3707
|
-
}
|
|
3708
|
-
val groupPosition = when {
|
|
3709
|
-
item.type == "identity" && item.json.optString("presentation") == "walletSidebar" -> "single"
|
|
3710
|
-
// OneKey patch: explicit selector rows preserve the v1 ListItem corner radius.
|
|
3711
|
-
item.type == "identity" && item.json.optString("presentation") in setOf("accountSelector", "networkSelector") && item.json.has("height") -> "single"
|
|
3712
|
-
else -> when (item.type) {
|
|
3713
|
-
"metricCard" -> "single"
|
|
3714
|
-
"rail" -> "rail"
|
|
3715
|
-
else -> item.json.optString("groupPosition")
|
|
3716
|
-
}
|
|
3717
|
-
}
|
|
3718
|
-
var backgroundGroupPosition = if (item.type == "mediaTile") "mediaTile" else groupPosition
|
|
3719
|
-
if (layout == "sectioned" && !item.json.optBoolean("selected", false)) {
|
|
3720
|
-
// Selection in sectioned lists is represented by the OneKey checkbox,
|
|
3721
|
-
// matching iOS and the app-monorepo network selector.
|
|
3722
|
-
rowBackground = color(theme, "rowBackground", "#FFFFFF")
|
|
3723
|
-
if (item.type == "sectionHeader") backgroundGroupPosition = ""
|
|
3724
|
-
} else if (
|
|
3725
|
-
layout == "table" &&
|
|
3726
|
-
item.type == "dataRow" &&
|
|
3727
|
-
(if (item.json.has("index")) item.json.optInt("index") else itemIndex)?.rem(2) == 0 &&
|
|
3728
|
-
!selected
|
|
3729
|
-
) {
|
|
3730
|
-
rowBackground = color(theme, "subduedBackground", "#F9F9F9")
|
|
3731
|
-
backgroundGroupPosition = ""
|
|
3732
|
-
}
|
|
3733
|
-
// OneKey patch: section heading backgrounds are independent of list rows.
|
|
3734
|
-
if (item.json.has("backgroundColor")) rowBackground = safeColor(item.json.optString("backgroundColor"), rowBackground)
|
|
3735
|
-
restingRowBackground = groupedBackground(backgroundGroupPosition, rowBackground)
|
|
3736
|
-
background = restingRowBackground
|
|
3737
|
-
}
|
|
3738
|
-
|
|
3739
|
-
private fun showText(view: TextView, value: String, lines: Int) {
|
|
3740
|
-
if (value.isEmpty()) return
|
|
3741
|
-
view.text = value
|
|
3742
|
-
view.maxLines = lines
|
|
3743
|
-
view.visibility = VISIBLE
|
|
3744
|
-
}
|
|
3745
|
-
|
|
3746
|
-
private fun showTrailing(
|
|
3747
|
-
index: Int,
|
|
3748
|
-
value: String,
|
|
3749
|
-
primary: Boolean,
|
|
3750
|
-
actionKey: String? = null,
|
|
3751
|
-
textColor: Int? = null,
|
|
3752
|
-
) {
|
|
3753
|
-
if (index !in trailingViews.indices || value.isEmpty()) return
|
|
3754
|
-
val view = trailingViews[index]
|
|
3755
|
-
view.maxLines = 1
|
|
3756
|
-
view.text = value
|
|
3757
|
-
view.textSize = sp(if (primary) 16f else 14f)
|
|
3758
|
-
view.typeface = if (primary) NativeListFonts.medium(context) else NativeListFonts.regular(context)
|
|
3759
|
-
view.includeFontPadding = false
|
|
3760
|
-
view.fontFeatureSettings = "tnum"
|
|
3761
|
-
TextViewCompat.setLineHeight(view, dp(if (primary) 24 else 20))
|
|
3762
|
-
textColor?.let(view::setTextColor)
|
|
3763
|
-
view.visibility = VISIBLE
|
|
3764
|
-
if (!actionKey.isNullOrEmpty()) {
|
|
3765
|
-
view.setOnClickListener {
|
|
3766
|
-
(tag as? NativeListItem)
|
|
3767
|
-
?.takeUnless { item -> item.json.optBoolean("disabled", false) }
|
|
3768
|
-
?.let { item ->
|
|
3769
|
-
emitAction(item, actionKey, null, view, "trailingAccessory", index)
|
|
3770
|
-
}
|
|
3771
|
-
}
|
|
3772
|
-
}
|
|
3773
|
-
}
|
|
3774
|
-
|
|
3775
|
-
private fun showTrailingValuePair(index: Int, accessory: JSONObject, theme: JSONObject?) {
|
|
3776
|
-
if (index !in trailingViews.indices) return
|
|
3777
|
-
val primary = accessory.optString("primary")
|
|
3778
|
-
val secondary = accessory.optString("secondary")
|
|
3779
|
-
if (primary.isEmpty() && secondary.isEmpty()) return
|
|
3780
|
-
val value = SpannableStringBuilder()
|
|
3781
|
-
val primaryStart = value.length
|
|
3782
|
-
value.append(primary)
|
|
3783
|
-
value.setSpan(
|
|
3784
|
-
ForegroundColorSpan(accessoryTextColor(accessory.optString("primaryTone"), "primary", theme)),
|
|
3785
|
-
primaryStart,
|
|
3786
|
-
value.length,
|
|
3787
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
3788
|
-
)
|
|
3789
|
-
value.setSpan(
|
|
3790
|
-
AbsoluteSizeSpan(
|
|
3791
|
-
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp(16f), resources.displayMetrics).roundToInt(),
|
|
3792
|
-
),
|
|
3793
|
-
primaryStart,
|
|
3794
|
-
value.length,
|
|
3795
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
3796
|
-
)
|
|
3797
|
-
if (primary.isNotEmpty() && secondary.isNotEmpty()) value.append('\n')
|
|
3798
|
-
val secondaryStart = value.length
|
|
3799
|
-
value.append(secondary)
|
|
3800
|
-
value.setSpan(
|
|
3801
|
-
ForegroundColorSpan(accessoryTextColor(accessory.optString("secondaryTone"), "secondary", theme)),
|
|
3802
|
-
secondaryStart,
|
|
3803
|
-
value.length,
|
|
3804
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
3805
|
-
)
|
|
3806
|
-
value.setSpan(
|
|
3807
|
-
AbsoluteSizeSpan(
|
|
3808
|
-
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp(14f), resources.displayMetrics).roundToInt(),
|
|
3809
|
-
),
|
|
3810
|
-
secondaryStart,
|
|
3811
|
-
value.length,
|
|
3812
|
-
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
3813
|
-
)
|
|
3814
|
-
val view = trailingViews[index]
|
|
3815
|
-
view.text = value
|
|
3816
|
-
view.maxLines = 2
|
|
3817
|
-
view.gravity = Gravity.END
|
|
3818
|
-
view.includeFontPadding = false
|
|
3819
|
-
view.typeface = NativeListFonts.regular(context)
|
|
3820
|
-
view.fontFeatureSettings = "tnum"
|
|
3821
|
-
TextViewCompat.setLineHeight(view, dp(20))
|
|
3822
|
-
view.visibility = VISIBLE
|
|
3823
|
-
}
|
|
3824
|
-
|
|
3825
|
-
private fun showTrailingMenu(index: Int, actionKey: String) {
|
|
3826
|
-
showTrailing(index, "⋮", true, actionKey)
|
|
3827
|
-
if (index !in trailingViews.indices) return
|
|
3828
|
-
trailingViews[index].apply {
|
|
3829
|
-
gravity = Gravity.CENTER
|
|
3830
|
-
textSize = sp(20f)
|
|
3831
|
-
layoutParams = LayoutParams(dp(24), dp(24)).apply {
|
|
3832
|
-
if (trailingColumn.orientation == HORIZONTAL && index > 0) marginStart = dp(8)
|
|
3833
|
-
}
|
|
3834
|
-
}
|
|
3835
|
-
}
|
|
3836
|
-
|
|
3837
|
-
private fun accessoryTextColor(tone: String, defaultTone: String, theme: JSONObject?): Int =
|
|
3838
|
-
when (tone.ifEmpty { defaultTone }) {
|
|
3839
|
-
"positive" -> color(theme, "positive", "#00713FDE")
|
|
3840
|
-
"negative" -> color(theme, "negative", "#C40006D3")
|
|
3841
|
-
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
3842
|
-
else -> color(theme, "primaryText", "#000000DF")
|
|
3843
|
-
}
|
|
3844
|
-
|
|
3845
|
-
private fun showTrailingIcon(index: Int, item: NativeListItem, data: JSONObject, theme: JSONObject?) {
|
|
3846
|
-
if (index !in trailingIcons.indices) return
|
|
3847
|
-
val icon = trailingIcons[index]
|
|
3848
|
-
icon.iconName = data.optString("name")
|
|
3849
|
-
icon.tintColor = safeColor(data.optString("tintColor"), iconSubduedColor)
|
|
3850
|
-
// OneKey patch: preserve the original 38dp press target around its 24dp layout slot.
|
|
3851
|
-
icon.setTag(com.facebook.react.R.id.react_test_id, data.optString("testID").takeIf { it.isNotEmpty() })
|
|
3852
|
-
icon.accessibilityDelegate = selectorAccessibilityDelegate
|
|
3853
|
-
icon.contentDescription = data.optString("accessibilityLabel").takeIf { it.isNotEmpty() }
|
|
3854
|
-
if (item.json.optString("presentation") == "accountSelector") {
|
|
3855
|
-
icon.glyphSizeDp = 24
|
|
3856
|
-
val isSourceMenu = item.json.has("height") && icon.iconName == "DotHorOutline"
|
|
3857
|
-
val size = if (item.json.has("height") && icon.iconName == "PlusSmallOutline") 36 else 38
|
|
3858
|
-
icon.layoutParams = LayoutParams(dp(size), dp(size)).apply {
|
|
3859
|
-
gravity = Gravity.CENTER_VERTICAL
|
|
3860
|
-
marginStart = -dp(7)
|
|
3861
|
-
marginEnd = -dp(7)
|
|
3862
|
-
}
|
|
3863
|
-
// The frame overhangs its 24dp layout slot by 7dp on each side. The column
|
|
3864
|
-
// clips children and the row clips to padding by default, which would cut
|
|
3865
|
-
// the pressed circle down to a 24dp-wide pill.
|
|
3866
|
-
trailingColumn.clipChildren = false
|
|
3867
|
-
clipToPadding = false
|
|
3868
|
-
if (isSourceMenu && data.optString("actionKey").isNotEmpty()) {
|
|
3869
|
-
icon.background = StateListDrawable().apply {
|
|
3870
|
-
addState(
|
|
3871
|
-
intArrayOf(android.R.attr.state_pressed),
|
|
3872
|
-
roundedFill(color(theme, "rowPressedBackground", "#00000017"), 19f),
|
|
3873
|
-
)
|
|
3874
|
-
addState(intArrayOf(), roundedFill(Color.TRANSPARENT, 19f))
|
|
3875
|
-
}
|
|
3876
|
-
}
|
|
3877
|
-
} else if (icon.iconName == "ChevronRightSmallOutline") {
|
|
3878
|
-
icon.glyphSizeDp = null
|
|
3879
|
-
// ListItem.DrillIn is a 24dp icon with mx=-6, for a 12dp layout footprint.
|
|
3880
|
-
icon.layoutParams = LayoutParams(dp(24), dp(24)).apply {
|
|
3881
|
-
marginStart = -dp(6)
|
|
3882
|
-
marginEnd = -dp(6)
|
|
3883
|
-
}
|
|
3884
|
-
} else {
|
|
3885
|
-
// ListItem.IconButton medium keeps a 24dp glyph in a 36dp frame. Its
|
|
3886
|
-
// m=-7 moves the last frame 7dp through the row's trailing padding. For
|
|
3887
|
-
// the Bookmark XStack, gap=$6 plus both negative margins leaves 10dp
|
|
3888
|
-
// between the physical frames (46dp between glyph centers).
|
|
3889
|
-
icon.glyphSizeDp = 24
|
|
3890
|
-
setPadding(paddingLeft, paddingTop, dp(5), paddingBottom)
|
|
3891
|
-
icon.layoutParams = LayoutParams(dp(36), dp(36)).apply {
|
|
3892
|
-
gravity = Gravity.END
|
|
3893
|
-
if (trailingColumn.orientation == HORIZONTAL && index > 0) {
|
|
3894
|
-
marginStart = dp(10)
|
|
3895
|
-
}
|
|
3896
|
-
}
|
|
3897
|
-
}
|
|
3898
|
-
icon.visibility = VISIBLE
|
|
3899
|
-
icon.isEnabled = !data.optBoolean("disabled", false)
|
|
3900
|
-
icon.alpha = if (icon.isEnabled) 1f else 0.4f
|
|
3901
|
-
val actionKey = data.optString("actionKey")
|
|
3902
|
-
if (icon.isEnabled && actionKey.isNotEmpty()) {
|
|
3903
|
-
icon.setOnClickListener {
|
|
3904
|
-
emitAction(item, actionKey, null, icon, "trailingAccessory", index)
|
|
3905
|
-
}
|
|
3906
|
-
}
|
|
3907
|
-
}
|
|
3908
|
-
|
|
3909
|
-
private fun applySize(item: NativeListItem) {
|
|
3910
|
-
if (item.type == "system" && item.json.optString("presentation") == "market" &&
|
|
3911
|
-
item.json.optString("variant") in setOf("retry", "noMatch")) {
|
|
3912
|
-
val defaultHeight = if (item.json.optString("variant") == "noMatch") 88.0 else if (item.json.optString("message").isEmpty()) 52.0 else 120.0
|
|
3913
|
-
minimumHeight = (item.json.optDouble("height", defaultHeight).toFloat() * resources.displayMetrics.density).roundToInt()
|
|
3914
|
-
selectorHeight = if (item.json.has("height")) minimumHeight else null
|
|
3915
|
-
return
|
|
3916
|
-
}
|
|
3917
|
-
if (item.type == "market") {
|
|
3918
|
-
val style = item.json.optJSONObject("style")
|
|
3919
|
-
val imageHeight = style?.optJSONObject("image")?.optDouble(
|
|
3920
|
-
"height",
|
|
3921
|
-
if (item.json.optString("variant") == "stock") 40.0 else 32.0,
|
|
3922
|
-
) ?: if (item.json.optString("variant") == "stock") 40.0 else 32.0
|
|
3923
|
-
val verticalPadding = style?.optDouble("verticalPadding", 12.0) ?: 12.0
|
|
3924
|
-
val defaultHeight = if (item.json.optString("variant") == "stock") 72.0 else 68.0
|
|
3925
|
-
// OneKey patch: preserve fractional DP until the final physical pixel edge.
|
|
3926
|
-
val rowHeight = item.json.optDouble(
|
|
3927
|
-
"height",
|
|
3928
|
-
maxOf(defaultHeight, imageHeight + verticalPadding * 2),
|
|
3929
|
-
)
|
|
3930
|
-
val sourceScale = if (selectorUsesSourceScale) 1f else NativeListScale.factor(resources)
|
|
3931
|
-
minimumHeight = (rowHeight * sourceScale * resources.displayMetrics.density).roundToInt()
|
|
3932
|
-
// OneKey patch: explicit Market row heights must not grow after child pixel rounding.
|
|
3933
|
-
selectorHeight = if (item.json.has("height")) minimumHeight else null
|
|
3934
|
-
return
|
|
3935
|
-
}
|
|
3936
|
-
val isWalletSidebar =
|
|
3937
|
-
item.type == "identity" && item.json.optString("presentation") == "walletSidebar"
|
|
3938
|
-
val isAccountSelectorIdentity =
|
|
3939
|
-
item.type == "identity" && item.json.optString("presentation") == "accountSelector"
|
|
3940
|
-
val isNetworkSelectorIdentity =
|
|
3941
|
-
item.type == "identity" && item.json.optString("presentation") == "networkSelector"
|
|
3942
|
-
val isAccountSelectorAction =
|
|
3943
|
-
item.type == "action" && item.json.optString("presentation") == "accountSelector"
|
|
3944
|
-
val isNetworkSelectorSection =
|
|
3945
|
-
item.type == "sectionHeader" && item.json.optString("presentation") == "networkSelector"
|
|
3946
|
-
title.textSize = sp(
|
|
3947
|
-
if (isWalletSidebar) {
|
|
3948
|
-
12f
|
|
3949
|
-
} else {
|
|
3950
|
-
when (item.type) {
|
|
3951
|
-
"message" -> 14f
|
|
3952
|
-
"sectionHeader" -> when {
|
|
3953
|
-
isNetworkSelectorSection && item.json.optString("variant") != "summary" -> 14f
|
|
3954
|
-
item.json.optString("variant") == "gallery" -> 18f
|
|
3955
|
-
item.json.optString("variant") == "summary" -> 16f
|
|
3956
|
-
currentLayout == "table" -> 11f
|
|
3957
|
-
item.json.optString("variant") == "history" || item.sectionKey?.startsWith("history-") == true -> 12f
|
|
3958
|
-
else -> 14f
|
|
3959
|
-
}
|
|
3960
|
-
"rail" -> 12f
|
|
3961
|
-
"system" -> 14f
|
|
3962
|
-
"metricCard" -> if (item.json.optString("size") == "large") 24f else 18f
|
|
3963
|
-
else -> 16f
|
|
3964
|
-
}
|
|
3965
|
-
},
|
|
3966
|
-
)
|
|
3967
|
-
title.typeface = if (isAccountSelectorAction && item.json.has("icon")) {
|
|
3968
|
-
NativeListFonts.medium(context)
|
|
3969
|
-
} else if (isWalletSidebar || isAccountSelectorIdentity || isAccountSelectorAction) {
|
|
3970
|
-
NativeListFonts.regular(context)
|
|
3971
|
-
} else {
|
|
3972
|
-
when (item.type) {
|
|
3973
|
-
"sectionHeader" -> when {
|
|
3974
|
-
isNetworkSelectorSection && item.json.has("height") && item.json.optString("variant") != "summary" && (item.json.optJSONObject("checkbox") != null || item.json.optString("titleActionKey").isEmpty()) -> NativeListFonts.semibold(context)
|
|
3975
|
-
isNetworkSelectorSection -> NativeListFonts.medium(context)
|
|
3976
|
-
currentLayout == "table" -> NativeListFonts.regular(context)
|
|
3977
|
-
item.json.optString("variant") == "summary" -> NativeListFonts.medium(context)
|
|
3978
|
-
item.sectionKey in setOf("linear-tokens", "action-tokens") -> NativeListFonts.regular(context)
|
|
3979
|
-
else -> NativeListFonts.semibold(context)
|
|
3980
|
-
}
|
|
3981
|
-
"system" -> NativeListFonts.regular(context)
|
|
3982
|
-
"message", "metricCard" -> NativeListFonts.semibold(context)
|
|
3983
|
-
else -> NativeListFonts.medium(context)
|
|
3984
|
-
}
|
|
3985
|
-
}
|
|
3986
|
-
subtitle.textSize = sp(when (item.type) {
|
|
3987
|
-
"mediaTile" -> 12f
|
|
3988
|
-
"metricCard" -> 11f
|
|
3989
|
-
else -> 14f
|
|
3990
|
-
})
|
|
3991
|
-
tertiary.textSize = sp(14f)
|
|
3992
|
-
if (isNetworkSelectorSection && item.json.optString("variant") != "summary") {
|
|
3993
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
3994
|
-
} else if (item.type == "sectionHeader" && item.json.optString("variant") == "gallery") {
|
|
3995
|
-
TextViewCompat.setLineHeight(title, dp(24))
|
|
3996
|
-
} else if (item.type == "sectionHeader" && item.json.optString("variant") == "summary") {
|
|
3997
|
-
TextViewCompat.setLineHeight(title, dp(24))
|
|
3998
|
-
} else if (item.type == "sectionHeader" && currentLayout == "table") {
|
|
3999
|
-
TextViewCompat.setLineHeight(title, dp(14))
|
|
4000
|
-
TextViewCompat.setLineHeight(trailingViews[0], dp(14))
|
|
4001
|
-
} else if (
|
|
4002
|
-
item.type == "sectionHeader" &&
|
|
4003
|
-
(item.json.optString("variant") == "history" || item.sectionKey?.startsWith("history-") == true)
|
|
4004
|
-
) {
|
|
4005
|
-
TextViewCompat.setLineHeight(title, dp(16))
|
|
4006
|
-
} else if (item.type == "sectionHeader" && item.sectionKey in setOf("linear-tokens", "action-tokens")) {
|
|
4007
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
4008
|
-
} else if (item.type == "identity" && item.json.optString("presentation") == "walletSidebar") {
|
|
4009
|
-
TextViewCompat.setLineHeight(title, dp(16))
|
|
4010
|
-
} else if (item.type == "identity") {
|
|
4011
|
-
TextViewCompat.setLineHeight(title, dp(24))
|
|
4012
|
-
TextViewCompat.setLineHeight(subtitle, dp(20))
|
|
4013
|
-
TextViewCompat.setLineHeight(tertiary, dp(20))
|
|
4014
|
-
} else if (item.type == "action") {
|
|
4015
|
-
TextViewCompat.setLineHeight(title, dp(24))
|
|
4016
|
-
} else if (item.type == "system") {
|
|
4017
|
-
TextViewCompat.setLineHeight(title, dp(20))
|
|
4018
|
-
} else if (item.type == "mediaTile") {
|
|
4019
|
-
TextViewCompat.setLineHeight(title, dp(24))
|
|
4020
|
-
TextViewCompat.setLineHeight(subtitle, dp(16))
|
|
4021
|
-
}
|
|
4022
|
-
status.textSize = sp(12f)
|
|
4023
|
-
badgeLine.textSize = sp(12f)
|
|
4024
|
-
dataColumns.forEach { column ->
|
|
4025
|
-
column.textSize = sp(16f)
|
|
4026
|
-
column.typeface = NativeListFonts.medium(context)
|
|
4027
|
-
column.fontFeatureSettings = "tnum"
|
|
4028
|
-
}
|
|
4029
|
-
// OneKey patch: exact selector row dimensions override template minimums.
|
|
4030
|
-
selectorHeight = if (item.json.has("height")) dp(item.json.optInt("height")) else null
|
|
4031
|
-
// OneKey patch: React Native's section spacers and letter blocks truncate physical heights.
|
|
4032
|
-
val isSelectorLetter = isNetworkSelectorSection && item.json.has("height") &&
|
|
4033
|
-
item.json.optString("variant") != "summary" && item.json.optString("titleActionKey").isEmpty() &&
|
|
4034
|
-
item.json.optJSONObject("checkbox") == null
|
|
4035
|
-
val isSelectorSectionSpacer = selectorUsesSourceScale && currentLayout == "sectioned" &&
|
|
4036
|
-
item.type == "system" && item.json.optString("variant") == "spacer"
|
|
4037
|
-
if (isSelectorLetter || isSelectorSectionSpacer) {
|
|
4038
|
-
selectorHeight = (item.json.optInt("height") * resources.displayMetrics.density).toInt()
|
|
4039
|
-
}
|
|
4040
|
-
if (isSelectorLetter) {
|
|
4041
|
-
val inset = (20 * resources.displayMetrics.density).toInt() - dp(8)
|
|
4042
|
-
// OneKey patch: SectionHeader has a fixed height and centered text, without vertical padding.
|
|
4043
|
-
setPadding(inset, 0, inset, 0)
|
|
4044
|
-
}
|
|
4045
|
-
if (item.json.has("height") && isNetworkSelectorSection && item.json.optString("variant") != "summary" && item.json.optString("titleActionKey").isNotEmpty() && item.json.optJSONObject("checkbox") == null) {
|
|
4046
|
-
// OneKey patch: the text-and-underline header's fractional measured height rounds up in React Native.
|
|
4047
|
-
selectorHeight = kotlin.math.ceil((item.json.optInt("height") * (if (selectorUsesSourceScale) 1f else NativeListScale.factor(resources)) * resources.displayMetrics.density).toDouble()).toInt()
|
|
4048
|
-
}
|
|
4049
|
-
// OneKey patch: an explicit per-row policy preserves each source list's measured heights.
|
|
4050
|
-
if (item.json.has("height")) {
|
|
4051
|
-
when (item.json.optString("heightRounding")) {
|
|
4052
|
-
"floor" -> selectorHeight = (item.json.optInt("height") * resources.displayMetrics.density).toInt()
|
|
4053
|
-
"nearest" -> selectorHeight = (item.json.optInt("height") * resources.displayMetrics.density).roundToInt()
|
|
4054
|
-
}
|
|
4055
|
-
}
|
|
4056
|
-
val baseHeight = when {
|
|
4057
|
-
item.json.has("height") -> item.json.optInt("height")
|
|
4058
|
-
item.type == "system" && item.json.optString("variant") == "spacer" -> item.json.optInt("height", 0)
|
|
4059
|
-
item.type == "walletGroup" -> {
|
|
4060
|
-
val childCount = item.json.optJSONArray("children")?.length() ?: 0
|
|
4061
|
-
// OneKey patch: include badge heights in the group layout.
|
|
4062
|
-
// (childCount + 1) * 68 + childCount * 12
|
|
4063
|
-
val members = listOf(item.json.getJSONObject("parent")) + (0 until childCount).map { item.json.getJSONArray("children").getJSONObject(it) }
|
|
4064
|
-
members.sumOf { it.optInt("height", if ((it.optJSONArray("badges")?.length() ?: 0) > 0) 92 else 68) } + childCount * 12 + if (members.first().has("height")) 2 else 0
|
|
4065
|
-
}
|
|
4066
|
-
isNetworkSelectorIdentity -> 47
|
|
4067
|
-
else -> when (item.type) {
|
|
4068
|
-
"rail" -> 28
|
|
4069
|
-
"activity" -> if ((item.json.optJSONArray("footerActions")?.length() ?: 0) > 0) 104 else 60
|
|
4070
|
-
"message" -> 0
|
|
4071
|
-
"mediaTile" -> 0
|
|
4072
|
-
"metricCard" -> when (item.json.optString("variant")) {
|
|
4073
|
-
"activity" -> 0
|
|
4074
|
-
"performance" -> 0
|
|
4075
|
-
else -> 132
|
|
4076
|
-
}
|
|
4077
|
-
"sectionHeader" -> when {
|
|
4078
|
-
isNetworkSelectorSection -> 44
|
|
4079
|
-
currentLayout == "table" -> 28
|
|
4080
|
-
item.json.optString("variant") == "summary" -> 80
|
|
4081
|
-
item.json.optString("variant") == "gallery" -> 32
|
|
4082
|
-
item.json.optString("variant") == "history" || item.sectionKey?.startsWith("history-") == true -> 16
|
|
4083
|
-
item.sectionKey in setOf("linear-tokens", "action-tokens") -> 30
|
|
4084
|
-
item.json.optString("value").isNotEmpty() && item.json.optJSONObject("checkbox") != null -> 40
|
|
4085
|
-
else -> 36
|
|
4086
|
-
}
|
|
4087
|
-
"system" -> when (item.json.optString("variant")) {
|
|
4088
|
-
"loading" -> when (item.json.optString("loadingStyle")) {
|
|
4089
|
-
"skeleton" -> 56
|
|
4090
|
-
"spinner" -> 52
|
|
4091
|
-
else -> if (item.json.optString("presentation") == "market") 68 else 56
|
|
4092
|
-
}
|
|
4093
|
-
"noMatch", "retry" -> if (item.json.optString("presentation") == "market") 44 else if (item.json.optString("variant") == "noMatch") 36 else 44
|
|
4094
|
-
"warning" -> 0
|
|
4095
|
-
"end" -> if (item.json.optString("presentation") == "market") 44 else 36
|
|
4096
|
-
else -> 56
|
|
4097
|
-
}
|
|
4098
|
-
"action" -> when {
|
|
4099
|
-
item.json.optString("presentation") == "accountSelector" -> 48
|
|
4100
|
-
item.json.has("icon") -> 60
|
|
4101
|
-
else -> 44
|
|
4102
|
-
}
|
|
4103
|
-
"dataRow" -> if (currentLayout == "table") {
|
|
4104
|
-
60
|
|
4105
|
-
} else if ((0 until item.json.getJSONArray("columns").length()).any {
|
|
4106
|
-
item.json.getJSONArray("columns").getJSONObject(it).optString("secondaryText").isNotEmpty()
|
|
4107
|
-
}) {
|
|
4108
|
-
64
|
|
4109
|
-
} else {
|
|
4110
|
-
56
|
|
4111
|
-
}
|
|
4112
|
-
else -> when {
|
|
4113
|
-
item.type == "identity" && item.json.optString("presentation") == "walletSidebar" -> if ((item.json.optJSONArray("badges")?.length() ?: 0) > 0) 92 else 68
|
|
4114
|
-
item.type == "identity" && item.json.optString("tertiary").isNotEmpty() -> 72
|
|
4115
|
-
item.type == "identity" && item.json.optString("subtitle").isNotEmpty() -> 60
|
|
4116
|
-
else -> 56
|
|
4117
|
-
}
|
|
4118
|
-
}
|
|
4119
|
-
}
|
|
4120
|
-
val modifier = if (item.json.has("height") || isNetworkSelectorIdentity) {
|
|
4121
|
-
0
|
|
4122
|
-
} else if (
|
|
4123
|
-
item.type == "sectionHeader" && item.json.optString("variant") in listOf("summary", "gallery")
|
|
4124
|
-
) {
|
|
4125
|
-
0
|
|
4126
|
-
} else {
|
|
4127
|
-
when (item.json.optString("size", "medium")) { "small" -> -8; "large" -> 12; else -> 0 }
|
|
4128
|
-
}
|
|
4129
|
-
val sectionSpacing = 0
|
|
4130
|
-
val hasSecondaryColumn = item.type == "dataRow" &&
|
|
4131
|
-
(0 until item.json.getJSONArray("columns").length()).any {
|
|
4132
|
-
item.json.getJSONArray("columns").getJSONObject(it).optString("secondaryText").isNotEmpty()
|
|
4133
|
-
}
|
|
4134
|
-
val tableAdjustment = if (item.type == "dataRow" && currentLayout == "table" && !hasSecondaryColumn) -8 else 0
|
|
4135
|
-
minimumHeight = dp((baseHeight + modifier + sectionSpacing + tableAdjustment).coerceAtLeast(0))
|
|
4136
|
-
}
|
|
4137
|
-
|
|
4138
|
-
private fun addSkeleton(
|
|
4139
|
-
view: View,
|
|
4140
|
-
widthDp: Int,
|
|
4141
|
-
heightDp: Int,
|
|
4142
|
-
theme: JSONObject?,
|
|
4143
|
-
bottomMarginDp: Int = 0,
|
|
4144
|
-
) {
|
|
4145
|
-
view.background = roundedFill(color(theme, "strongBackground", "#0000000F"), 6f)
|
|
4146
|
-
mainColumn.addView(
|
|
4147
|
-
view,
|
|
4148
|
-
LayoutParams(dp(widthDp), dp(heightDp)).apply { bottomMargin = dp(bottomMarginDp) },
|
|
4149
|
-
)
|
|
4150
|
-
}
|
|
4151
|
-
|
|
4152
|
-
private fun applyListOrientation(item: NativeListItem, listOrientation: String) {
|
|
4153
|
-
val params = layoutParams ?: return
|
|
4154
|
-
if (listOrientation == "horizontal") {
|
|
4155
|
-
params.width = when (item.type) {
|
|
4156
|
-
"rail" -> railWidth()
|
|
4157
|
-
"mediaTile" -> dp(200)
|
|
4158
|
-
else -> dp(280)
|
|
4159
|
-
}
|
|
4160
|
-
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
4161
|
-
} else {
|
|
4162
|
-
params.width = ViewGroup.LayoutParams.MATCH_PARENT
|
|
4163
|
-
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
4164
|
-
}
|
|
4165
|
-
layoutParams = params
|
|
4166
|
-
}
|
|
4167
|
-
|
|
4168
|
-
private fun railWidth(): Int {
|
|
4169
|
-
var width = dp(4 + 20 + 6).toFloat() + title.paint.measureText(title.text.toString())
|
|
4170
|
-
if (badgeLine.visibility == VISIBLE) {
|
|
4171
|
-
width += dp(6) + badgeLine.paint.measureText(badgeLine.text.toString())
|
|
4172
|
-
}
|
|
4173
|
-
if (status.visibility == VISIBLE) {
|
|
4174
|
-
width += dp(6) + status.paint.measureText(status.text.toString())
|
|
4175
|
-
}
|
|
4176
|
-
width += dp(4)
|
|
4177
|
-
return width.roundToInt()
|
|
4178
|
-
}
|
|
4179
|
-
|
|
4180
|
-
private fun leadingCornerRadius(shape: String, sizeDp: Int): Float = when (shape) {
|
|
4181
|
-
"square" -> 0f
|
|
4182
|
-
"rounded" -> minOf(10f, sizeDp / 4f)
|
|
4183
|
-
else -> sizeDp / 2f
|
|
4184
|
-
}
|
|
4185
|
-
|
|
4186
|
-
private fun leadingOutlineProvider(shape: String) = object : ViewOutlineProvider() {
|
|
4187
|
-
override fun getOutline(view: View, outline: Outline) {
|
|
4188
|
-
when (shape) {
|
|
4189
|
-
"square" -> outline.setRect(0, 0, view.width, view.height)
|
|
4190
|
-
"rounded" -> {
|
|
4191
|
-
// OneKey patch: the account avatar has an 8-point radius at its 32-point size.
|
|
4192
|
-
// outline.setRoundRect(0, 0, view.width, view.height, dp(10).toFloat())
|
|
4193
|
-
val radius = if ((tag as? NativeListItem)?.json?.optString("presentation") == "accountSelector" && (tag as? NativeListItem)?.json?.has("height") == true) dp(8).toFloat() else dp(10).toFloat()
|
|
4194
|
-
outline.setRoundRect(0, 0, view.width, view.height, radius)
|
|
4195
|
-
}
|
|
4196
|
-
else -> outline.setOval(0, 0, view.width, view.height)
|
|
4197
|
-
}
|
|
4198
|
-
}
|
|
4199
|
-
}
|
|
4200
|
-
|
|
4201
|
-
private fun roundedOutlineProvider(radiusDp: Float) = object : ViewOutlineProvider() {
|
|
4202
|
-
override fun getOutline(view: View, outline: Outline) {
|
|
4203
|
-
outline.setRoundRect(0, 0, view.width, view.height, scaledDp(radiusDp))
|
|
4204
|
-
}
|
|
4205
|
-
}
|
|
4206
|
-
|
|
4207
|
-
private fun JSONArray?.hasAccessory(kind: String): Boolean {
|
|
4208
|
-
if (this == null) return false
|
|
4209
|
-
return (0 until length()).any { optJSONObject(it)?.optString("kind") == kind }
|
|
4210
|
-
}
|
|
4211
|
-
|
|
4212
|
-
private fun JSONArray?.isBookmarkEditActions(): Boolean =
|
|
4213
|
-
this != null && length() == 2 &&
|
|
4214
|
-
optJSONObject(0)?.optString("kind") == "icon" &&
|
|
4215
|
-
optJSONObject(0)?.optString("name") == "PencilOutline" &&
|
|
4216
|
-
optJSONObject(1)?.optString("kind") == "icon" &&
|
|
4217
|
-
optJSONObject(1)?.optString("name") == "DragOutline"
|
|
4218
|
-
|
|
4219
|
-
private fun JSONArray?.isValuePairMenu(): Boolean =
|
|
4220
|
-
this != null && length() == 2 &&
|
|
4221
|
-
optJSONObject(0)?.optString("kind") == "valuePair" &&
|
|
4222
|
-
optJSONObject(1)?.optString("kind") == "menu"
|
|
4223
|
-
|
|
4224
|
-
private fun bindImage(
|
|
4225
|
-
source: JSONObject,
|
|
4226
|
-
imageView: OneKeyImageReusableView,
|
|
4227
|
-
token: String,
|
|
4228
|
-
slot: Int,
|
|
4229
|
-
variant: String,
|
|
4230
|
-
round: Boolean = false,
|
|
4231
|
-
onLoad: (() -> Unit)? = null,
|
|
4232
|
-
onError: (() -> Unit)? = null,
|
|
4233
|
-
hideUntilLoaded: Boolean = false,
|
|
4234
|
-
retryAttempt: Int = 0,
|
|
4235
|
-
) {
|
|
4236
|
-
selectorImageRetries.remove(imageView)?.let(imageView::removeCallbacks)
|
|
4237
|
-
val expectedEpoch = bindingEpoch
|
|
4238
|
-
val retryLimit = source.optInt("retryTimes", 0).coerceAtLeast(0)
|
|
4239
|
-
val uri = source.optString("uri").trim().takeIf(String::isNotEmpty)
|
|
4240
|
-
val sourceHeadersJson = source.optJSONObject("headers")?.toString()
|
|
4241
|
-
val recyclingKey = if (retryAttempt == 0) "$token:$slot" else "$token:$slot:retry:$retryAttempt"
|
|
4242
|
-
if (hideUntilLoaded) {
|
|
4243
|
-
imageView.visibility = if (
|
|
4244
|
-
imageView.isDisplaying(uri, sourceHeadersJson, recyclingKey)
|
|
4245
|
-
) VISIBLE else INVISIBLE
|
|
4246
|
-
}
|
|
4247
|
-
val handleLoad: (() -> Unit)? = if (hideUntilLoaded) ({
|
|
4248
|
-
if (bindingEpoch == expectedEpoch) {
|
|
4249
|
-
imageView.visibility = VISIBLE
|
|
4250
|
-
onLoad?.invoke()
|
|
4251
|
-
}
|
|
4252
|
-
}) else onLoad
|
|
4253
|
-
val handleError: (() -> Unit)? = if (hideUntilLoaded) ({
|
|
4254
|
-
if (bindingEpoch == expectedEpoch) {
|
|
4255
|
-
imageView.visibility = INVISIBLE
|
|
4256
|
-
onError?.invoke()
|
|
4257
|
-
}
|
|
4258
|
-
}) else onError
|
|
4259
|
-
imageView.configure(
|
|
4260
|
-
sourceUri = uri,
|
|
4261
|
-
sourceHeadersJson = sourceHeadersJson,
|
|
4262
|
-
variant = variant,
|
|
4263
|
-
contentFit = source.optString("contentFit", "cover"),
|
|
4264
|
-
cachePolicy = source.optString("cachePolicy", "memory-disk"),
|
|
4265
|
-
autoplay = source.optBoolean("autoplay", false),
|
|
4266
|
-
recyclingKey = recyclingKey,
|
|
4267
|
-
optimizeTos = retryAttempt == 0 && source.optBoolean("optimizeTos", true),
|
|
4268
|
-
round = round,
|
|
4269
|
-
overscan = source.optDouble("overscan", 1.1),
|
|
4270
|
-
loadingStrategy = source.optString("loadingStrategy", "none"),
|
|
4271
|
-
placeholderColor = imagePlaceholderColor,
|
|
4272
|
-
onLoad = if (retryLimit == 0) handleLoad else ({
|
|
4273
|
-
if (bindingEpoch == expectedEpoch) {
|
|
4274
|
-
selectorImageRetries.remove(imageView)?.let(imageView::removeCallbacks)
|
|
4275
|
-
handleLoad?.invoke()
|
|
4276
|
-
}
|
|
4277
|
-
}),
|
|
4278
|
-
onError = if (retryLimit == 0) handleError else ({
|
|
4279
|
-
if (bindingEpoch == expectedEpoch) {
|
|
4280
|
-
handleError?.invoke()
|
|
4281
|
-
if (retryAttempt < retryLimit && !selectorImageRetries.containsKey(imageView)) {
|
|
4282
|
-
val retry = Runnable {
|
|
4283
|
-
if (bindingEpoch == expectedEpoch) {
|
|
4284
|
-
selectorImageRetries.remove(imageView)
|
|
4285
|
-
bindImage(
|
|
4286
|
-
source = source,
|
|
4287
|
-
imageView = imageView,
|
|
4288
|
-
token = token,
|
|
4289
|
-
slot = slot,
|
|
4290
|
-
variant = variant,
|
|
4291
|
-
round = round,
|
|
4292
|
-
onLoad = onLoad,
|
|
4293
|
-
onError = onError,
|
|
4294
|
-
hideUntilLoaded = hideUntilLoaded,
|
|
4295
|
-
retryAttempt = retryAttempt + 1,
|
|
4296
|
-
)
|
|
4297
|
-
}
|
|
4298
|
-
}
|
|
4299
|
-
selectorImageRetries[imageView] = retry
|
|
4300
|
-
imageView.postDelayed(retry, kotlin.random.Random.nextLong(3) * 1000L)
|
|
4301
|
-
}
|
|
4302
|
-
}
|
|
4303
|
-
}),
|
|
4304
|
-
)
|
|
4305
|
-
}
|
|
4306
|
-
|
|
4307
|
-
private fun weighted() = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
|
4308
|
-
private fun wrap() = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
|
4309
|
-
private fun dp(value: Int): Int = if (selectorUsesSourceScale) (value * resources.displayMetrics.density).roundToInt() else NativeListScale.dp(resources, value)
|
|
4310
|
-
private fun scaledDp(value: Float): Float = if (selectorUsesSourceScale) (value * resources.displayMetrics.density).roundToInt().toFloat() else NativeListScale.dp(resources, value)
|
|
4311
|
-
private fun sp(value: Float): Float = if (selectorUsesSourceScale) value else NativeListScale.font(resources, value)
|
|
4312
|
-
|
|
4313
|
-
private fun color(theme: JSONObject?, key: String, fallback: String): Int =
|
|
4314
|
-
safeColor(theme?.optString(key, fallback), parseNativeListColor(fallback))
|
|
4315
|
-
|
|
4316
|
-
private fun safeColor(value: String?, fallback: Int): Int = try {
|
|
4317
|
-
if (value.isNullOrEmpty()) fallback else parseNativeListColor(value)
|
|
4318
|
-
} catch (_: IllegalArgumentException) {
|
|
4319
|
-
fallback
|
|
4320
|
-
}
|
|
4321
|
-
|
|
4322
|
-
private fun roundedFill(color: Int, radiusDp: Float) = GradientDrawable().apply {
|
|
4323
|
-
setColor(color)
|
|
4324
|
-
cornerRadius = scaledDp(radiusDp)
|
|
4325
|
-
}
|
|
4326
|
-
|
|
4327
|
-
private fun roundedStroke(stroke: Int, fill: Int, radiusDp: Float) = GradientDrawable().apply {
|
|
4328
|
-
setColor(fill)
|
|
4329
|
-
setStroke(dp(2), stroke)
|
|
4330
|
-
cornerRadius = scaledDp(radiusDp)
|
|
4331
|
-
}
|
|
4332
|
-
|
|
4333
|
-
private fun roundedHairlineStroke(stroke: Int, radiusDp: Float) = GradientDrawable().apply {
|
|
4334
|
-
setColor(Color.TRANSPARENT)
|
|
4335
|
-
setStroke(1, stroke)
|
|
4336
|
-
cornerRadius = scaledDp(radiusDp)
|
|
4337
|
-
}
|
|
4338
|
-
|
|
4339
|
-
private fun groupedBackground(position: String, color: Int) = GradientDrawable().apply {
|
|
4340
|
-
setColor(color)
|
|
4341
|
-
val radius = scaledDp(12f)
|
|
4342
|
-
cornerRadii = when (position) {
|
|
4343
|
-
"first" -> floatArrayOf(radius, radius, radius, radius, 0f, 0f, 0f, 0f)
|
|
4344
|
-
"last" -> floatArrayOf(0f, 0f, 0f, 0f, radius, radius, radius, radius)
|
|
4345
|
-
"single" -> FloatArray(8) { radius }
|
|
4346
|
-
"rail" -> FloatArray(8) { scaledDp(8f) }
|
|
4347
|
-
"mediaTile" -> FloatArray(8) { scaledDp(16f) }
|
|
4348
|
-
else -> FloatArray(8)
|
|
4349
|
-
}
|
|
4350
|
-
}
|
|
4351
|
-
|
|
4352
|
-
}
|
|
4353
|
-
|
|
4354
|
-
private class OneKeyIconView(context: android.content.Context) : View(context) {
|
|
4355
|
-
var useSourceScale = false
|
|
4356
|
-
var iconName: String = ""
|
|
4357
|
-
set(value) {
|
|
4358
|
-
field = value
|
|
4359
|
-
invalidate()
|
|
4360
|
-
}
|
|
4361
|
-
var tintColor: Int = Color.rgb(100, 100, 100)
|
|
4362
|
-
set(value) {
|
|
4363
|
-
field = value
|
|
4364
|
-
invalidate()
|
|
4365
|
-
}
|
|
4366
|
-
var glyphSizeDp: Int? = null
|
|
4367
|
-
set(value) {
|
|
4368
|
-
field = value
|
|
4369
|
-
invalidate()
|
|
4370
|
-
}
|
|
4371
|
-
|
|
4372
|
-
private val fill = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
4373
|
-
|
|
4374
|
-
override fun onDraw(canvas: Canvas) {
|
|
4375
|
-
super.onDraw(canvas)
|
|
4376
|
-
val pathData = iconPaths[iconName] ?: return
|
|
4377
|
-
val drawSize = minOf(
|
|
4378
|
-
minOf(width, height).toFloat(),
|
|
4379
|
-
glyphSizeDp?.let { if (useSourceScale) (it * resources.displayMetrics.density).roundToInt().toFloat() else NativeListScale.dp(resources, it).toFloat() } ?: Float.MAX_VALUE,
|
|
4380
|
-
)
|
|
4381
|
-
// OneKey patch: custom account-error artwork uses an 18-point viewBox.
|
|
4382
|
-
// val scale = drawSize / 24f
|
|
4383
|
-
val scale = drawSize / (selectorIconViewBoxes[iconName] ?: 24f)
|
|
4384
|
-
fill.color = tintColor
|
|
4385
|
-
canvas.save()
|
|
4386
|
-
canvas.translate((width - drawSize) / 2f, (height - drawSize) / 2f)
|
|
4387
|
-
canvas.scale(scale, scale)
|
|
4388
|
-
val sourceFillTypes = actionIconFillTypes[iconName]
|
|
4389
|
-
pathData.forEachIndexed { index, data ->
|
|
4390
|
-
PathParser.createPathFromPathData(data)?.let { path ->
|
|
4391
|
-
path.fillType = sourceFillTypes?.getOrNull(index) ?: Path.FillType.EVEN_ODD
|
|
4392
|
-
// OneKey patch: provider illustration colors are part of their source asset.
|
|
4393
|
-
fill.color = selectorIconColors[iconName]?.getOrNull(index) ?: tintColor
|
|
4394
|
-
canvas.drawPath(path, fill)
|
|
4395
|
-
}
|
|
4396
|
-
}
|
|
4397
|
-
canvas.restore()
|
|
4398
|
-
}
|
|
4399
|
-
|
|
4400
|
-
companion object {
|
|
4401
|
-
// OneKey patch: preserve provider colors and non-24 viewBoxes.
|
|
4402
|
-
private val selectorIconColors: Map<String, List<Int?>> = mapOf(
|
|
4403
|
-
"GlobusOutline" to listOf(null),
|
|
4404
|
-
"LockSolid" to listOf(null),
|
|
4405
|
-
"GoogleIllus" to listOf(parseNativeListColor("#4285F4"), parseNativeListColor("#34A853"), parseNativeListColor("#FBBC05"), parseNativeListColor("#EA4335")),
|
|
4406
|
-
"AppleBrand" to listOf(null),
|
|
4407
|
-
"BotIllus" to listOf(parseNativeListColor("#8897A5"), parseNativeListColor("#3FA9F5"), parseNativeListColor("#8897A5"), parseNativeListColor("#8897A5"), parseNativeListColor("#10243E"), parseNativeListColor("#10243E"), parseNativeListColor("#10243E")),
|
|
4408
|
-
"AllNetworksSolid" to listOf(null, null),
|
|
4409
|
-
"CrossedSmallSolid" to listOf(null),
|
|
4410
|
-
"AccountErrorCustom" to listOf(Color.argb(0x72, 0, 0, 0), Color.argb(0x72, 0, 0, 0)),
|
|
4411
|
-
"Circle" to listOf(null),
|
|
4412
|
-
)
|
|
4413
|
-
private val selectorIconViewBoxes = mapOf(
|
|
4414
|
-
"GlobusOutline" to 24f,
|
|
4415
|
-
"LockSolid" to 24f,
|
|
4416
|
-
"GoogleIllus" to 24f,
|
|
4417
|
-
"AppleBrand" to 16f,
|
|
4418
|
-
"BotIllus" to 24f,
|
|
4419
|
-
"AllNetworksSolid" to 24f,
|
|
4420
|
-
"CrossedSmallSolid" to 24f,
|
|
4421
|
-
"AccountErrorCustom" to 18f,
|
|
4422
|
-
"Circle" to 24f,
|
|
4423
|
-
)
|
|
4424
|
-
// Keep the SVG fill-rule used by each Action-row source path. React Native
|
|
4425
|
-
// SVG defaults to nonzero (WINDING); only paths declaring fillRule="evenodd"
|
|
4426
|
-
// use EVEN_ODD. Other existing icons retain their prior rendering behavior.
|
|
4427
|
-
private val actionIconFillTypes = mapOf(
|
|
4428
|
-
"GlobusOutline" to listOf(Path.FillType.EVEN_ODD),
|
|
4429
|
-
"LockSolid" to listOf(Path.FillType.EVEN_ODD),
|
|
4430
|
-
"GoogleIllus" to listOf(Path.FillType.WINDING, Path.FillType.WINDING, Path.FillType.WINDING, Path.FillType.WINDING),
|
|
4431
|
-
"AppleBrand" to listOf(Path.FillType.WINDING),
|
|
4432
|
-
"BotIllus" to listOf(Path.FillType.WINDING, Path.FillType.WINDING, Path.FillType.WINDING, Path.FillType.WINDING, Path.FillType.WINDING, Path.FillType.WINDING, Path.FillType.WINDING),
|
|
4433
|
-
"AllNetworksSolid" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
4434
|
-
"CrossedSmallSolid" to listOf(Path.FillType.WINDING),
|
|
4435
|
-
"AccountErrorCustom" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
4436
|
-
"Circle" to listOf(Path.FillType.WINDING),
|
|
4437
|
-
"BadgeVerifiedSolid" to listOf(Path.FillType.EVEN_ODD),
|
|
4438
|
-
"ChevronRightSmallOutline" to listOf(Path.FillType.WINDING),
|
|
4439
|
-
"MinusCircleOutline" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
4440
|
-
"PlusCircleOutline" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
4441
|
-
"PlusSmallOutline" to listOf(Path.FillType.WINDING),
|
|
4442
|
-
"DotHorOutline" to listOf(Path.FillType.WINDING),
|
|
4443
|
-
"MinusCircleSolid" to listOf(Path.FillType.EVEN_ODD),
|
|
4444
|
-
"PencilOutline" to listOf(Path.FillType.EVEN_ODD),
|
|
4445
|
-
"DragOutline" to listOf(Path.FillType.WINDING),
|
|
4446
|
-
"StarSolid" to listOf(Path.FillType.WINDING),
|
|
4447
|
-
"ChevronGrabberVerOutline" to listOf(Path.FillType.WINDING),
|
|
4448
|
-
"ChevronBottomOutline" to listOf(Path.FillType.WINDING),
|
|
4449
|
-
"ChevronTopOutline" to listOf(Path.FillType.WINDING),
|
|
4450
|
-
"ImageSquareWavesOutline" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
4451
|
-
)
|
|
4452
|
-
|
|
4453
|
-
// Exact 24x24 paths from app-monorepo packages/components Icon sources.
|
|
4454
|
-
private val iconPaths = mapOf(
|
|
4455
|
-
// OneKey patch: official selector SVG path geometry.
|
|
4456
|
-
"GlobusOutline" to listOf("M12 2c5.185 0 9.448 3.947 9.95 9H22v2h-.05c-.502 5.053-4.765 9-9.95 9s-9.448-3.947-9.95-9H2v-2h.05C2.552 5.947 6.815 2 12 2M9.523 13c.09 1.982.438 3.726.934 5.002.29.746.612 1.282.917 1.614.304.331.517.384.626.384s.322-.053.626-.384c.305-.332.627-.868.917-1.614.496-1.276.845-3.02.934-5.002zm-5.459 0a8 8 0 0 0 4.8 6.36 10 10 0 0 1-.271-.633C7.994 17.187 7.61 15.189 7.52 13zm12.416 0c-.09 2.189-.474 4.187-1.073 5.727a10 10 0 0 1-.271.633 8 8 0 0 0 4.8-6.36zM8.863 4.639A8 8 0 0 0 4.064 11h3.457c.09-2.189.473-4.187 1.072-5.727q.127-.327.27-.634M12 4c-.109 0-.322.053-.626.384-.305.332-.627.868-.917 1.614-.496 1.276-.844 3.02-.934 5.002h4.954c-.09-1.982-.438-3.726-.934-5.002-.29-.746-.612-1.282-.917-1.614C12.322 4.053 12.109 4 12 4m3.136.639q.144.307.271.634c.599 1.54.982 3.538 1.073 5.727h3.456a8 8 0 0 0-4.8-6.361"),
|
|
4457
|
-
"LockSolid" to listOf("M12 2a5 5 0 0 1 5 5v2h3v13H4V9h3V7a5 5 0 0 1 5-5m-1 11v5h2v-5zm1-9a3 3 0 0 0-3 3v2h6V7a3 3 0 0 0-3-3"),
|
|
4458
|
-
"GoogleIllus" to listOf("M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09", "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23", "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22z", "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53"),
|
|
4459
|
-
"AppleBrand" to listOf("M11.67.834c.117 1.074-.315 2.153-.955 2.928-.64.773-1.692 1.378-2.718 1.298-.14-1.054.38-2.151.971-2.836C9.63 1.45 10.746.872 11.67.834M14.994 7.093c-.176.108-1.992 1.224-1.972 3.482.025 2.769 2.428 3.693 2.46 3.705l-.004.015a10.1 10.1 0 0 1-1.264 2.593c-.764 1.116-1.556 2.229-2.806 2.254-.598.011-1-.162-1.416-.343-.437-.19-.891-.386-1.609-.386-.751 0-1.226.203-1.683.398-.397.169-.78.333-1.32.354-1.208.047-2.124-1.207-2.895-2.32C.909 14.57-.294 10.414 1.322 7.612c.803-1.395 2.237-2.275 3.794-2.298.671-.014 1.32.244 1.89.47.434.172.821.326 1.135.326.282 0 .659-.149 1.099-.323.692-.273 1.539-.607 2.41-.518.599.026 2.276.24 3.354 1.818z"),
|
|
4460
|
-
"BotIllus" to listOf("M11 2a1 1 0 1 1 2 0v1.8l1.6 1.6a1 1 0 1 1-1.4 1.4L12 5.6l-1.2 1.2a1 1 0 0 1-1.4-1.4L11 3.8z", "M8.0 6.0h8.0a5.0 5.0 0 0 1 5.0 5.0v4.0a5.0 5.0 0 0 1 -5.0 5.0h-8.0a5.0 5.0 0 0 1 -5.0 -5.0v-4.0a5.0 5.0 0 0 1 5.0 -5.0z", "M3.0 10.0h0.0a1.5 1.5 0 0 1 1.5 1.5v3.0a1.5 1.5 0 0 1 -1.5 1.5h0.0a1.5 1.5 0 0 1 -1.5 -1.5v-3.0a1.5 1.5 0 0 1 1.5 -1.5z", "M21.0 10.0h0.0a1.5 1.5 0 0 1 1.5 1.5v3.0a1.5 1.5 0 0 1 -1.5 1.5h0.0a1.5 1.5 0 0 1 -1.5 -1.5v-3.0a1.5 1.5 0 0 1 1.5 -1.5z", "M7.5 12.0a1.5 1.5 0 1 0 3.0 0a1.5 1.5 0 1 0 -3.0 0", "M13.5 12.0a1.5 1.5 0 1 0 3.0 0a1.5 1.5 0 1 0 -3.0 0", "M8.5 15.4c.9.8 2.08 1.2 3.5 1.2s2.6-.4 3.5-1.2c.24-.2.6-.18.8.06.2.23.17.6-.06.8-1.14.98-2.58 1.46-4.24 1.46s-3.1-.48-4.24-1.46a.58.58 0 0 1-.06-.8c.2-.24.56-.26.8-.06"),
|
|
4461
|
-
"AllNetworksSolid" to listOf("M15.333 13.998a1.335 1.335 0 1 1 0 2.67 1.335 1.335 0 0 1 0-2.67", "M12 0c6.627 0 12 5.373 12 12s-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0M8 12.668A2 2 0 0 0 6 14.666V16c0 1.103.895 1.997 1.998 1.998h1.334A2 2 0 0 0 11.33 16v-1.334a2 2 0 0 0-1.998-1.998zm7.333 0a2.665 2.665 0 1 0 0 5.33 2.665 2.665 0 0 0 0-5.33M7.999 6.001A2 2 0 0 0 6.001 8v1.334c0 1.103.895 1.998 1.998 1.998h1.334a2 2 0 0 0 1.998-1.998V7.999a2 2 0 0 0-1.998-1.998zm6.667 0A2 2 0 0 0 12.668 8v1.334c0 1.103.895 1.998 1.998 1.998H16a2 2 0 0 0 1.998-1.998V7.999A2 2 0 0 0 16 6.001z"),
|
|
4462
|
-
"CrossedSmallSolid" to listOf("M17.87 8.25 14.12 12l3.75 3.75-2.12 2.121-3.75-3.75-3.75 3.75-2.121-2.121L9.879 12l-3.75-3.75 2.12-2.121L12 9.879l3.75-3.75 2.122 2.121Z"),
|
|
4463
|
-
"AccountErrorCustom" to listOf("M12.5 12.75a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5", "M0 3.5A3.5 3.5 0 0 1 3.5 0h8.088A2.41 2.41 0 0 1 14 2.412V5h1a3 3 0 0 1 3 3v7a3 3 0 0 1-3 3H4a4 4 0 0 1-4-4zm2 3.163V14a2 2 0 0 0 2 2h11a1 1 0 0 0 1-1V8a1 1 0 0 0-1-1H3.5c-.537 0-1.045-.12-1.5-.337M2 3.5A1.5 1.5 0 0 0 3.5 5H12V2.412A.41.41 0 0 0 11.588 2H3.5A1.5 1.5 0 0 0 2 3.5"),
|
|
4464
|
-
"Circle" to listOf("M0 12a12 12 0 1 0 24 0a12 12 0 1 0 -24 0"),
|
|
4465
|
-
"BadgeVerifiedSolid" to listOf("M9.483 11.458v3.5h-1v-3.5z M10.467 2.698a2.03 2.03 0 0 1 3.065 0l1.358 1.564a.03.03 0 0 0 .028.01l2.046-.325a2.03 2.03 0 0 1 2.347 1.971l.037 2.07q0 .016.014.026l1.776 1.066a2.03 2.03 0 0 1 .532 3.019l-1.304 1.609a.03.03 0 0 0-.005.03l.675 1.956a2.03 2.03 0 0 1-1.533 2.656l-2.033.394a.03.03 0 0 0-.023.019l-.741 1.933a2.03 2.03 0 0 1-2.88 1.05l-1.811-1.006a.03.03 0 0 0-.03 0l-1.811 1.005a2.03 2.03 0 0 1-2.88-1.049l-.742-1.933a.03.03 0 0 0-.023-.019l-2.033-.394a2.03 2.03 0 0 1-1.532-2.656l.675-1.957a.03.03 0 0 0-.005-.029l-1.304-1.61a2.03 2.03 0 0 1 .532-3.018l1.776-1.066a.03.03 0 0 0 .014-.026l.035-2.07a2.03 2.03 0 0 1 2.349-1.97l2.045.324a.03.03 0 0 0 .028-.01zm1.516 3.76a.5.5 0 0 0-.447.276l-1.861 3.724H8.483a1 1 0 0 0-1 1v3.5a1 1 0 0 0 1 1h6.692a2 2 0 0 0 1.981-1.73l.341-2.5a2 2 0 0 0-1.982-2.27h-1.939l.197-1.269a1.5 1.5 0 0 0-1.481-1.731z"),
|
|
4466
|
-
"ArrowBottomOutline" to listOf("m13 17.586 5-5L19.414 14 12 21.414 4.586 14 6 12.586l5 5V3h2z"),
|
|
4467
|
-
"ArrowTopOutline" to listOf("M19.414 10 18 11.414l-5-5V21h-2V6.414l-5 5L4.586 10 12 2.586z"),
|
|
4468
|
-
"ChartTrendingUpOutline" to listOf("M22 13h-2V9.414l-7 7-4-4-6 6L1.586 17 9 9.586l4 4L18.586 8H15V6h7z"),
|
|
4469
|
-
"SwapHorOutline" to listOf("M21 16H6.914l2.293 2.293-1.414 1.414L2.086 14H21zm.914-6H3V8h14.086l-2.293-2.293 1.414-1.414z"),
|
|
4470
|
-
"ShieldExclamationOutline" to listOf(
|
|
4471
|
-
"M12 12.25a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5m0-4.75a1 1 0 0 1 1 1v2a1 1 0 0 1-2 0v-2a1 1 0 0 1 1-1",
|
|
4472
|
-
"M11.352 2.223c.42-.145.876-.145 1.296 0l6.98 2.4a1.995 1.995 0 0 1 1.347 1.886v5.432c0 2.799-1.146 4.817-2.805 6.387-1.61 1.525-3.735 2.652-5.696 3.71a1 1 0 0 1-.947 0c-1.961-1.058-4.086-2.185-5.697-3.71-1.659-1.57-2.805-3.588-2.805-6.386V6.508c0-.852.542-1.61 1.347-1.887l6.98-2.4ZM5.02 6.509v5.432c0 2.16.848 3.676 2.18 4.938 1.272 1.204 2.957 2.149 4.799 3.145 1.842-.996 3.527-1.94 4.799-3.145 1.332-1.262 2.181-2.778 2.181-4.938V6.51L12 4.109z",
|
|
4473
|
-
),
|
|
4474
|
-
"InfoCircleOutline" to listOf(
|
|
4475
|
-
"M13 17h-2v-5h-1v-2h3zm0-8h-2V7h2z",
|
|
4476
|
-
"M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2m0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16",
|
|
4477
|
-
),
|
|
4478
|
-
"SpeakerPromoteOutline" to listOf("M21.996 11a4 4 0 0 1-3 3.874v5.495l-5.361-1.711A3.998 3.998 0 0 1 5.996 17v-.779l-4-1.276v-7.89l17-5.424v5.495c1.725.444 3 2.01 3 3.874m-2 0c0-.74-.402-1.385-1-1.731v3.46c.597-.345 1-.989 1-1.729M7.998 7.24v7.521l8.998 2.871V4.368l-8.998 2.87ZM3.996 8.516v4.967l2.002.638V7.877zm4 8.484a2 2 0 0 0 3.706 1.041L7.996 16.86z"),
|
|
4479
|
-
"PlusCircleOutline" to listOf(
|
|
4480
|
-
"M13 11h4v2h-4v4h-2v-4l-4 .001v-2L11 11V7h2z",
|
|
4481
|
-
"M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2m0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16",
|
|
4482
|
-
),
|
|
4483
|
-
"PlusSmallOutline" to listOf("M13 11h5v2h-5v5h-2v-5H6v-2h5V6h2z"),
|
|
4484
|
-
"DotHorOutline" to listOf("M6 14H2v-4h4zm8 0h-4v-4h4zm8 0h-4v-4h4z"),
|
|
4485
|
-
"MinusCircleOutline" to listOf(
|
|
4486
|
-
"M17 13H7v-2h10z",
|
|
4487
|
-
"M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2m0 2a8 8 0 1 0 0 16 8 8 0 0 0 0-16",
|
|
4488
|
-
),
|
|
4489
|
-
"MinusCircleSolid" to listOf("M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2M7 13h10v-2H7z"),
|
|
4490
|
-
"ErrorSolid" to listOf("M23.256 20H.742L12 1.041zM11 15v2h2v-2zm0-6v5h2V9z"),
|
|
4491
|
-
"QuestionmarkSolid" to listOf("M12 2c5.523 0 10 4.477 10 10s-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2m-1 15h2v-2h-2zM9 7v3h2V9h2v1l-2 1.5V14h2v-1.5l2-1.5V7z"),
|
|
4492
|
-
"PencilOutline" to listOf("M22.414 7.5 7.914 22H2v-5.914l14.5-14.5zM4 16.914V20h3.086l9.5-9.5L13.5 7.414zM14.914 6 18 9.086 19.586 7.5 16.5 4.414z"),
|
|
4493
|
-
"ChevronRightSmallOutline" to listOf("M15.414 12 10 17.414 8.586 16l4-4-4-4L10 6.586z"),
|
|
4494
|
-
"DragOutline" to listOf("M11 21H7v-4h4zm6 0h-4v-4h4zm-6-7H7v-4h4zm6 0h-4v-4h4zm-6-7H7V3h4zm6 0h-4V3h4z"),
|
|
4495
|
-
"StarOutline" to listOf("m15.455 7.243 7.729 1.123-5.592 5.45 1.32 7.698L12 17.879l-6.911 3.635 1.32-7.698-5.592-5.45 7.728-1.123L12 .24zM9.872 9.071l-4.759.69 3.444 3.358-.814 4.738L12 15.62l.465.245 3.791 1.993-.813-4.739 3.443-3.357-4.758-.69L12 4.758z"),
|
|
4496
|
-
"StarSolid" to listOf("m15.405 7.313 7.84 1.034-5.735 5.443 1.44 7.774L12 17.793l-6.948 3.771 1.44-7.774L.756 8.347l7.839-1.034L12 .178z"),
|
|
4497
|
-
"ChevronGrabberVerOutline" to listOf("M17.414 15 12 20.414 6.586 15 8 13.586l4 4 4-4zm0-6L16 10.414l-4-4-4 4L6.586 9 12 3.586z"),
|
|
4498
|
-
"ChevronBottomOutline" to listOf("M20.707 9.707 12 18.414 3.293 9.707l1.414-1.414L12 15.586l7.293-7.293z"),
|
|
4499
|
-
"ChevronTopOutline" to listOf("m20.707 14.293-1.414 1.414L12 8.414l-7.293 7.293-1.414-1.414L12 5.586z"),
|
|
4500
|
-
"ImageSquareWavesOutline" to listOf(
|
|
4501
|
-
"M14.25 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4",
|
|
4502
|
-
"M21 21H3V3h18zM5 16.414V19h12.586L14 15.414l-2 2-4-4zm0-2.828 3-3 4 4 2-2 5 5V5H5z",
|
|
4503
|
-
),
|
|
4504
|
-
)
|
|
4505
|
-
}
|
|
4506
|
-
}
|
|
4507
|
-
|
|
4508
|
-
private class OneKeyCheckboxView(context: android.content.Context) : View(context) {
|
|
4509
|
-
var usesSelectorGeometry = false
|
|
4510
|
-
private val glyphPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
4511
|
-
private var state = "unchecked"
|
|
4512
|
-
|
|
4513
|
-
fun setState(value: String, color: Int) {
|
|
4514
|
-
state = value
|
|
4515
|
-
glyphPaint.color = color
|
|
4516
|
-
invalidate()
|
|
4517
|
-
}
|
|
4518
|
-
|
|
4519
|
-
override fun onDraw(canvas: Canvas) {
|
|
4520
|
-
super.onDraw(canvas)
|
|
4521
|
-
val pathData = when (state) {
|
|
4522
|
-
"checked" -> "M12.204 5.043a1 1 0 0 1 0 1.414l-4.5 4.5a1 1 0 0 1-1.414 0l-2-2a1 1 0 1 1 1.414-1.414l1.293 1.293 3.793-3.793a1 1 0 0 1 1.414 0"
|
|
4523
|
-
"indeterminate" -> "M4 8a1 1 0 0 1 1-1h6a1 1 0 0 1 0 2H5a1 1 0 0 1-1-1"
|
|
4524
|
-
else -> return
|
|
4525
|
-
}
|
|
4526
|
-
// OneKey patch: the source icon is a 16dp child after the 2dp border, not 80% of a rounded frame.
|
|
4527
|
-
val drawSize = if (usesSelectorGeometry) (16 * resources.displayMetrics.density).roundToInt().toFloat() else minOf(width, height) * 0.8f
|
|
4528
|
-
val borderOffset = (2 * resources.displayMetrics.density).roundToInt().toFloat()
|
|
4529
|
-
canvas.save()
|
|
4530
|
-
canvas.translate(if (usesSelectorGeometry) borderOffset else (width - drawSize) / 2f, if (usesSelectorGeometry) borderOffset else (height - drawSize) / 2f)
|
|
4531
|
-
canvas.scale(drawSize / 16f, drawSize / 16f)
|
|
4532
|
-
PathParser.createPathFromPathData(pathData)?.let { canvas.drawPath(it, glyphPaint) }
|
|
4533
|
-
canvas.restore()
|
|
4534
|
-
}
|
|
4535
|
-
}
|
|
4536
|
-
|
|
4537
|
-
internal class NativeListViewHolder(val rowView: NativeListRowView) :
|
|
4538
|
-
androidx.recyclerview.widget.RecyclerView.ViewHolder(rowView)
|