@onekeyfe/react-native-native-list 3.0.105
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 +342 -0
- package/android/CMakeLists.txt +20 -0
- package/android/build.gradle +105 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/AndroidManifest.xml +1 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/onekey/nativelist/HybridNativeList.kt +125 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +165 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +147 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListPackage.kt +25 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +2939 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +1794 -0
- package/common/fonts/Roobert-Bold.ttf +0 -0
- package/common/fonts/Roobert-Medium.ttf +0 -0
- package/common/fonts/Roobert-Regular.ttf +0 -0
- package/common/fonts/Roobert-SemiBold.ttf +0 -0
- package/docs/DESIGN.md +129 -0
- package/ios/HybridNativeList.swift +131 -0
- package/ios/NativeListCell.swift +2858 -0
- package/ios/NativeListDesignAssets.swift +110 -0
- package/ios/NativeListModels.swift +215 -0
- package/ios/RNCNativeListView.swift +1737 -0
- package/ios/Resources/NativeListIcons.xcassets/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_bottom.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_arrow_top.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chart_trending_up.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_checked.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_checkbox_indeterminate.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_bottom.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_grabber_ver.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_right_small.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_chevron_top.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_dot_hor.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_drag.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_error_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_image_square_waves.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_info_circle.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_minus_circle_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_pencil.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_circle.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_plus_small.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_questionmark_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_shield_exclamation.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_speaker_promote.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_star_solid.imageset/icon.svg +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/Contents.json +1 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_swap_hor.imageset/icon.svg +1 -0
- package/lib/module/NativeList.js +183 -0
- package/lib/module/NativeList.nitro.js +4 -0
- package/lib/module/NativeList.types.js +4 -0
- package/lib/module/NativeList.web.js +167 -0
- package/lib/module/index.js +6 -0
- package/lib/module/models.js +2 -0
- package/lib/module/package.json +1 -0
- package/lib/module/scrolling.js +121 -0
- package/lib/module/selection.js +85 -0
- package/lib/module/validation.js +562 -0
- package/lib/module/web/NativeListWebEngine.js +2238 -0
- package/lib/nitrogen/generated/.gitattributes +1 -0
- package/lib/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
- package/lib/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
- package/lib/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
- package/lib/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
- package/lib/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
- package/lib/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
- package/lib/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
- package/lib/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
- package/lib/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
- package/lib/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
- package/lib/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
- package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
- package/lib/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
- package/lib/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
- package/lib/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
- package/lib/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
- package/lib/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
- package/lib/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
- package/lib/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
- package/lib/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
- package/lib/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
- package/lib/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeList.d.ts +18 -0
- package/lib/typescript/src/NativeList.nitro.d.ts +29 -0
- package/lib/typescript/src/NativeList.types.d.ts +87 -0
- package/lib/typescript/src/NativeList.web.d.ts +19 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/models.d.ts +456 -0
- package/lib/typescript/src/scrolling.d.ts +32 -0
- package/lib/typescript/src/selection.d.ts +27 -0
- package/lib/typescript/src/validation.d.ts +7 -0
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +203 -0
- package/nitro.json +23 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/c++/JFunc_void_std__string.hpp +76 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.cpp +201 -0
- package/nitrogen/generated/android/c++/JHybridNativeListSpec.hpp +84 -0
- package/nitrogen/generated/android/c++/JNativeListScrollAlignment.hpp +64 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.cpp +104 -0
- package/nitrogen/generated/android/c++/views/JHybridNativeListStateUpdater.hpp +54 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/Func_void_std__string.kt +78 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/HybridNativeListSpec.kt +177 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/NativeListScrollAlignment.kt +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/nativelistOnLoad.kt +35 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListManager.kt +96 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nativelist/views/HybridNativeListStateUpdater.kt +23 -0
- package/nitrogen/generated/android/nativelist+autolinking.cmake +83 -0
- package/nitrogen/generated/android/nativelist+autolinking.gradle +27 -0
- package/nitrogen/generated/android/nativelistOnLoad.cpp +58 -0
- package/nitrogen/generated/android/nativelistOnLoad.hpp +34 -0
- package/nitrogen/generated/ios/NativeListModule+autolinking.rb +62 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.cpp +41 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Bridge.hpp +91 -0
- package/nitrogen/generated/ios/NativeListModule-Swift-Cxx-Umbrella.hpp +49 -0
- package/nitrogen/generated/ios/NativeListModuleAutolinking.mm +33 -0
- package/nitrogen/generated/ios/NativeListModuleAutolinking.swift +26 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.cpp +11 -0
- package/nitrogen/generated/ios/c++/HybridNativeListSpecSwift.hpp +180 -0
- package/nitrogen/generated/ios/c++/views/HybridNativeListComponent.mm +180 -0
- package/nitrogen/generated/ios/swift/Func_void_std__string.swift +46 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec.swift +69 -0
- package/nitrogen/generated/ios/swift/HybridNativeListSpec_cxx.swift +447 -0
- package/nitrogen/generated/ios/swift/NativeListScrollAlignment.swift +48 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.cpp +43 -0
- package/nitrogen/generated/shared/c++/HybridNativeListSpec.hpp +87 -0
- package/nitrogen/generated/shared/c++/NativeListScrollAlignment.hpp +84 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.cpp +46 -0
- package/nitrogen/generated/shared/c++/views/HybridNativeListComponent.hpp +103 -0
- package/nitrogen/generated/shared/json/NativeListConfig.json +16 -0
- package/package.json +126 -0
- package/react-native-native-list.podspec +38 -0
- package/react-native.config.js +19 -0
- package/src/NativeList.nitro.ts +48 -0
- package/src/NativeList.tsx +298 -0
- package/src/NativeList.types.ts +125 -0
- package/src/NativeList.web.tsx +258 -0
- package/src/index.ts +35 -0
- package/src/models.ts +604 -0
- package/src/scrolling.ts +189 -0
- package/src/selection.ts +159 -0
- package/src/validation.ts +877 -0
- package/src/web/NativeListWebEngine.ts +3767 -0
|
@@ -0,0 +1,2939 @@
|
|
|
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.Outline
|
|
10
|
+
import android.graphics.Paint
|
|
11
|
+
import android.graphics.Path
|
|
12
|
+
import android.graphics.Typeface
|
|
13
|
+
import android.graphics.drawable.Drawable
|
|
14
|
+
import android.graphics.drawable.GradientDrawable
|
|
15
|
+
import android.text.Spannable
|
|
16
|
+
import android.text.SpannableStringBuilder
|
|
17
|
+
import android.text.TextUtils
|
|
18
|
+
import android.text.style.AbsoluteSizeSpan
|
|
19
|
+
import android.text.style.BackgroundColorSpan
|
|
20
|
+
import android.text.style.ForegroundColorSpan
|
|
21
|
+
import android.util.TypedValue
|
|
22
|
+
import android.view.Gravity
|
|
23
|
+
import android.view.MotionEvent
|
|
24
|
+
import android.view.View
|
|
25
|
+
import android.view.ViewGroup
|
|
26
|
+
import android.view.ViewOutlineProvider
|
|
27
|
+
import android.widget.FrameLayout
|
|
28
|
+
import android.widget.LinearLayout
|
|
29
|
+
import android.widget.ProgressBar
|
|
30
|
+
import android.widget.TextView
|
|
31
|
+
import com.facebook.react.uimanager.ThemedReactContext
|
|
32
|
+
import com.margelo.nitro.onekeyimage.OneKeyImageReusableView
|
|
33
|
+
import androidx.core.graphics.PathParser
|
|
34
|
+
import androidx.core.widget.TextViewCompat
|
|
35
|
+
import org.json.JSONArray
|
|
36
|
+
import org.json.JSONObject
|
|
37
|
+
import kotlin.math.roundToInt
|
|
38
|
+
|
|
39
|
+
internal data class NativeListActionOrigin(
|
|
40
|
+
val sourceView: View,
|
|
41
|
+
val ownerRowView: NativeListRowView,
|
|
42
|
+
val bindingEpoch: Long,
|
|
43
|
+
val source: String,
|
|
44
|
+
val slot: Int? = null,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
/** React Native color strings use CSS #RRGGBBAA ordering; Android expects #AARRGGBB. */
|
|
48
|
+
internal fun parseNativeListColor(value: String): Int {
|
|
49
|
+
val normalized = value.trim()
|
|
50
|
+
val androidValue = if (
|
|
51
|
+
normalized.length == 9 && normalized[0] == '#' &&
|
|
52
|
+
normalized.drop(1).all { it.isDigit() || it.lowercaseChar() in 'a'..'f' }
|
|
53
|
+
) {
|
|
54
|
+
"#${normalized.takeLast(2)}${normalized.substring(1, 7)}"
|
|
55
|
+
} else {
|
|
56
|
+
normalized
|
|
57
|
+
}
|
|
58
|
+
return Color.parseColor(androidValue)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
internal object NativeListScale {
|
|
62
|
+
private const val STANDARD_DP_WIDTH_THRESHOLD = 400
|
|
63
|
+
|
|
64
|
+
fun factor(resources: android.content.res.Resources): Float {
|
|
65
|
+
val widthDp = resources.configuration.screenWidthDp
|
|
66
|
+
return if (widthDp in 1 until STANDARD_DP_WIDTH_THRESHOLD) 0.9f else 1f
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fun dp(resources: android.content.res.Resources, value: Int): Int =
|
|
70
|
+
(value * factor(resources) * resources.displayMetrics.density).roundToInt()
|
|
71
|
+
|
|
72
|
+
fun dp(resources: android.content.res.Resources, value: Float): Float =
|
|
73
|
+
(value * factor(resources) * resources.displayMetrics.density).roundToInt().toFloat()
|
|
74
|
+
|
|
75
|
+
fun font(resources: android.content.res.Resources, value: Float): Float =
|
|
76
|
+
if (factor(resources) == 1f) value else (value * factor(resources)).roundToInt().toFloat()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
internal object NativeListFonts {
|
|
80
|
+
private val cache = mutableMapOf<String, Typeface>()
|
|
81
|
+
|
|
82
|
+
private fun load(context: android.content.Context, weight: String, fallback: String): Typeface =
|
|
83
|
+
cache.getOrPut(weight) {
|
|
84
|
+
try {
|
|
85
|
+
Typeface.createFromAsset(context.assets, "fonts/Roobert-$weight.ttf")
|
|
86
|
+
} catch (_: RuntimeException) {
|
|
87
|
+
Typeface.create(fallback, Typeface.NORMAL)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
fun regular(context: android.content.Context) = load(context, "Regular", "sans-serif")
|
|
92
|
+
fun medium(context: android.content.Context) = load(context, "Medium", "sans-serif-medium")
|
|
93
|
+
fun semibold(context: android.content.Context) = load(context, "SemiBold", "sans-serif-medium")
|
|
94
|
+
fun bold(context: android.content.Context) = load(context, "Bold", "sans-serif")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
internal data class NativeSelectionTarget(val scope: String, val key: String?)
|
|
98
|
+
|
|
99
|
+
private class DottedUnderlineTextView(context: android.content.Context) : TextView(context) {
|
|
100
|
+
var showsDottedUnderline = false
|
|
101
|
+
var dottedUnderlineColor = Color.TRANSPARENT
|
|
102
|
+
private val dottedPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
103
|
+
|
|
104
|
+
override fun onDraw(canvas: Canvas) {
|
|
105
|
+
super.onDraw(canvas)
|
|
106
|
+
if (!showsDottedUnderline || text.isEmpty()) return
|
|
107
|
+
dottedPaint.color = dottedUnderlineColor
|
|
108
|
+
val radius = NativeListScale.dp(resources, 0.75f)
|
|
109
|
+
val spacing = NativeListScale.dp(resources, 4f)
|
|
110
|
+
val lineWidth = paint.measureText(text.toString()).coerceAtMost(width.toFloat())
|
|
111
|
+
val y = height - radius
|
|
112
|
+
var x = NativeListScale.dp(resources, 1f)
|
|
113
|
+
while (x <= lineWidth - radius) {
|
|
114
|
+
canvas.drawCircle(x, y, radius, dottedPaint)
|
|
115
|
+
x += spacing
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
private class PackedTitleLineLayout(context: android.content.Context) : LinearLayout(context) {
|
|
121
|
+
var packsChildrenAtStart = false
|
|
122
|
+
|
|
123
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
124
|
+
if (!packsChildrenAtStart || childCount < 2) {
|
|
125
|
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
126
|
+
return
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
val title = getChildAt(0)
|
|
130
|
+
val badge = getChildAt(1)
|
|
131
|
+
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
|
|
132
|
+
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
|
|
133
|
+
if (badge.visibility != GONE) {
|
|
134
|
+
measureChildWithMargins(
|
|
135
|
+
badge,
|
|
136
|
+
widthMeasureSpec,
|
|
137
|
+
paddingLeft + paddingRight,
|
|
138
|
+
heightMeasureSpec,
|
|
139
|
+
paddingTop + paddingBottom,
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
val badgeMargins = badge.layoutParams as MarginLayoutParams
|
|
144
|
+
val badgeWidth = if (badge.visibility == GONE) 0 else {
|
|
145
|
+
badge.measuredWidth + badgeMargins.leftMargin + badgeMargins.rightMargin
|
|
146
|
+
}
|
|
147
|
+
val titleMargins = title.layoutParams as MarginLayoutParams
|
|
148
|
+
if (widthMode != MeasureSpec.UNSPECIFIED) {
|
|
149
|
+
(title as TextView).maxWidth = (widthSize - paddingLeft - paddingRight - badgeWidth -
|
|
150
|
+
titleMargins.leftMargin - titleMargins.rightMargin).coerceAtLeast(0)
|
|
151
|
+
}
|
|
152
|
+
(title.layoutParams as LayoutParams).apply {
|
|
153
|
+
width = LayoutParams.WRAP_CONTENT
|
|
154
|
+
weight = 0f
|
|
155
|
+
}
|
|
156
|
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
157
|
+
if (widthMode != MeasureSpec.UNSPECIFIED) {
|
|
158
|
+
setMeasuredDimension(widthSize, measuredHeight)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
private class NativeListTableColumnView(context: android.content.Context) : LinearLayout(context) {
|
|
164
|
+
private val primaryLine = LinearLayout(context)
|
|
165
|
+
private val primary = TextView(context)
|
|
166
|
+
private val badges = LinearLayout(context)
|
|
167
|
+
private val secondaryLine = LinearLayout(context)
|
|
168
|
+
private val secondaryLeading = TextView(context)
|
|
169
|
+
private val secondary = TextView(context)
|
|
170
|
+
|
|
171
|
+
init {
|
|
172
|
+
orientation = VERTICAL
|
|
173
|
+
primaryLine.orientation = HORIZONTAL
|
|
174
|
+
primaryLine.gravity = Gravity.CENTER_VERTICAL
|
|
175
|
+
badges.orientation = HORIZONTAL
|
|
176
|
+
badges.gravity = Gravity.CENTER_VERTICAL
|
|
177
|
+
primary.includeFontPadding = false
|
|
178
|
+
primary.fontFeatureSettings = "tnum"
|
|
179
|
+
primary.maxLines = 1
|
|
180
|
+
primary.ellipsize = TextUtils.TruncateAt.END
|
|
181
|
+
primary.textSize = sp(14f)
|
|
182
|
+
primary.typeface = NativeListFonts.medium(context)
|
|
183
|
+
TextViewCompat.setLineHeight(primary, dp(20))
|
|
184
|
+
secondaryLine.orientation = HORIZONTAL
|
|
185
|
+
secondaryLine.gravity = Gravity.CENTER_VERTICAL
|
|
186
|
+
listOf(secondaryLeading, secondary).forEach { label ->
|
|
187
|
+
label.includeFontPadding = false
|
|
188
|
+
label.fontFeatureSettings = "tnum"
|
|
189
|
+
label.maxLines = 1
|
|
190
|
+
label.ellipsize = TextUtils.TruncateAt.END
|
|
191
|
+
label.textSize = sp(12f)
|
|
192
|
+
label.typeface = NativeListFonts.regular(context)
|
|
193
|
+
TextViewCompat.setLineHeight(label, dp(16))
|
|
194
|
+
}
|
|
195
|
+
secondaryLeading.maxWidth = dp(120)
|
|
196
|
+
secondaryLine.addView(secondaryLeading, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)))
|
|
197
|
+
secondaryLine.addView(secondary, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)).apply {
|
|
198
|
+
marginStart = dp(4)
|
|
199
|
+
})
|
|
200
|
+
primaryLine.addView(primary, LayoutParams(LayoutParams.WRAP_CONTENT, dp(20)))
|
|
201
|
+
primaryLine.addView(badges, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)).apply {
|
|
202
|
+
marginStart = dp(6)
|
|
203
|
+
})
|
|
204
|
+
addView(primaryLine, LayoutParams(LayoutParams.WRAP_CONTENT, dp(20)))
|
|
205
|
+
addView(secondaryLine, LayoutParams(LayoutParams.MATCH_PARENT, dp(16)).apply {
|
|
206
|
+
topMargin = dp(4)
|
|
207
|
+
})
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
fun reset() {
|
|
211
|
+
primary.text = ""
|
|
212
|
+
secondaryLeading.text = ""
|
|
213
|
+
secondary.text = ""
|
|
214
|
+
secondaryLeading.visibility = GONE
|
|
215
|
+
secondary.visibility = GONE
|
|
216
|
+
secondaryLine.visibility = GONE
|
|
217
|
+
badges.removeAllViews()
|
|
218
|
+
badges.visibility = GONE
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
fun bind(
|
|
222
|
+
column: JSONObject,
|
|
223
|
+
rowBadges: JSONArray?,
|
|
224
|
+
primaryColor: Int,
|
|
225
|
+
secondaryColor: Int,
|
|
226
|
+
infoColor: Int,
|
|
227
|
+
) {
|
|
228
|
+
reset()
|
|
229
|
+
gravity = when (column.optString("alignment", "start")) {
|
|
230
|
+
"center" -> Gravity.CENTER_HORIZONTAL
|
|
231
|
+
"end" -> Gravity.END
|
|
232
|
+
else -> Gravity.START
|
|
233
|
+
}
|
|
234
|
+
primary.gravity = gravity
|
|
235
|
+
secondaryLeading.gravity = gravity
|
|
236
|
+
secondary.gravity = gravity
|
|
237
|
+
primary.text = column.optString("text")
|
|
238
|
+
primary.setTextColor(primaryColor)
|
|
239
|
+
if (rowBadges != null && rowBadges.length() > 0) {
|
|
240
|
+
badges.visibility = VISIBLE
|
|
241
|
+
for (index in 0 until minOf(2, rowBadges.length())) {
|
|
242
|
+
val badge = TextView(context).apply {
|
|
243
|
+
includeFontPadding = false
|
|
244
|
+
fontFeatureSettings = "tnum"
|
|
245
|
+
gravity = Gravity.CENTER
|
|
246
|
+
text = rowBadges.getJSONObject(index).optString("text")
|
|
247
|
+
textSize = sp(10f)
|
|
248
|
+
typeface = NativeListFonts.regular(context)
|
|
249
|
+
setTextColor(infoColor)
|
|
250
|
+
setPadding(dp(6), 0, dp(6), 0)
|
|
251
|
+
background = GradientDrawable().apply {
|
|
252
|
+
setColor(parseNativeListColor("#008FF519"))
|
|
253
|
+
cornerRadius = dp(4).toFloat()
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
badges.addView(badge, LayoutParams(LayoutParams.WRAP_CONTENT, dp(16)).apply {
|
|
257
|
+
if (index > 0) marginStart = dp(4)
|
|
258
|
+
})
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
val secondaryLeadingText = column.optString("secondaryLeadingText")
|
|
262
|
+
if (secondaryLeadingText.isNotEmpty()) {
|
|
263
|
+
secondaryLeading.text = secondaryLeadingText
|
|
264
|
+
secondaryLeading.setTextColor(secondaryColor)
|
|
265
|
+
secondaryLeading.visibility = VISIBLE
|
|
266
|
+
secondaryLine.visibility = VISIBLE
|
|
267
|
+
}
|
|
268
|
+
val secondaryText = column.optString("secondaryText")
|
|
269
|
+
if (secondaryText.isNotEmpty()) {
|
|
270
|
+
secondary.text = secondaryText
|
|
271
|
+
secondary.setTextColor(secondaryColor)
|
|
272
|
+
secondary.visibility = VISIBLE
|
|
273
|
+
secondaryLine.visibility = VISIBLE
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
private fun dp(value: Int): Int = NativeListScale.dp(resources, value)
|
|
278
|
+
private fun sp(value: Float): Float = NativeListScale.font(resources, value)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
internal class NativeListRowView(
|
|
282
|
+
private val reactContext: ThemedReactContext,
|
|
283
|
+
) : LinearLayout(reactContext) {
|
|
284
|
+
private val leadingFrame = FrameLayout(context)
|
|
285
|
+
private val leadingImages = List(3) { OneKeyImageReusableView(reactContext) }
|
|
286
|
+
private val leadingOverlayBackground = View(context)
|
|
287
|
+
private val leadingCornerIconFrame = FrameLayout(context)
|
|
288
|
+
private val leadingCornerIcon = OneKeyIconView(context)
|
|
289
|
+
private val leadingFallback = TextView(context)
|
|
290
|
+
private val leadingIcon = OneKeyIconView(context)
|
|
291
|
+
private val favoriteIcon = OneKeyIconView(context)
|
|
292
|
+
private val headerTitleIcon = OneKeyIconView(context)
|
|
293
|
+
private val headerValueIcon = OneKeyIconView(context)
|
|
294
|
+
private val leadingActionIcon = OneKeyIconView(context)
|
|
295
|
+
private val secondaryImage = OneKeyImageReusableView(reactContext)
|
|
296
|
+
private val mediaNetworkImage = OneKeyImageReusableView(reactContext)
|
|
297
|
+
private val metricVisualImages = List(5) { OneKeyImageReusableView(reactContext) }
|
|
298
|
+
private val mainColumn = LinearLayout(context)
|
|
299
|
+
private val mediaMetadataRow = LinearLayout(context)
|
|
300
|
+
private val titleLine = PackedTitleLineLayout(context)
|
|
301
|
+
private val title = DottedUnderlineTextView(context)
|
|
302
|
+
private val subtitle = TextView(context)
|
|
303
|
+
private val tertiary = TextView(context)
|
|
304
|
+
private val status = TextView(context)
|
|
305
|
+
private val metricSubtitle = TextView(context)
|
|
306
|
+
private val badgeLine = TextView(context)
|
|
307
|
+
private val activityContentRow = LinearLayout(context)
|
|
308
|
+
private val actionLine = LinearLayout(context)
|
|
309
|
+
private val actionViews = List(3) { TextView(context) }
|
|
310
|
+
private val trailingColumn = LinearLayout(context)
|
|
311
|
+
private val trailingViews = List(2) { TextView(context) }
|
|
312
|
+
private val trailingIcons = List(2) { OneKeyIconView(context) }
|
|
313
|
+
private val checkbox = OneKeyCheckboxView(context)
|
|
314
|
+
private val spinner = ProgressBar(context)
|
|
315
|
+
private val dataContainer = LinearLayout(context)
|
|
316
|
+
private val dataColumns = List(4) { TextView(context) }
|
|
317
|
+
private val tableDataContainer = LinearLayout(context)
|
|
318
|
+
private val tableDataColumns = List(4) { NativeListTableColumnView(context) }
|
|
319
|
+
private val unreadDot = View(context)
|
|
320
|
+
private val mediaBadge = TextView(context)
|
|
321
|
+
private val skeletonPrimary = View(context)
|
|
322
|
+
private val skeletonSecondary = View(context)
|
|
323
|
+
private val walletGroupRows = mutableListOf<NativeListRowView>()
|
|
324
|
+
private val walletGroupDragBadgeBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
325
|
+
private val walletGroupDragBadgeBorderPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
326
|
+
style = Paint.Style.STROKE
|
|
327
|
+
}
|
|
328
|
+
private val walletGroupDragBadgeTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
329
|
+
textAlign = Paint.Align.CENTER
|
|
330
|
+
typeface = NativeListFonts.semibold(context)
|
|
331
|
+
}
|
|
332
|
+
private var walletGroupDragChildCount = 0
|
|
333
|
+
private var walletGroupExpandedHeightPx = 0
|
|
334
|
+
private var walletGroupExpandAnimator: ValueAnimator? = null
|
|
335
|
+
private var isMediaTile = false
|
|
336
|
+
private var boundKey: String? = null
|
|
337
|
+
var bindingEpoch: Long = 0
|
|
338
|
+
private set
|
|
339
|
+
private var boundCheckboxData: JSONObject? = null
|
|
340
|
+
private var currentLayout = "linear"
|
|
341
|
+
private var restingRowBackground: Drawable? = null
|
|
342
|
+
private var pressedRowBackground: Drawable? = null
|
|
343
|
+
private var reorderActive = false
|
|
344
|
+
private var checkboxCheckedColor = Color.rgb(32, 32, 32)
|
|
345
|
+
private var checkboxUncheckedColor = Color.rgb(252, 252, 252)
|
|
346
|
+
private var checkboxBorderColor = Color.rgb(206, 206, 206)
|
|
347
|
+
private var iconSubduedColor = Color.rgb(141, 141, 141)
|
|
348
|
+
private var visualBackdropColor = Color.WHITE
|
|
349
|
+
private val circleOutlineProvider = object : ViewOutlineProvider() {
|
|
350
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
351
|
+
outline.setOval(0, 0, view.width, view.height)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
private val separatorPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
355
|
+
private var showsSeparator = false
|
|
356
|
+
|
|
357
|
+
var onRowPress: ((NativeListItem, NativeListActionOrigin) -> Unit)? = null
|
|
358
|
+
var onAction: ((NativeListItem, String, NativeSelectionTarget?, NativeListActionOrigin?) -> Unit)? = null
|
|
359
|
+
var onBindingInvalidated: ((NativeListRowView, Long) -> Unit)? = null
|
|
360
|
+
|
|
361
|
+
init {
|
|
362
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
363
|
+
isClickable = true
|
|
364
|
+
isFocusable = true
|
|
365
|
+
|
|
366
|
+
leadingFallback.gravity = Gravity.CENTER
|
|
367
|
+
leadingFallback.typeface = NativeListFonts.bold(context)
|
|
368
|
+
leadingFrame.addView(leadingFallback)
|
|
369
|
+
leadingFrame.addView(leadingIcon)
|
|
370
|
+
leadingFrame.addView(leadingImages[0])
|
|
371
|
+
leadingFrame.addView(leadingOverlayBackground)
|
|
372
|
+
leadingFrame.addView(leadingImages[1])
|
|
373
|
+
leadingFrame.addView(leadingImages[2])
|
|
374
|
+
leadingCornerIconFrame.addView(leadingCornerIcon)
|
|
375
|
+
leadingFrame.addView(leadingCornerIconFrame)
|
|
376
|
+
leadingFrame.addView(unreadDot)
|
|
377
|
+
leadingFrame.addView(mediaBadge)
|
|
378
|
+
leadingFrame.clipChildren = false
|
|
379
|
+
leadingFrame.clipToPadding = false
|
|
380
|
+
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(18), dp(18), Gravity.CENTER)
|
|
381
|
+
favoriteIcon.layoutParams = LayoutParams(dp(24), dp(24))
|
|
382
|
+
leadingActionIcon.layoutParams = LayoutParams(dp(24), dp(24))
|
|
383
|
+
leadingCornerIcon.layoutParams = FrameLayout.LayoutParams(dp(18), dp(18), Gravity.CENTER)
|
|
384
|
+
unreadDot.layoutParams = FrameLayout.LayoutParams(dp(8), dp(8), Gravity.TOP or Gravity.END)
|
|
385
|
+
mainColumn.orientation = VERTICAL
|
|
386
|
+
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
387
|
+
title.typeface = NativeListFonts.regular(context)
|
|
388
|
+
subtitle.typeface = NativeListFonts.regular(context)
|
|
389
|
+
tertiary.typeface = NativeListFonts.regular(context)
|
|
390
|
+
status.typeface = NativeListFonts.regular(context)
|
|
391
|
+
metricSubtitle.typeface = NativeListFonts.regular(context)
|
|
392
|
+
badgeLine.typeface = NativeListFonts.medium(context)
|
|
393
|
+
listOf(title, subtitle, tertiary, status, metricSubtitle, badgeLine).forEach {
|
|
394
|
+
it.includeFontPadding = false
|
|
395
|
+
it.fontFeatureSettings = "tnum"
|
|
396
|
+
}
|
|
397
|
+
subtitle.maxLines = 2
|
|
398
|
+
status.maxLines = 1
|
|
399
|
+
metricSubtitle.maxLines = 1
|
|
400
|
+
badgeLine.maxLines = 1
|
|
401
|
+
titleLine.orientation = HORIZONTAL
|
|
402
|
+
titleLine.gravity = Gravity.CENTER_VERTICAL
|
|
403
|
+
titleLine.addView(title, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f))
|
|
404
|
+
titleLine.addView(badgeLine, wrap())
|
|
405
|
+
mainColumn.addView(titleLine)
|
|
406
|
+
mainColumn.addView(subtitle)
|
|
407
|
+
mainColumn.addView(tertiary)
|
|
408
|
+
mainColumn.addView(status)
|
|
409
|
+
mainColumn.addView(metricSubtitle)
|
|
410
|
+
|
|
411
|
+
actionLine.orientation = HORIZONTAL
|
|
412
|
+
actionViews.forEach { action ->
|
|
413
|
+
action.typeface = NativeListFonts.semibold(context)
|
|
414
|
+
action.includeFontPadding = false
|
|
415
|
+
action.textSize = sp(14f)
|
|
416
|
+
TextViewCompat.setLineHeight(action, dp(20))
|
|
417
|
+
action.setPadding(dp(8), dp(4), dp(8), dp(4))
|
|
418
|
+
actionLine.addView(action)
|
|
419
|
+
}
|
|
420
|
+
mainColumn.addView(actionLine)
|
|
421
|
+
|
|
422
|
+
trailingColumn.orientation = VERTICAL
|
|
423
|
+
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
424
|
+
trailingViews.forEach { trailingColumn.addView(it) }
|
|
425
|
+
trailingIcons.forEach { trailingColumn.addView(it) }
|
|
426
|
+
trailingColumn.addView(checkbox)
|
|
427
|
+
trailingColumn.addView(spinner)
|
|
428
|
+
|
|
429
|
+
dataContainer.orientation = HORIZONTAL
|
|
430
|
+
dataContainer.gravity = Gravity.CENTER_VERTICAL
|
|
431
|
+
dataColumns.forEach { dataContainer.addView(it) }
|
|
432
|
+
tableDataContainer.orientation = HORIZONTAL
|
|
433
|
+
tableDataContainer.gravity = Gravity.CENTER_VERTICAL
|
|
434
|
+
tableDataColumns.forEach { tableDataContainer.addView(it) }
|
|
435
|
+
|
|
436
|
+
mediaBadge.gravity = Gravity.CENTER
|
|
437
|
+
mediaBadge.typeface = NativeListFonts.regular(context)
|
|
438
|
+
secondaryImage.outlineProvider = object : ViewOutlineProvider() {
|
|
439
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
440
|
+
outline.setRoundRect(0, 0, view.width, view.height, dp(6).toFloat())
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
secondaryImage.clipToOutline = true
|
|
444
|
+
|
|
445
|
+
setOnTouchListener { _, event ->
|
|
446
|
+
when (event.actionMasked) {
|
|
447
|
+
MotionEvent.ACTION_DOWN -> if (isEnabled) {
|
|
448
|
+
if ((tag as? NativeListItem)?.type == "mediaTile") {
|
|
449
|
+
leadingFrame.alpha = 0.8f
|
|
450
|
+
} else {
|
|
451
|
+
background = pressedRowBackground
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
MotionEvent.ACTION_MOVE -> if (
|
|
455
|
+
event.x < 0 || event.y < 0 || event.x >= width || event.y >= height
|
|
456
|
+
) {
|
|
457
|
+
restoreRestingBackground()
|
|
458
|
+
}
|
|
459
|
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> restoreRestingBackground()
|
|
460
|
+
}
|
|
461
|
+
false
|
|
462
|
+
}
|
|
463
|
+
setOnClickListener { view ->
|
|
464
|
+
(view.tag as? NativeListItem)?.let { item ->
|
|
465
|
+
onRowPress?.invoke(item, actionOrigin(view, "row"))
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
setWillNotDraw(false)
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
override fun dispatchDraw(canvas: Canvas) {
|
|
472
|
+
super.dispatchDraw(canvas)
|
|
473
|
+
if (showsSeparator) {
|
|
474
|
+
val start = if ((tag as? NativeListItem)?.type == "identity") dp(60).toFloat() else dp(12).toFloat()
|
|
475
|
+
canvas.drawLine(start, height - 1f, width.toFloat(), height - 1f, separatorPaint)
|
|
476
|
+
}
|
|
477
|
+
if (reorderActive && (tag as? NativeListItem)?.type == "walletGroup" && walletGroupDragChildCount > 0) {
|
|
478
|
+
val label = "+$walletGroupDragChildCount"
|
|
479
|
+
val badgeHeight = dp(24).toFloat()
|
|
480
|
+
val badgeWidth = maxOf(
|
|
481
|
+
dp(24).toFloat(),
|
|
482
|
+
walletGroupDragBadgeTextPaint.measureText(label) + dp(12),
|
|
483
|
+
)
|
|
484
|
+
val right = width - dp(4).toFloat()
|
|
485
|
+
val bottom = height - dp(4).toFloat()
|
|
486
|
+
val left = right - badgeWidth
|
|
487
|
+
val top = bottom - badgeHeight
|
|
488
|
+
canvas.drawRoundRect(
|
|
489
|
+
left,
|
|
490
|
+
top,
|
|
491
|
+
right,
|
|
492
|
+
bottom,
|
|
493
|
+
badgeHeight / 2f,
|
|
494
|
+
badgeHeight / 2f,
|
|
495
|
+
walletGroupDragBadgeBackgroundPaint,
|
|
496
|
+
)
|
|
497
|
+
canvas.drawRoundRect(
|
|
498
|
+
left,
|
|
499
|
+
top,
|
|
500
|
+
right,
|
|
501
|
+
bottom,
|
|
502
|
+
dp(12).toFloat(),
|
|
503
|
+
dp(12).toFloat(),
|
|
504
|
+
walletGroupDragBadgeBorderPaint,
|
|
505
|
+
)
|
|
506
|
+
val metrics = walletGroupDragBadgeTextPaint.fontMetrics
|
|
507
|
+
val baseline = (top + bottom - metrics.ascent - metrics.descent) / 2f
|
|
508
|
+
canvas.drawText(label, (left + right) / 2f, baseline, walletGroupDragBadgeTextPaint)
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
513
|
+
if (isMediaTile) {
|
|
514
|
+
val availableWidth = (MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight)
|
|
515
|
+
.coerceAtLeast(0)
|
|
516
|
+
leadingFrame.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, availableWidth)
|
|
517
|
+
}
|
|
518
|
+
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
fun bind(
|
|
522
|
+
item: NativeListItem,
|
|
523
|
+
theme: JSONObject?,
|
|
524
|
+
layout: String,
|
|
525
|
+
listOrientation: String,
|
|
526
|
+
itemIndex: Int?,
|
|
527
|
+
selected: Boolean,
|
|
528
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
529
|
+
) {
|
|
530
|
+
invalidateCurrentBinding()
|
|
531
|
+
bindingEpoch += 1
|
|
532
|
+
boundKey = item.key
|
|
533
|
+
currentLayout = layout
|
|
534
|
+
tag = item
|
|
535
|
+
reorderActive = false
|
|
536
|
+
leadingImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
537
|
+
secondaryImage.prepareForReuse()
|
|
538
|
+
mediaNetworkImage.prepareForReuse()
|
|
539
|
+
metricVisualImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
540
|
+
resetViews()
|
|
541
|
+
|
|
542
|
+
val primary = color(theme, "primaryText", "#000000DF")
|
|
543
|
+
val secondary = color(theme, "secondaryText", "#0000009B")
|
|
544
|
+
val accent = color(theme, "accent", "#0D8200FC")
|
|
545
|
+
checkboxCheckedColor = primary
|
|
546
|
+
checkboxUncheckedColor = color(theme, "inverseText", "#FCFCFC")
|
|
547
|
+
checkboxBorderColor = Color.argb(
|
|
548
|
+
0x31,
|
|
549
|
+
0,
|
|
550
|
+
0,
|
|
551
|
+
0,
|
|
552
|
+
)
|
|
553
|
+
iconSubduedColor = color(theme, "iconSubdued", "#00000072")
|
|
554
|
+
visualBackdropColor = color(theme, "rowBackground", "#FFFFFF")
|
|
555
|
+
unreadDot.background = roundedFill(
|
|
556
|
+
parseNativeListColor("#E5484D"),
|
|
557
|
+
4f,
|
|
558
|
+
)
|
|
559
|
+
applySelectionState(item, theme, layout, itemIndex, selected)
|
|
560
|
+
pressedRowBackground = groupedBackground(
|
|
561
|
+
when (item.type) {
|
|
562
|
+
"rail" -> "rail"
|
|
563
|
+
"mediaTile" -> "mediaTile"
|
|
564
|
+
else -> "single"
|
|
565
|
+
},
|
|
566
|
+
color(
|
|
567
|
+
theme,
|
|
568
|
+
if (item.type == "rail") "strongBackground" else "rowPressedBackground",
|
|
569
|
+
if (item.type == "rail") "#0000000F" else "#00000017",
|
|
570
|
+
),
|
|
571
|
+
)
|
|
572
|
+
background = restingRowBackground
|
|
573
|
+
if (layout == "table") {
|
|
574
|
+
if (item.type == "dataRow") {
|
|
575
|
+
setPadding(dp(20), dp(10), dp(20), dp(10))
|
|
576
|
+
} else {
|
|
577
|
+
setPadding(dp(16), dp(8), dp(16), dp(8))
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
title.setTextColor(primary)
|
|
581
|
+
subtitle.setTextColor(secondary)
|
|
582
|
+
tertiary.setTextColor(secondary)
|
|
583
|
+
status.setTextColor(secondary)
|
|
584
|
+
metricSubtitle.setTextColor(secondary)
|
|
585
|
+
badgeLine.setTextColor(accent)
|
|
586
|
+
separatorPaint.color = color(theme, "separator", "#0000001F")
|
|
587
|
+
separatorPaint.strokeWidth = 1f
|
|
588
|
+
showsSeparator = item.json.optBoolean("separator", false) &&
|
|
589
|
+
!item.key.startsWith("token-") &&
|
|
590
|
+
!item.key.startsWith("balance-token-") &&
|
|
591
|
+
item.key != "linear-custom-token"
|
|
592
|
+
invalidate()
|
|
593
|
+
trailingViews.forEach { it.setTextColor(primary) }
|
|
594
|
+
isEnabled = !item.json.optBoolean("disabled", false)
|
|
595
|
+
alpha = if (isEnabled) 1f else 0.5f
|
|
596
|
+
contentDescription = item.json.optString("accessibilityLabel", item.json.optString("title"))
|
|
597
|
+
|
|
598
|
+
when (item.type) {
|
|
599
|
+
"walletGroup" -> bindWalletGroup(item, theme, layout, listOrientation, checkboxState)
|
|
600
|
+
"identity" -> bindIdentity(item, theme, selected, checkboxState)
|
|
601
|
+
"rail" -> bindRail(item, theme)
|
|
602
|
+
"activity" -> bindActivity(item, theme)
|
|
603
|
+
"message" -> bindMessage(item, theme)
|
|
604
|
+
"dataRow" -> bindDataRow(item, theme, checkboxState)
|
|
605
|
+
"mediaTile" -> bindMediaTile(item, theme)
|
|
606
|
+
"metricCard" -> bindMetricCard(item, theme)
|
|
607
|
+
"sectionHeader" -> bindSectionHeader(item, theme, checkboxState)
|
|
608
|
+
"action" -> bindAction(item, theme, checkboxState)
|
|
609
|
+
"system" -> bindSystem(item, theme)
|
|
610
|
+
}
|
|
611
|
+
applySize(item)
|
|
612
|
+
applyListOrientation(item, listOrientation)
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
fun recycle() {
|
|
616
|
+
restoreRestingBackground()
|
|
617
|
+
invalidateCurrentBinding()
|
|
618
|
+
boundKey = null
|
|
619
|
+
leadingImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
620
|
+
secondaryImage.prepareForReuse()
|
|
621
|
+
mediaNetworkImage.prepareForReuse()
|
|
622
|
+
metricVisualImages.forEach(OneKeyImageReusableView::prepareForReuse)
|
|
623
|
+
walletGroupRows.forEach { row ->
|
|
624
|
+
row.visibility = VISIBLE
|
|
625
|
+
row.alpha = 1f
|
|
626
|
+
row.recycle()
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
fun bindSelection(
|
|
631
|
+
item: NativeListItem,
|
|
632
|
+
theme: JSONObject?,
|
|
633
|
+
layout: String,
|
|
634
|
+
itemIndex: Int?,
|
|
635
|
+
selected: Boolean,
|
|
636
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
637
|
+
) {
|
|
638
|
+
if (boundKey != item.key) return
|
|
639
|
+
if (item.type == "walletGroup") {
|
|
640
|
+
val members = buildList {
|
|
641
|
+
add(item.json.getJSONObject("parent"))
|
|
642
|
+
item.json.getJSONArray("children").let { children ->
|
|
643
|
+
for (index in 0 until children.length()) add(children.getJSONObject(index))
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
members.forEachIndexed { index, memberJson ->
|
|
647
|
+
walletGroupRows.getOrNull(index)?.bindSelection(
|
|
648
|
+
NativeListItem.parse(memberJson),
|
|
649
|
+
theme,
|
|
650
|
+
layout,
|
|
651
|
+
null,
|
|
652
|
+
memberJson.optBoolean("selected", false),
|
|
653
|
+
checkboxState,
|
|
654
|
+
)
|
|
655
|
+
}
|
|
656
|
+
// The group owns the subdued backdrop. Selection belongs to a member;
|
|
657
|
+
// applying the translucent selected color to both levels produces dark
|
|
658
|
+
// bands in the 12dp gaps and double-composites the parent highlight.
|
|
659
|
+
background = restingRowBackground
|
|
660
|
+
return
|
|
661
|
+
}
|
|
662
|
+
applySelectionState(item, theme, layout, itemIndex, selected)
|
|
663
|
+
if (item.type == "identity" && item.json.optString("presentation") == "walletSidebar") {
|
|
664
|
+
title.setTextColor(
|
|
665
|
+
color(
|
|
666
|
+
theme,
|
|
667
|
+
if (selected) "primaryText" else "secondaryText",
|
|
668
|
+
if (selected) "#FFFFFFED" else "#FFFFFFAF",
|
|
669
|
+
),
|
|
670
|
+
)
|
|
671
|
+
}
|
|
672
|
+
boundCheckboxData?.let { bindCheckbox(item, it, checkboxState) }
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
fun bindStableSummary(item: NativeListItem) {
|
|
676
|
+
if (
|
|
677
|
+
boundKey != item.key ||
|
|
678
|
+
item.type != "sectionHeader" ||
|
|
679
|
+
item.json.optString("variant") != "summary"
|
|
680
|
+
) {
|
|
681
|
+
return
|
|
682
|
+
}
|
|
683
|
+
tag = item
|
|
684
|
+
contentDescription = item.json.optString("accessibilityLabel", item.json.optString("title"))
|
|
685
|
+
title.text = item.json.optString("title")
|
|
686
|
+
trailingViews[0].text = item.json.optString("value")
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
fun dispose() {
|
|
690
|
+
restoreRestingBackground()
|
|
691
|
+
leadingImages.forEach(OneKeyImageReusableView::dispose)
|
|
692
|
+
secondaryImage.dispose()
|
|
693
|
+
mediaNetworkImage.dispose()
|
|
694
|
+
metricVisualImages.forEach(OneKeyImageReusableView::dispose)
|
|
695
|
+
walletGroupRows.forEach(NativeListRowView::dispose)
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
private fun actionOrigin(
|
|
699
|
+
sourceView: View,
|
|
700
|
+
source: String,
|
|
701
|
+
slot: Int? = null,
|
|
702
|
+
) = NativeListActionOrigin(sourceView, this, bindingEpoch, source, slot)
|
|
703
|
+
|
|
704
|
+
private fun emitAction(
|
|
705
|
+
item: NativeListItem,
|
|
706
|
+
actionKey: String,
|
|
707
|
+
target: NativeSelectionTarget?,
|
|
708
|
+
sourceView: View,
|
|
709
|
+
source: String,
|
|
710
|
+
slot: Int? = null,
|
|
711
|
+
) {
|
|
712
|
+
onAction?.invoke(item, actionKey, target, actionOrigin(sourceView, source, slot))
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
private fun invalidateCurrentBinding() {
|
|
716
|
+
if (boundKey == null) return
|
|
717
|
+
onBindingInvalidated?.invoke(this, bindingEpoch)
|
|
718
|
+
bindingEpoch += 1
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
private fun resetViews() {
|
|
722
|
+
walletGroupRows.forEach { it.invalidateCurrentBinding() }
|
|
723
|
+
walletGroupExpandAnimator?.removeAllListeners()
|
|
724
|
+
walletGroupExpandAnimator?.cancel()
|
|
725
|
+
walletGroupExpandAnimator = null
|
|
726
|
+
layoutParams?.takeIf { it.height != ViewGroup.LayoutParams.WRAP_CONTENT }?.let { params ->
|
|
727
|
+
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
728
|
+
layoutParams = params
|
|
729
|
+
}
|
|
730
|
+
restoreRestingBackground()
|
|
731
|
+
walletGroupRows.forEach { row ->
|
|
732
|
+
row.visibility = VISIBLE
|
|
733
|
+
row.alpha = 1f
|
|
734
|
+
row.recycle()
|
|
735
|
+
}
|
|
736
|
+
activityContentRow.removeAllViews()
|
|
737
|
+
(actionLine.parent as? ViewGroup)?.removeView(actionLine)
|
|
738
|
+
removeAllViews()
|
|
739
|
+
orientation = HORIZONTAL
|
|
740
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
741
|
+
minimumHeight = 0
|
|
742
|
+
setPadding(dp(12), dp(8), dp(12), dp(8))
|
|
743
|
+
isMediaTile = false
|
|
744
|
+
walletGroupDragChildCount = 0
|
|
745
|
+
walletGroupExpandedHeightPx = 0
|
|
746
|
+
boundCheckboxData = null
|
|
747
|
+
mainColumn.orientation = VERTICAL
|
|
748
|
+
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
749
|
+
mediaMetadataRow.removeView(subtitle)
|
|
750
|
+
mediaMetadataRow.removeView(mediaNetworkImage)
|
|
751
|
+
mainColumn.removeView(mediaMetadataRow)
|
|
752
|
+
mainColumn.removeView(titleLine)
|
|
753
|
+
mainColumn.removeView(subtitle)
|
|
754
|
+
mainColumn.removeView(tertiary)
|
|
755
|
+
mainColumn.addView(titleLine, 0)
|
|
756
|
+
mainColumn.addView(subtitle, 1)
|
|
757
|
+
mainColumn.addView(tertiary, 2)
|
|
758
|
+
mainColumn.addView(actionLine)
|
|
759
|
+
titleLine.packsChildrenAtStart = false
|
|
760
|
+
titleLine.removeView(headerTitleIcon)
|
|
761
|
+
trailingColumn.removeView(headerValueIcon)
|
|
762
|
+
title.gravity = Gravity.START
|
|
763
|
+
title.ellipsize = null
|
|
764
|
+
title.maxWidth = Int.MAX_VALUE
|
|
765
|
+
title.setHorizontallyScrolling(false)
|
|
766
|
+
title.text = ""
|
|
767
|
+
subtitle.text = ""
|
|
768
|
+
tertiary.text = ""
|
|
769
|
+
status.text = ""
|
|
770
|
+
metricSubtitle.text = ""
|
|
771
|
+
badgeLine.text = ""
|
|
772
|
+
title.setLineSpacing(0f, 1f)
|
|
773
|
+
subtitle.setLineSpacing(0f, 1f)
|
|
774
|
+
title.letterSpacing = 0f
|
|
775
|
+
title.showsDottedUnderline = false
|
|
776
|
+
title.setPadding(0, 0, 0, 0)
|
|
777
|
+
subtitle.letterSpacing = 0f
|
|
778
|
+
status.letterSpacing = 0f
|
|
779
|
+
badgeLine.letterSpacing = 0f
|
|
780
|
+
status.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
781
|
+
subtitle.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
782
|
+
title.layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
|
783
|
+
badgeLine.layoutParams = wrap()
|
|
784
|
+
titleLine.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
785
|
+
title.visibility = GONE
|
|
786
|
+
subtitle.visibility = GONE
|
|
787
|
+
tertiary.visibility = GONE
|
|
788
|
+
status.visibility = GONE
|
|
789
|
+
metricSubtitle.visibility = GONE
|
|
790
|
+
badgeLine.visibility = GONE
|
|
791
|
+
badgeLine.background = null
|
|
792
|
+
badgeLine.setPadding(0, 0, 0, 0)
|
|
793
|
+
actionLine.visibility = GONE
|
|
794
|
+
actionViews.forEach {
|
|
795
|
+
it.visibility = GONE
|
|
796
|
+
it.background = null
|
|
797
|
+
it.setOnClickListener(null)
|
|
798
|
+
}
|
|
799
|
+
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
800
|
+
trailingColumn.orientation = VERTICAL
|
|
801
|
+
trailingColumn.layoutParams = wrap()
|
|
802
|
+
trailingViews.forEach {
|
|
803
|
+
it.visibility = GONE
|
|
804
|
+
it.gravity = Gravity.END
|
|
805
|
+
it.maxLines = 1
|
|
806
|
+
it.layoutParams = wrap()
|
|
807
|
+
it.background = null
|
|
808
|
+
it.setPadding(0, 0, 0, 0)
|
|
809
|
+
it.setOnClickListener(null)
|
|
810
|
+
}
|
|
811
|
+
trailingIcons.forEach {
|
|
812
|
+
it.visibility = GONE
|
|
813
|
+
it.layoutParams = LayoutParams(dp(24), dp(24)).apply { gravity = Gravity.END }
|
|
814
|
+
it.glyphSizeDp = null
|
|
815
|
+
it.alpha = 1f
|
|
816
|
+
it.isEnabled = true
|
|
817
|
+
it.setOnClickListener(null)
|
|
818
|
+
}
|
|
819
|
+
checkbox.visibility = GONE
|
|
820
|
+
checkbox.layoutParams = LayoutParams(dp(20), dp(20)).apply { gravity = Gravity.END }
|
|
821
|
+
checkbox.alpha = 1f
|
|
822
|
+
checkbox.setOnClickListener(null)
|
|
823
|
+
spinner.visibility = GONE
|
|
824
|
+
spinner.layoutParams = LayoutParams(dp(20), dp(20)).apply { gravity = Gravity.END }
|
|
825
|
+
spinner.alpha = 1f
|
|
826
|
+
leadingFrame.visibility = GONE
|
|
827
|
+
leadingFrame.background = null
|
|
828
|
+
leadingFrame.clipChildren = false
|
|
829
|
+
leadingFrame.clipToPadding = false
|
|
830
|
+
secondaryImage.visibility = GONE
|
|
831
|
+
secondaryImage.foreground = null
|
|
832
|
+
mediaNetworkImage.visibility = GONE
|
|
833
|
+
metricVisualImages.forEach {
|
|
834
|
+
(it.parent as? ViewGroup)?.removeView(it)
|
|
835
|
+
it.visibility = GONE
|
|
836
|
+
it.background = null
|
|
837
|
+
it.clipToOutline = false
|
|
838
|
+
it.outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
839
|
+
}
|
|
840
|
+
unreadDot.visibility = GONE
|
|
841
|
+
mediaBadge.visibility = GONE
|
|
842
|
+
mediaBadge.text = ""
|
|
843
|
+
leadingIcon.visibility = GONE
|
|
844
|
+
leadingIcon.iconName = ""
|
|
845
|
+
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(18), dp(18), Gravity.CENTER)
|
|
846
|
+
favoriteIcon.visibility = GONE
|
|
847
|
+
favoriteIcon.iconName = ""
|
|
848
|
+
headerTitleIcon.visibility = GONE
|
|
849
|
+
headerTitleIcon.iconName = ""
|
|
850
|
+
headerValueIcon.visibility = GONE
|
|
851
|
+
headerValueIcon.iconName = ""
|
|
852
|
+
leadingActionIcon.visibility = GONE
|
|
853
|
+
leadingActionIcon.iconName = ""
|
|
854
|
+
leadingActionIcon.glyphSizeDp = 24
|
|
855
|
+
leadingActionIcon.setOnClickListener(null)
|
|
856
|
+
mainColumn.visibility = VISIBLE
|
|
857
|
+
dataContainer.visibility = GONE
|
|
858
|
+
dataColumns.forEach { it.visibility = GONE }
|
|
859
|
+
tableDataContainer.visibility = GONE
|
|
860
|
+
tableDataColumns.forEach {
|
|
861
|
+
it.reset()
|
|
862
|
+
it.visibility = GONE
|
|
863
|
+
}
|
|
864
|
+
leadingOverlayBackground.visibility = GONE
|
|
865
|
+
leadingCornerIconFrame.visibility = GONE
|
|
866
|
+
leadingCornerIcon.iconName = ""
|
|
867
|
+
leadingImages.forEach {
|
|
868
|
+
it.visibility = GONE
|
|
869
|
+
it.clipToOutline = false
|
|
870
|
+
it.outlineProvider = ViewOutlineProvider.BACKGROUND
|
|
871
|
+
}
|
|
872
|
+
leadingFallback.text = ""
|
|
873
|
+
leadingFallback.textSize = sp(13f)
|
|
874
|
+
leadingFallback.typeface = NativeListFonts.bold(context)
|
|
875
|
+
leadingFallback.visibility = GONE
|
|
876
|
+
leadingFallback.background = null
|
|
877
|
+
showsSeparator = false
|
|
878
|
+
mainColumn.removeView(skeletonPrimary)
|
|
879
|
+
mainColumn.removeView(skeletonSecondary)
|
|
880
|
+
setOnClickListener { view ->
|
|
881
|
+
(view.tag as? NativeListItem)?.let { item ->
|
|
882
|
+
onRowPress?.invoke(item, actionOrigin(view, "row"))
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
private fun restoreRestingBackground() {
|
|
888
|
+
background = if (reorderActive) pressedRowBackground else restingRowBackground
|
|
889
|
+
leadingFrame.alpha = 1f
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
fun setReorderActive(active: Boolean) {
|
|
893
|
+
if ((tag as? NativeListItem)?.type == "walletGroup" && reorderActive == active) return
|
|
894
|
+
reorderActive = active
|
|
895
|
+
if ((tag as? NativeListItem)?.type == "walletGroup") {
|
|
896
|
+
walletGroupExpandAnimator?.removeAllListeners()
|
|
897
|
+
walletGroupExpandAnimator?.cancel()
|
|
898
|
+
walletGroupExpandAnimator = null
|
|
899
|
+
walletGroupRows.forEachIndexed { index, row ->
|
|
900
|
+
row.alpha = 1f
|
|
901
|
+
row.visibility = if (active && index > 0) GONE else VISIBLE
|
|
902
|
+
if (index == 0) row.setReorderActive(active)
|
|
903
|
+
}
|
|
904
|
+
minimumHeight = if (active) dp(68) else walletGroupExpandedHeightPx
|
|
905
|
+
layoutParams = layoutParams?.apply {
|
|
906
|
+
height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
907
|
+
}
|
|
908
|
+
background = restingRowBackground
|
|
909
|
+
requestLayout()
|
|
910
|
+
invalidate()
|
|
911
|
+
return
|
|
912
|
+
}
|
|
913
|
+
restoreRestingBackground()
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
fun finishWalletGroupReorder(
|
|
917
|
+
durationMs: Long,
|
|
918
|
+
interpolator: TimeInterpolator,
|
|
919
|
+
completion: (() -> Unit)? = null,
|
|
920
|
+
) {
|
|
921
|
+
if ((tag as? NativeListItem)?.type != "walletGroup") {
|
|
922
|
+
setReorderActive(false)
|
|
923
|
+
completion?.invoke()
|
|
924
|
+
return
|
|
925
|
+
}
|
|
926
|
+
reorderActive = false
|
|
927
|
+
walletGroupRows.firstOrNull()?.setReorderActive(false)
|
|
928
|
+
val startHeight = height.coerceAtLeast(dp(68))
|
|
929
|
+
val targetHeight = walletGroupExpandedHeightPx
|
|
930
|
+
minimumHeight = startHeight
|
|
931
|
+
layoutParams = layoutParams?.apply { height = startHeight }
|
|
932
|
+
walletGroupRows.drop(1).forEach { row ->
|
|
933
|
+
row.visibility = VISIBLE
|
|
934
|
+
row.alpha = 0f
|
|
935
|
+
}
|
|
936
|
+
background = restingRowBackground
|
|
937
|
+
if (targetHeight <= startHeight) {
|
|
938
|
+
walletGroupRows.forEach { row ->
|
|
939
|
+
row.visibility = VISIBLE
|
|
940
|
+
row.alpha = 1f
|
|
941
|
+
}
|
|
942
|
+
minimumHeight = targetHeight
|
|
943
|
+
layoutParams = layoutParams?.apply { height = ViewGroup.LayoutParams.WRAP_CONTENT }
|
|
944
|
+
requestLayout()
|
|
945
|
+
invalidate()
|
|
946
|
+
completion?.invoke()
|
|
947
|
+
return
|
|
948
|
+
}
|
|
949
|
+
walletGroupExpandAnimator = ValueAnimator.ofInt(startHeight, targetHeight).apply {
|
|
950
|
+
duration = durationMs
|
|
951
|
+
this.interpolator = interpolator
|
|
952
|
+
addUpdateListener { animator ->
|
|
953
|
+
val progress = animator.animatedFraction
|
|
954
|
+
layoutParams = layoutParams?.apply { height = animator.animatedValue as Int }
|
|
955
|
+
walletGroupRows.drop(1).forEach { it.alpha = progress }
|
|
956
|
+
requestLayout()
|
|
957
|
+
invalidate()
|
|
958
|
+
}
|
|
959
|
+
addListener(object : AnimatorListenerAdapter() {
|
|
960
|
+
override fun onAnimationEnd(animation: Animator) {
|
|
961
|
+
walletGroupExpandAnimator = null
|
|
962
|
+
walletGroupRows.forEach { row ->
|
|
963
|
+
row.visibility = VISIBLE
|
|
964
|
+
row.alpha = 1f
|
|
965
|
+
}
|
|
966
|
+
minimumHeight = targetHeight
|
|
967
|
+
layoutParams = layoutParams?.apply { height = ViewGroup.LayoutParams.WRAP_CONTENT }
|
|
968
|
+
requestLayout()
|
|
969
|
+
invalidate()
|
|
970
|
+
postOnAnimation {
|
|
971
|
+
if (!reorderActive) {
|
|
972
|
+
walletGroupRows.forEach { row ->
|
|
973
|
+
row.visibility = VISIBLE
|
|
974
|
+
row.alpha = 1f
|
|
975
|
+
}
|
|
976
|
+
requestLayout()
|
|
977
|
+
invalidate()
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
completion?.invoke()
|
|
981
|
+
}
|
|
982
|
+
})
|
|
983
|
+
start()
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
private fun bindWalletGroup(
|
|
988
|
+
item: NativeListItem,
|
|
989
|
+
theme: JSONObject?,
|
|
990
|
+
layout: String,
|
|
991
|
+
listOrientation: String,
|
|
992
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
993
|
+
) {
|
|
994
|
+
orientation = VERTICAL
|
|
995
|
+
gravity = Gravity.CENTER_HORIZONTAL
|
|
996
|
+
setPadding(0, 0, 0, 0)
|
|
997
|
+
isClickable = false
|
|
998
|
+
setOnClickListener(null)
|
|
999
|
+
val members = mutableListOf<JSONObject>()
|
|
1000
|
+
members.add(item.json.getJSONObject("parent"))
|
|
1001
|
+
item.json.getJSONArray("children").let { children ->
|
|
1002
|
+
for (index in 0 until children.length()) {
|
|
1003
|
+
members.add(children.getJSONObject(index))
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
walletGroupDragChildCount = members.size - 1
|
|
1007
|
+
walletGroupExpandedHeightPx =
|
|
1008
|
+
dp(members.size * 68 + walletGroupDragChildCount * 12)
|
|
1009
|
+
walletGroupDragBadgeBackgroundPaint.color = color(
|
|
1010
|
+
theme,
|
|
1011
|
+
"inverseBackground",
|
|
1012
|
+
"#000000DF",
|
|
1013
|
+
)
|
|
1014
|
+
walletGroupDragBadgeBorderPaint.color = color(
|
|
1015
|
+
theme,
|
|
1016
|
+
"rowBackground",
|
|
1017
|
+
"#FFFFFF",
|
|
1018
|
+
)
|
|
1019
|
+
walletGroupDragBadgeBorderPaint.strokeWidth = dp(1).toFloat()
|
|
1020
|
+
walletGroupDragBadgeTextPaint.color = color(
|
|
1021
|
+
theme,
|
|
1022
|
+
"inverseText",
|
|
1023
|
+
"#FCFCFC",
|
|
1024
|
+
)
|
|
1025
|
+
walletGroupDragBadgeTextPaint.textSize = TypedValue.applyDimension(
|
|
1026
|
+
TypedValue.COMPLEX_UNIT_SP,
|
|
1027
|
+
sp(12f),
|
|
1028
|
+
resources.displayMetrics,
|
|
1029
|
+
)
|
|
1030
|
+
while (walletGroupRows.size < members.size) {
|
|
1031
|
+
walletGroupRows.add(NativeListRowView(reactContext))
|
|
1032
|
+
}
|
|
1033
|
+
members.forEachIndexed { index, memberJson ->
|
|
1034
|
+
val member = NativeListItem.parse(memberJson)
|
|
1035
|
+
val memberRow = walletGroupRows[index]
|
|
1036
|
+
memberRow.onRowPress = { item, origin -> onRowPress?.invoke(item, origin) }
|
|
1037
|
+
memberRow.onAction = { source, actionKey, target, origin ->
|
|
1038
|
+
onAction?.invoke(source, actionKey, target, origin)
|
|
1039
|
+
}
|
|
1040
|
+
memberRow.onBindingInvalidated = { row, epoch ->
|
|
1041
|
+
onBindingInvalidated?.invoke(row, epoch)
|
|
1042
|
+
}
|
|
1043
|
+
memberRow.bind(
|
|
1044
|
+
member,
|
|
1045
|
+
theme,
|
|
1046
|
+
layout,
|
|
1047
|
+
listOrientation,
|
|
1048
|
+
null,
|
|
1049
|
+
memberJson.optBoolean("selected", false),
|
|
1050
|
+
checkboxState,
|
|
1051
|
+
)
|
|
1052
|
+
memberRow.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, dp(68)).apply {
|
|
1053
|
+
if (index > 0) topMargin = dp(12)
|
|
1054
|
+
}
|
|
1055
|
+
addView(memberRow)
|
|
1056
|
+
}
|
|
1057
|
+
val fill = color(theme, "subduedBackground", "#F9F9F9")
|
|
1058
|
+
val stroke = color(theme, "separator", "#0000001F")
|
|
1059
|
+
restingRowBackground = GradientDrawable().apply {
|
|
1060
|
+
setColor(fill)
|
|
1061
|
+
setStroke(dp(1), stroke)
|
|
1062
|
+
cornerRadius = dp(12).toFloat()
|
|
1063
|
+
}
|
|
1064
|
+
pressedRowBackground = restingRowBackground
|
|
1065
|
+
background = restingRowBackground
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
private fun bindIdentity(
|
|
1069
|
+
item: NativeListItem,
|
|
1070
|
+
theme: JSONObject?,
|
|
1071
|
+
selected: Boolean,
|
|
1072
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1073
|
+
) {
|
|
1074
|
+
if (item.json.optString("presentation") == "walletSidebar") {
|
|
1075
|
+
orientation = VERTICAL
|
|
1076
|
+
gravity = Gravity.CENTER
|
|
1077
|
+
setPadding(dp(4), dp(4), dp(4), dp(4))
|
|
1078
|
+
addLeading(item.json.optJSONObject("leading"), 40, spacingDp = 0)
|
|
1079
|
+
leadingFallback.textSize = sp(28f)
|
|
1080
|
+
leadingFallback.typeface = NativeListFonts.regular(context)
|
|
1081
|
+
mainColumn.gravity = Gravity.CENTER
|
|
1082
|
+
titleLine.gravity = Gravity.CENTER
|
|
1083
|
+
titleLine.packsChildrenAtStart = false
|
|
1084
|
+
titleLine.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1085
|
+
title.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
|
1086
|
+
title.gravity = Gravity.CENTER
|
|
1087
|
+
showText(title, item.json.optString("title"), 1)
|
|
1088
|
+
title.setTextColor(
|
|
1089
|
+
color(
|
|
1090
|
+
theme,
|
|
1091
|
+
if (selected) "primaryText" else "secondaryText",
|
|
1092
|
+
if (selected) "#FFFFFFED" else "#FFFFFFAF",
|
|
1093
|
+
),
|
|
1094
|
+
)
|
|
1095
|
+
addView(
|
|
1096
|
+
mainColumn,
|
|
1097
|
+
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1098
|
+
topMargin = dp(4)
|
|
1099
|
+
},
|
|
1100
|
+
)
|
|
1101
|
+
return
|
|
1102
|
+
}
|
|
1103
|
+
if (item.json.optString("presentation") == "networkSelector") {
|
|
1104
|
+
setPadding(dp(12), dp(7), dp(12), dp(8))
|
|
1105
|
+
}
|
|
1106
|
+
val leading = item.json.optJSONObject("leading")
|
|
1107
|
+
item.json.optJSONObject("leadingAction")?.let { action ->
|
|
1108
|
+
leadingActionIcon.visibility = VISIBLE
|
|
1109
|
+
leadingActionIcon.iconName = action.optString("name")
|
|
1110
|
+
leadingActionIcon.glyphSizeDp = 24
|
|
1111
|
+
leadingActionIcon.tintColor = safeColor(
|
|
1112
|
+
action.optString("tintColor"),
|
|
1113
|
+
color(theme, "icon", "#0000009B"),
|
|
1114
|
+
)
|
|
1115
|
+
leadingActionIcon.isEnabled = !action.optBoolean("disabled", false)
|
|
1116
|
+
leadingActionIcon.alpha = if (leadingActionIcon.isEnabled) 1f else 0.4f
|
|
1117
|
+
leadingActionIcon.setOnClickListener {
|
|
1118
|
+
emitAction(
|
|
1119
|
+
item,
|
|
1120
|
+
action.optString("actionKey"),
|
|
1121
|
+
null,
|
|
1122
|
+
leadingActionIcon,
|
|
1123
|
+
"leadingAction",
|
|
1124
|
+
)
|
|
1125
|
+
}
|
|
1126
|
+
// ListItem.IconButton is 36dp with 6dp inner padding and m=-7. Keep the
|
|
1127
|
+
// full button frame but absorb its leading negative margin into the row
|
|
1128
|
+
// padding and its trailing margin into the following gap.
|
|
1129
|
+
setPadding(dp(5), paddingTop, paddingRight, paddingBottom)
|
|
1130
|
+
addView(leadingActionIcon, LayoutParams(dp(36), dp(36)).apply { marginEnd = dp(5) })
|
|
1131
|
+
}
|
|
1132
|
+
addLeading(
|
|
1133
|
+
leading,
|
|
1134
|
+
if (
|
|
1135
|
+
leading?.optString("kind") == "network" ||
|
|
1136
|
+
item.json.optString("presentation") == "accountSelector"
|
|
1137
|
+
) 32 else 40,
|
|
1138
|
+
)
|
|
1139
|
+
addView(mainColumn, weighted())
|
|
1140
|
+
titleLine.packsChildrenAtStart = true
|
|
1141
|
+
title.ellipsize = TextUtils.TruncateAt.END
|
|
1142
|
+
showText(title, item.json.optString("title"), item.json.optInt("titleLines", 1))
|
|
1143
|
+
if (item.json.optString("presentation") == "accountSelector") {
|
|
1144
|
+
title.typeface = NativeListFonts.regular(context)
|
|
1145
|
+
}
|
|
1146
|
+
showText(subtitle, item.json.optString("subtitle"), item.json.optInt("subtitleLines", 2))
|
|
1147
|
+
showText(tertiary, item.json.optString("tertiary"), 1)
|
|
1148
|
+
tertiary.setTextColor(
|
|
1149
|
+
color(
|
|
1150
|
+
theme,
|
|
1151
|
+
if (item.json.optString("tertiaryTone") == "info") "info" else "secondaryText",
|
|
1152
|
+
if (item.json.optString("tertiaryTone") == "info") "#006DCBF2" else "#0000009B",
|
|
1153
|
+
),
|
|
1154
|
+
)
|
|
1155
|
+
val badges = item.json.optJSONArray("badges")
|
|
1156
|
+
if (badges != null && badges.length() > 0) {
|
|
1157
|
+
val texts = (0 until minOf(2, badges.length())).map { badges.getJSONObject(it).optString("text") }
|
|
1158
|
+
showText(badgeLine, texts.joinToString(" "), 1)
|
|
1159
|
+
badgeLine.layoutParams = wrap().apply { marginStart = dp(8) }
|
|
1160
|
+
badgeLine.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
1161
|
+
badgeLine.background = roundedFill(color(theme, "strongBackground", "#0000000F"), 4f)
|
|
1162
|
+
badgeLine.setPadding(dp(8), dp(2), dp(8), dp(2))
|
|
1163
|
+
TextViewCompat.setLineHeight(badgeLine, dp(16))
|
|
1164
|
+
}
|
|
1165
|
+
addView(trailingColumn, wrap())
|
|
1166
|
+
val accessories = item.json.optJSONArray("trailing")
|
|
1167
|
+
if (accessories.hasAccessory("checkbox") && accessories.hasAccessory("value")) {
|
|
1168
|
+
trailingColumn.orientation = HORIZONTAL
|
|
1169
|
+
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1170
|
+
checkbox.layoutParams = LayoutParams(dp(20), dp(20)).apply { marginStart = dp(12) }
|
|
1171
|
+
} else if (accessories.isValuePairMenu()) {
|
|
1172
|
+
trailingColumn.orientation = HORIZONTAL
|
|
1173
|
+
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1174
|
+
} else if (accessories.isBookmarkEditActions()) {
|
|
1175
|
+
trailingColumn.orientation = HORIZONTAL
|
|
1176
|
+
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1177
|
+
}
|
|
1178
|
+
bindAccessories(item, accessories, theme, checkboxState)
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
private fun bindRail(item: NativeListItem, theme: JSONObject?) {
|
|
1182
|
+
setPadding(dp(4), dp(4), dp(4), dp(4))
|
|
1183
|
+
addLeading(item.json.optJSONObject("visual"), 20, spacingDp = 6)
|
|
1184
|
+
showText(title, item.json.optString("title"), 1)
|
|
1185
|
+
TextViewCompat.setLineHeight(title, dp(16))
|
|
1186
|
+
title.fontFeatureSettings = "tnum"
|
|
1187
|
+
title.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
|
1188
|
+
titleLine.layoutParams = wrap()
|
|
1189
|
+
mainColumn.orientation = HORIZONTAL
|
|
1190
|
+
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
1191
|
+
mainColumn.layoutParams = wrap()
|
|
1192
|
+
title.setHorizontallyScrolling(true)
|
|
1193
|
+
item.json.optJSONObject("badge")?.let { badge ->
|
|
1194
|
+
showText(badgeLine, badge.optString("text"), 1)
|
|
1195
|
+
badgeLine.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1196
|
+
marginStart = dp(6)
|
|
1197
|
+
}
|
|
1198
|
+
badgeLine.setTextColor(
|
|
1199
|
+
when (badge.optString("tone")) {
|
|
1200
|
+
"success" -> color(theme, "positive", "#00713FDE")
|
|
1201
|
+
"danger" -> color(theme, "negative", "#C40006D3")
|
|
1202
|
+
else -> color(theme, "secondaryText", "#0000009B")
|
|
1203
|
+
},
|
|
1204
|
+
)
|
|
1205
|
+
badgeLine.typeface = NativeListFonts.medium(context)
|
|
1206
|
+
badgeLine.fontFeatureSettings = "tnum"
|
|
1207
|
+
TextViewCompat.setLineHeight(badgeLine, dp(16))
|
|
1208
|
+
}
|
|
1209
|
+
item.json.optString("status").takeUnless { it.isEmpty() || it == "none" }
|
|
1210
|
+
?.let { showText(status, it, 1) }
|
|
1211
|
+
addView(mainColumn, wrap())
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
private fun bindActivity(item: NativeListItem, theme: JSONObject?) {
|
|
1215
|
+
addLeading(
|
|
1216
|
+
item.json.optJSONObject("leading"),
|
|
1217
|
+
secondaryVisual = item.json.optJSONObject("secondaryLeading"),
|
|
1218
|
+
)
|
|
1219
|
+
addView(mainColumn, weighted())
|
|
1220
|
+
showText(title, item.json.optString("title"), 1)
|
|
1221
|
+
showText(subtitle, item.json.optString("description"), 2)
|
|
1222
|
+
TextViewCompat.setLineHeight(title, dp(24))
|
|
1223
|
+
TextViewCompat.setLineHeight(subtitle, dp(20))
|
|
1224
|
+
if (item.json.optString("status") == "Failed") {
|
|
1225
|
+
showText(badgeLine, "Failed", 1)
|
|
1226
|
+
badgeLine.setTextColor(color(theme, "negative", "#C40006D3"))
|
|
1227
|
+
badgeLine.background = roundedFill(
|
|
1228
|
+
color(theme, "criticalBackground", "#F3000D14"),
|
|
1229
|
+
4f,
|
|
1230
|
+
)
|
|
1231
|
+
badgeLine.setPadding(dp(8), dp(2), dp(8), dp(2))
|
|
1232
|
+
} else {
|
|
1233
|
+
showText(status, item.json.optString("status"), 1)
|
|
1234
|
+
}
|
|
1235
|
+
addView(trailingColumn, wrap())
|
|
1236
|
+
showTrailing(0, item.json.optString("primaryAmount"), true)
|
|
1237
|
+
showTrailing(1, item.json.optString("secondaryAmount"), false)
|
|
1238
|
+
if (item.json.optString("primaryAmount").startsWith("+")) {
|
|
1239
|
+
trailingViews[0].setTextColor(color(theme, "positive", "#00713FDE"))
|
|
1240
|
+
}
|
|
1241
|
+
val actions = item.json.optJSONArray("footerActions")
|
|
1242
|
+
if (actions != null && actions.length() > 0) {
|
|
1243
|
+
actionLine.visibility = VISIBLE
|
|
1244
|
+
for (index in 0 until minOf(3, actions.length())) {
|
|
1245
|
+
val action = actions.getJSONObject(index)
|
|
1246
|
+
val actionView = actionViews[index]
|
|
1247
|
+
actionView.text = action.optString("label")
|
|
1248
|
+
actionView.visibility = VISIBLE
|
|
1249
|
+
actionView.isEnabled = isEnabled && !action.optBoolean("disabled", false)
|
|
1250
|
+
actionView.background = roundedFill(color(theme, "strongBackground", "#0000000F"), 8f)
|
|
1251
|
+
actionView.setTextColor(
|
|
1252
|
+
if (action.optString("tone") == "danger") {
|
|
1253
|
+
color(theme, "negative", "#C40006D3")
|
|
1254
|
+
} else {
|
|
1255
|
+
color(theme, "primaryText", "#000000DF")
|
|
1256
|
+
},
|
|
1257
|
+
)
|
|
1258
|
+
actionView.layoutParams = LayoutParams(
|
|
1259
|
+
LayoutParams.WRAP_CONTENT,
|
|
1260
|
+
LayoutParams.WRAP_CONTENT,
|
|
1261
|
+
).apply { marginEnd = dp(8) }
|
|
1262
|
+
actionView.setOnClickListener {
|
|
1263
|
+
emitAction(item, action.optString("key"), null, actionView, "footerAction", index)
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
// TxActionCommonListView is one column ListItem with an 8dp gap between
|
|
1267
|
+
// its content XStack and the pending action footer.
|
|
1268
|
+
removeView(leadingFrame)
|
|
1269
|
+
removeView(mainColumn)
|
|
1270
|
+
removeView(trailingColumn)
|
|
1271
|
+
mainColumn.removeView(actionLine)
|
|
1272
|
+
activityContentRow.orientation = HORIZONTAL
|
|
1273
|
+
activityContentRow.gravity = Gravity.CENTER_VERTICAL
|
|
1274
|
+
activityContentRow.addView(leadingFrame)
|
|
1275
|
+
activityContentRow.addView(mainColumn)
|
|
1276
|
+
activityContentRow.addView(trailingColumn)
|
|
1277
|
+
orientation = VERTICAL
|
|
1278
|
+
gravity = Gravity.START
|
|
1279
|
+
addView(activityContentRow, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
1280
|
+
addView(actionLine, LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1281
|
+
marginStart = if (leadingFrame.visibility == VISIBLE) dp(52) else 0
|
|
1282
|
+
topMargin = dp(8)
|
|
1283
|
+
bottomMargin = dp(4)
|
|
1284
|
+
})
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
private fun bindMessage(item: NativeListItem, theme: JSONObject?) {
|
|
1289
|
+
gravity = Gravity.TOP
|
|
1290
|
+
setPadding(dp(12), dp(16), dp(12), dp(16))
|
|
1291
|
+
item.json.optJSONObject("leading")?.let { addLeading(it, 28) }
|
|
1292
|
+
unreadDot.visibility = if (item.json.optBoolean("unread", false)) VISIBLE else GONE
|
|
1293
|
+
addView(mainColumn, weighted())
|
|
1294
|
+
showText(title, item.json.optString("title"), 2)
|
|
1295
|
+
showText(subtitle, item.json.optString("body"), item.json.optInt("bodyLines", 3).coerceIn(1, 3))
|
|
1296
|
+
showText(status, item.json.optString("time"), 1)
|
|
1297
|
+
TextViewCompat.setLineHeight(title, dp(20))
|
|
1298
|
+
TextViewCompat.setLineHeight(subtitle, dp(20))
|
|
1299
|
+
TextViewCompat.setLineHeight(status, dp(16))
|
|
1300
|
+
subtitle.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1301
|
+
topMargin = dp(2)
|
|
1302
|
+
}
|
|
1303
|
+
status.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1304
|
+
topMargin = dp(4)
|
|
1305
|
+
}
|
|
1306
|
+
status.setTextColor(color(theme, "disabledText", "#00000072"))
|
|
1307
|
+
item.json.optJSONObject("thumbnail")?.let { source ->
|
|
1308
|
+
secondaryImage.visibility = VISIBLE
|
|
1309
|
+
secondaryImage.foreground = roundedHairlineStroke(
|
|
1310
|
+
color(theme, "strongBackground", "#0000000F"),
|
|
1311
|
+
6f,
|
|
1312
|
+
)
|
|
1313
|
+
addView(secondaryImage, LayoutParams(dp(64), dp(64)).apply { marginStart = dp(12) })
|
|
1314
|
+
bindImage(source, secondaryImage, item.key, 0, "generic")
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1318
|
+
private fun bindDataRow(
|
|
1319
|
+
item: NativeListItem,
|
|
1320
|
+
theme: JSONObject?,
|
|
1321
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1322
|
+
) {
|
|
1323
|
+
if (
|
|
1324
|
+
item.json.optBoolean("favorite", false) ||
|
|
1325
|
+
item.json.optBoolean("favoriteActive", false)
|
|
1326
|
+
) {
|
|
1327
|
+
val favoriteActive = item.json.optBoolean("favoriteActive", false)
|
|
1328
|
+
favoriteIcon.visibility = VISIBLE
|
|
1329
|
+
favoriteIcon.iconName = if (favoriteActive) "StarSolid" else "StarOutline"
|
|
1330
|
+
favoriteIcon.tintColor = color(
|
|
1331
|
+
theme,
|
|
1332
|
+
if (favoriteActive) "icon" else "iconSubdued",
|
|
1333
|
+
if (favoriteActive) "#0000009B" else "#00000072",
|
|
1334
|
+
)
|
|
1335
|
+
addView(favoriteIcon, LayoutParams(dp(20), dp(20)).apply {
|
|
1336
|
+
marginEnd = dp(if (currentLayout == "table") 8 else 12)
|
|
1337
|
+
})
|
|
1338
|
+
}
|
|
1339
|
+
item.json.optJSONObject("leading")?.let {
|
|
1340
|
+
addLeading(it, 40, spacingDp = if (currentLayout == "table") 10 else 12)
|
|
1341
|
+
}
|
|
1342
|
+
var hasLeadingAccessory = false
|
|
1343
|
+
item.json.optJSONObject("checkbox")?.let {
|
|
1344
|
+
bindCheckbox(item, it, checkboxState)
|
|
1345
|
+
hasLeadingAccessory = true
|
|
1346
|
+
}
|
|
1347
|
+
if (item.json.has("index")) {
|
|
1348
|
+
val indexView = trailingViews[0]
|
|
1349
|
+
indexView.text = item.json.optInt("index").toString()
|
|
1350
|
+
indexView.visibility = VISIBLE
|
|
1351
|
+
hasLeadingAccessory = true
|
|
1352
|
+
}
|
|
1353
|
+
if (hasLeadingAccessory) {
|
|
1354
|
+
addView(trailingColumn, LayoutParams(dp(32), LayoutParams.WRAP_CONTENT).apply { marginEnd = dp(10) })
|
|
1355
|
+
}
|
|
1356
|
+
val columns = item.json.getJSONArray("columns")
|
|
1357
|
+
val rowBadges = item.json.optJSONArray("badges")
|
|
1358
|
+
if (currentLayout == "table") {
|
|
1359
|
+
tableDataContainer.visibility = VISIBLE
|
|
1360
|
+
for (index in 0 until minOf(4, columns.length())) {
|
|
1361
|
+
val column = columns.getJSONObject(index)
|
|
1362
|
+
tableDataColumns[index].visibility = VISIBLE
|
|
1363
|
+
tableDataColumns[index].bind(
|
|
1364
|
+
column = column,
|
|
1365
|
+
rowBadges = if (index == 0) rowBadges else null,
|
|
1366
|
+
primaryColor = dataTextColor(column.optString("tone"), theme),
|
|
1367
|
+
secondaryColor = dataTextColor(
|
|
1368
|
+
column.optString("secondaryTone", "secondary"),
|
|
1369
|
+
theme,
|
|
1370
|
+
),
|
|
1371
|
+
infoColor = color(theme, "info", "#006DCBF2"),
|
|
1372
|
+
)
|
|
1373
|
+
tableDataColumns[index].layoutParams = LayoutParams(
|
|
1374
|
+
0,
|
|
1375
|
+
dp(40),
|
|
1376
|
+
column.optInt("weight", 1).toFloat(),
|
|
1377
|
+
)
|
|
1378
|
+
}
|
|
1379
|
+
addView(tableDataContainer, weighted())
|
|
1380
|
+
return
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
dataContainer.visibility = VISIBLE
|
|
1384
|
+
for (index in 0 until minOf(4, columns.length())) {
|
|
1385
|
+
val column = columns.getJSONObject(index)
|
|
1386
|
+
val view = dataColumns[index]
|
|
1387
|
+
view.text = styledDataText(column, if (index == 0) rowBadges else null, theme)
|
|
1388
|
+
view.maxLines = if (column.optString("secondaryText").isEmpty()) 1 else 2
|
|
1389
|
+
view.gravity = when (column.optString("alignment", "start")) {
|
|
1390
|
+
"center" -> Gravity.CENTER
|
|
1391
|
+
"end" -> Gravity.END
|
|
1392
|
+
else -> Gravity.START
|
|
1393
|
+
}
|
|
1394
|
+
view.visibility = VISIBLE
|
|
1395
|
+
view.setTextColor(
|
|
1396
|
+
when (column.optString("tone", "primary")) {
|
|
1397
|
+
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
1398
|
+
"positive" -> color(theme, "positive", "#00713FDE")
|
|
1399
|
+
"negative" -> color(theme, "negative", "#C40006D3")
|
|
1400
|
+
else -> color(theme, "primaryText", "#000000DF")
|
|
1401
|
+
},
|
|
1402
|
+
)
|
|
1403
|
+
view.layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, column.optInt("weight", 1).toFloat())
|
|
1404
|
+
}
|
|
1405
|
+
addView(dataContainer, weighted())
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
private fun styledDataText(
|
|
1409
|
+
column: JSONObject,
|
|
1410
|
+
badges: JSONArray?,
|
|
1411
|
+
theme: JSONObject?,
|
|
1412
|
+
): CharSequence {
|
|
1413
|
+
val result = SpannableStringBuilder(column.optString("text"))
|
|
1414
|
+
result.setSpan(
|
|
1415
|
+
ForegroundColorSpan(dataTextColor(column.optString("tone"), theme)),
|
|
1416
|
+
0,
|
|
1417
|
+
result.length,
|
|
1418
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
1419
|
+
)
|
|
1420
|
+
if (badges != null) {
|
|
1421
|
+
for (index in 0 until minOf(2, badges.length())) {
|
|
1422
|
+
val start = result.length
|
|
1423
|
+
result.append(" ${badges.getJSONObject(index).optString("text")} ")
|
|
1424
|
+
result.setSpan(
|
|
1425
|
+
ForegroundColorSpan(color(theme, "info", "#006DCBF2")),
|
|
1426
|
+
start,
|
|
1427
|
+
result.length,
|
|
1428
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
1429
|
+
)
|
|
1430
|
+
result.setSpan(
|
|
1431
|
+
BackgroundColorSpan(parseNativeListColor("#008FF519")),
|
|
1432
|
+
start,
|
|
1433
|
+
result.length,
|
|
1434
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
1435
|
+
)
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
column.optString("secondaryText").takeIf(String::isNotEmpty)?.let { secondary ->
|
|
1439
|
+
val start = result.length
|
|
1440
|
+
result.append("\n$secondary")
|
|
1441
|
+
result.setSpan(
|
|
1442
|
+
ForegroundColorSpan(dataTextColor(column.optString("secondaryTone", "secondary"), theme)),
|
|
1443
|
+
start,
|
|
1444
|
+
result.length,
|
|
1445
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
1446
|
+
)
|
|
1447
|
+
}
|
|
1448
|
+
return result
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
private fun dataTextColor(tone: String, theme: JSONObject?): Int = when (tone.ifEmpty { "primary" }) {
|
|
1452
|
+
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
1453
|
+
"positive" -> color(theme, "positive", "#00713FDE")
|
|
1454
|
+
"negative" -> color(theme, "negative", "#C40006D3")
|
|
1455
|
+
else -> color(theme, "primaryText", "#000000DF")
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
private fun bindMediaTile(item: NativeListItem, theme: JSONObject?) {
|
|
1459
|
+
isMediaTile = true
|
|
1460
|
+
orientation = VERTICAL
|
|
1461
|
+
gravity = Gravity.START
|
|
1462
|
+
setPadding(dp(10), dp(10), dp(10), dp(10))
|
|
1463
|
+
val imageState = item.json.optString("imageState", "loaded")
|
|
1464
|
+
when (imageState) {
|
|
1465
|
+
"empty" -> {
|
|
1466
|
+
addLeading(null, 160)
|
|
1467
|
+
leadingFrame.background = roundedFill(Color.WHITE, 10f)
|
|
1468
|
+
}
|
|
1469
|
+
"error" -> {
|
|
1470
|
+
addLeading(null, 160)
|
|
1471
|
+
leadingFallback.visibility = VISIBLE
|
|
1472
|
+
leadingFallback.background = roundedFill(
|
|
1473
|
+
color(theme, "strongBackground", "#0000000F"),
|
|
1474
|
+
10f,
|
|
1475
|
+
)
|
|
1476
|
+
leadingIcon.visibility = VISIBLE
|
|
1477
|
+
leadingIcon.iconName = "ImageSquareWavesOutline"
|
|
1478
|
+
leadingIcon.tintColor = parseNativeListColor("#00000044")
|
|
1479
|
+
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(24), dp(24), Gravity.CENTER)
|
|
1480
|
+
}
|
|
1481
|
+
else -> {
|
|
1482
|
+
addLeading(
|
|
1483
|
+
JSONObject().put("kind", "image").put("image", item.json.getJSONObject("image")),
|
|
1484
|
+
160,
|
|
1485
|
+
)
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
leadingFrame.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, dp(160))
|
|
1489
|
+
leadingFallback.layoutParams = FrameLayout.LayoutParams(
|
|
1490
|
+
LayoutParams.MATCH_PARENT,
|
|
1491
|
+
LayoutParams.MATCH_PARENT,
|
|
1492
|
+
)
|
|
1493
|
+
leadingImages[0].layoutParams = FrameLayout.LayoutParams(
|
|
1494
|
+
LayoutParams.MATCH_PARENT,
|
|
1495
|
+
LayoutParams.MATCH_PARENT,
|
|
1496
|
+
)
|
|
1497
|
+
leadingImages[0].outlineProvider = object : ViewOutlineProvider() {
|
|
1498
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
1499
|
+
outline.setRoundRect(0, 0, view.width, view.height, dp(10).toFloat())
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
leadingImages[0].clipToOutline = true
|
|
1503
|
+
mainColumn.removeView(titleLine)
|
|
1504
|
+
mainColumn.removeView(subtitle)
|
|
1505
|
+
mediaMetadataRow.orientation = HORIZONTAL
|
|
1506
|
+
mediaMetadataRow.gravity = Gravity.CENTER_VERTICAL
|
|
1507
|
+
mediaMetadataRow.addView(subtitle, weighted().apply { marginEnd = dp(8) })
|
|
1508
|
+
item.json.optJSONObject("networkImage")?.let { networkImage ->
|
|
1509
|
+
mediaNetworkImage.visibility = VISIBLE
|
|
1510
|
+
mediaNetworkImage.outlineProvider = circleOutlineProvider
|
|
1511
|
+
mediaNetworkImage.clipToOutline = true
|
|
1512
|
+
mediaMetadataRow.addView(mediaNetworkImage, LayoutParams(dp(14), dp(14)))
|
|
1513
|
+
bindImage(networkImage, mediaNetworkImage, item.key, 2, "network")
|
|
1514
|
+
}
|
|
1515
|
+
mainColumn.addView(mediaMetadataRow, 0)
|
|
1516
|
+
mainColumn.addView(titleLine, 1)
|
|
1517
|
+
showText(subtitle, item.json.optString("subtitle"), 1)
|
|
1518
|
+
showText(title, item.json.optString("title"), 1)
|
|
1519
|
+
item.json.optJSONObject("badge")?.optString("text")?.let { value ->
|
|
1520
|
+
mediaBadge.text = value
|
|
1521
|
+
mediaBadge.setTextColor(parseNativeListColor("#FCFCFC"))
|
|
1522
|
+
mediaBadge.textSize = sp(14f)
|
|
1523
|
+
mediaBadge.background = roundedStroke(
|
|
1524
|
+
Color.WHITE,
|
|
1525
|
+
parseNativeListColor("#000000DF"),
|
|
1526
|
+
10f,
|
|
1527
|
+
)
|
|
1528
|
+
mediaBadge.setPadding(dp(8), 0, dp(8), 0)
|
|
1529
|
+
mediaBadge.layoutParams = FrameLayout.LayoutParams(
|
|
1530
|
+
LayoutParams.WRAP_CONTENT,
|
|
1531
|
+
dp(24),
|
|
1532
|
+
Gravity.END or Gravity.BOTTOM,
|
|
1533
|
+
)
|
|
1534
|
+
mediaBadge.visibility = VISIBLE
|
|
1535
|
+
}
|
|
1536
|
+
addView(mainColumn, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1537
|
+
topMargin = dp(8)
|
|
1538
|
+
})
|
|
1539
|
+
item.json.optString("closeActionKey").takeIf { it.isNotEmpty() }?.let { actionKey ->
|
|
1540
|
+
val close = trailingViews[0]
|
|
1541
|
+
close.text = "×"
|
|
1542
|
+
close.textSize = sp(22f)
|
|
1543
|
+
close.gravity = Gravity.END
|
|
1544
|
+
close.visibility = VISIBLE
|
|
1545
|
+
close.setOnClickListener {
|
|
1546
|
+
emitAction(item, actionKey, null, close, "mediaClose")
|
|
1547
|
+
}
|
|
1548
|
+
addView(trailingColumn, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
private fun bindMetricCard(item: NativeListItem, theme: JSONObject?) {
|
|
1553
|
+
orientation = VERTICAL
|
|
1554
|
+
gravity = Gravity.START
|
|
1555
|
+
setPadding(dp(14), dp(14), dp(14), dp(14))
|
|
1556
|
+
val variant = item.json.optString("variant", "standard")
|
|
1557
|
+
if (variant == "activity" || variant == "performance") {
|
|
1558
|
+
bindCompositeMetricCard(item, theme, variant)
|
|
1559
|
+
return
|
|
1560
|
+
}
|
|
1561
|
+
item.json.optJSONObject("visual")?.let {
|
|
1562
|
+
addLeading(it, 32)
|
|
1563
|
+
leadingFrame.clipChildren = true
|
|
1564
|
+
leadingFrame.clipToPadding = true
|
|
1565
|
+
}
|
|
1566
|
+
showText(subtitle, item.json.optString("title"), 1)
|
|
1567
|
+
subtitle.setTextColor(color(theme, "disabledText", "#00000072"))
|
|
1568
|
+
showText(title, item.json.optString("value"), 1)
|
|
1569
|
+
title.textSize = sp(if (item.json.optString("size") == "large") 24f else 18f)
|
|
1570
|
+
title.typeface = NativeListFonts.semibold(context)
|
|
1571
|
+
showText(status, item.json.optString("trend"), 1)
|
|
1572
|
+
status.setTextColor(
|
|
1573
|
+
when (item.json.optString("trendTone", "neutral")) {
|
|
1574
|
+
"positive" -> color(theme, "positive", "#00713FDE")
|
|
1575
|
+
"negative" -> color(theme, "negative", "#C40006D3")
|
|
1576
|
+
else -> color(theme, "secondaryText", "#0000009B")
|
|
1577
|
+
},
|
|
1578
|
+
)
|
|
1579
|
+
showText(metricSubtitle, item.json.optString("subtitle"), 1)
|
|
1580
|
+
item.json.optJSONObject("badge")?.optString("text")?.let { showText(badgeLine, it, 1) }
|
|
1581
|
+
addView(mainColumn, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT))
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
private fun bindCompositeMetricCard(item: NativeListItem, theme: JSONObject?, variant: String) {
|
|
1585
|
+
val metrics = item.json.optJSONArray("metrics") ?: JSONArray()
|
|
1586
|
+
addView(
|
|
1587
|
+
metricText(
|
|
1588
|
+
value = item.json.optString("title").uppercase(),
|
|
1589
|
+
size = 11f,
|
|
1590
|
+
lineHeight = 14,
|
|
1591
|
+
typeface = NativeListFonts.regular(context),
|
|
1592
|
+
textColor = color(theme, "disabledText", "#00000072"),
|
|
1593
|
+
letterSpacingDp = 1.2f,
|
|
1594
|
+
),
|
|
1595
|
+
weightedWidth(),
|
|
1596
|
+
)
|
|
1597
|
+
if (variant == "activity") {
|
|
1598
|
+
addView(activityHeroMetrics(metrics, theme), weightedWidth().apply {
|
|
1599
|
+
topMargin = dp(14)
|
|
1600
|
+
})
|
|
1601
|
+
addView(View(context).apply {
|
|
1602
|
+
setBackgroundColor(color(theme, "separator", "#0000001F"))
|
|
1603
|
+
}, LayoutParams(LayoutParams.MATCH_PARENT, 1).apply {
|
|
1604
|
+
topMargin = dp(14)
|
|
1605
|
+
bottomMargin = dp(14)
|
|
1606
|
+
})
|
|
1607
|
+
addView(activityCompactMetrics(metrics, theme), weightedWidth())
|
|
1608
|
+
} else {
|
|
1609
|
+
val winRateRow = LinearLayout(context).apply {
|
|
1610
|
+
orientation = HORIZONTAL
|
|
1611
|
+
gravity = Gravity.BOTTOM
|
|
1612
|
+
if (metrics.length() > 0) {
|
|
1613
|
+
addView(
|
|
1614
|
+
makeMetricColumn(
|
|
1615
|
+
metric = metrics.getJSONObject(0),
|
|
1616
|
+
visualSlot = 0,
|
|
1617
|
+
theme = theme,
|
|
1618
|
+
alignment = Gravity.START,
|
|
1619
|
+
valueSize = 18f,
|
|
1620
|
+
valueLineHeight = 24,
|
|
1621
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
1622
|
+
),
|
|
1623
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
1624
|
+
)
|
|
1625
|
+
}
|
|
1626
|
+
if (metrics.length() > 1) {
|
|
1627
|
+
addView(
|
|
1628
|
+
makeMetricColumn(
|
|
1629
|
+
metric = metrics.getJSONObject(1),
|
|
1630
|
+
visualSlot = 1,
|
|
1631
|
+
theme = theme,
|
|
1632
|
+
alignment = Gravity.END,
|
|
1633
|
+
valueSize = 14f,
|
|
1634
|
+
valueLineHeight = 20,
|
|
1635
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
1636
|
+
),
|
|
1637
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
1638
|
+
)
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
addView(winRateRow, weightedWidth().apply { topMargin = dp(14) })
|
|
1642
|
+
val progressValue = item.json.optDouble("progress", 0.0).coerceIn(0.0, 1.0)
|
|
1643
|
+
val progress = LinearLayout(context).apply {
|
|
1644
|
+
orientation = HORIZONTAL
|
|
1645
|
+
clipToOutline = true
|
|
1646
|
+
outlineProvider = object : ViewOutlineProvider() {
|
|
1647
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
1648
|
+
outline.setRoundRect(0, 0, view.width, view.height, dp(2).toFloat())
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
if (progressValue > 0.0) {
|
|
1652
|
+
addView(View(context).apply {
|
|
1653
|
+
setBackgroundColor(parseNativeListColor("#22AB15"))
|
|
1654
|
+
}, LayoutParams(0, LayoutParams.MATCH_PARENT, progressValue.toFloat()))
|
|
1655
|
+
}
|
|
1656
|
+
if (progressValue < 1.0) {
|
|
1657
|
+
addView(View(context).apply {
|
|
1658
|
+
setBackgroundColor(parseNativeListColor("#E5484D"))
|
|
1659
|
+
}, LayoutParams(0, LayoutParams.MATCH_PARENT, (1.0 - progressValue).toFloat()))
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
addView(progress, LayoutParams(LayoutParams.MATCH_PARENT, dp(4)).apply {
|
|
1663
|
+
topMargin = dp(8)
|
|
1664
|
+
})
|
|
1665
|
+
addView(performanceAverageMetrics(metrics, theme), weightedWidth().apply {
|
|
1666
|
+
topMargin = dp(14)
|
|
1667
|
+
})
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
private fun activityHeroMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
1672
|
+
LinearLayout(context).apply {
|
|
1673
|
+
orientation = HORIZONTAL
|
|
1674
|
+
gravity = Gravity.BOTTOM
|
|
1675
|
+
if (metrics.length() > 0) {
|
|
1676
|
+
addView(
|
|
1677
|
+
makeMetricColumn(
|
|
1678
|
+
metric = metrics.getJSONObject(0),
|
|
1679
|
+
visualSlot = 0,
|
|
1680
|
+
theme = theme,
|
|
1681
|
+
alignment = Gravity.START,
|
|
1682
|
+
valueSize = 16f,
|
|
1683
|
+
valueLineHeight = 24,
|
|
1684
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
1685
|
+
),
|
|
1686
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
1687
|
+
)
|
|
1688
|
+
}
|
|
1689
|
+
if (metrics.length() > 1) {
|
|
1690
|
+
addView(
|
|
1691
|
+
makeMetricColumn(
|
|
1692
|
+
metric = metrics.getJSONObject(1),
|
|
1693
|
+
visualSlot = 1,
|
|
1694
|
+
theme = theme,
|
|
1695
|
+
alignment = Gravity.END,
|
|
1696
|
+
valueSize = 14f,
|
|
1697
|
+
valueLineHeight = 20,
|
|
1698
|
+
valueTypeface = NativeListFonts.semibold(context),
|
|
1699
|
+
),
|
|
1700
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
1701
|
+
)
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
|
|
1705
|
+
private fun activityCompactMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
1706
|
+
LinearLayout(context).apply {
|
|
1707
|
+
orientation = HORIZONTAL
|
|
1708
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
1709
|
+
for (index in 2 until metrics.length()) {
|
|
1710
|
+
val alignment = when (index) {
|
|
1711
|
+
2 -> Gravity.START
|
|
1712
|
+
metrics.length() - 1 -> Gravity.END
|
|
1713
|
+
else -> Gravity.CENTER_HORIZONTAL
|
|
1714
|
+
}
|
|
1715
|
+
addView(
|
|
1716
|
+
makeMetricColumn(
|
|
1717
|
+
metric = metrics.getJSONObject(index),
|
|
1718
|
+
visualSlot = index,
|
|
1719
|
+
theme = theme,
|
|
1720
|
+
alignment = alignment,
|
|
1721
|
+
valueSize = 14f,
|
|
1722
|
+
valueLineHeight = 20,
|
|
1723
|
+
valueTypeface = NativeListFonts.medium(context),
|
|
1724
|
+
),
|
|
1725
|
+
LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f),
|
|
1726
|
+
)
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
private fun performanceAverageMetrics(metrics: JSONArray, theme: JSONObject?) =
|
|
1731
|
+
LinearLayout(context).apply {
|
|
1732
|
+
orientation = HORIZONTAL
|
|
1733
|
+
val indices = listOf(2, metrics.length() - 1).distinct().filter { it in 0 until metrics.length() }
|
|
1734
|
+
indices.forEachIndexed { position, index ->
|
|
1735
|
+
val metric = metrics.getJSONObject(index)
|
|
1736
|
+
val card = makeMetricColumn(
|
|
1737
|
+
metric = metric,
|
|
1738
|
+
visualSlot = index,
|
|
1739
|
+
theme = theme,
|
|
1740
|
+
alignment = Gravity.START,
|
|
1741
|
+
valueSize = 14f,
|
|
1742
|
+
valueLineHeight = 20,
|
|
1743
|
+
valueTypeface = NativeListFonts.medium(context),
|
|
1744
|
+
).apply {
|
|
1745
|
+
setPadding(dp(10), dp(10), dp(10), dp(10))
|
|
1746
|
+
background = roundedFill(color(theme, "strongBackground", "#0000000F"), 8f)
|
|
1747
|
+
}
|
|
1748
|
+
addView(card, LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f).apply {
|
|
1749
|
+
if (position > 0) marginStart = dp(8)
|
|
1750
|
+
})
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
private fun makeMetricColumn(
|
|
1755
|
+
metric: JSONObject,
|
|
1756
|
+
visualSlot: Int,
|
|
1757
|
+
theme: JSONObject?,
|
|
1758
|
+
alignment: Int,
|
|
1759
|
+
valueSize: Float,
|
|
1760
|
+
valueLineHeight: Int,
|
|
1761
|
+
valueTypeface: Typeface,
|
|
1762
|
+
) = LinearLayout(context).apply {
|
|
1763
|
+
orientation = VERTICAL
|
|
1764
|
+
gravity = alignment
|
|
1765
|
+
addView(
|
|
1766
|
+
metricText(
|
|
1767
|
+
value = metric.optString("label"),
|
|
1768
|
+
size = 11f,
|
|
1769
|
+
lineHeight = 14,
|
|
1770
|
+
typeface = NativeListFonts.regular(context),
|
|
1771
|
+
textColor = color(theme, "disabledText", "#00000072"),
|
|
1772
|
+
gravity = alignment,
|
|
1773
|
+
),
|
|
1774
|
+
)
|
|
1775
|
+
val valueText = metricText(
|
|
1776
|
+
value = metric.optString("value"),
|
|
1777
|
+
size = valueSize,
|
|
1778
|
+
lineHeight = valueLineHeight,
|
|
1779
|
+
typeface = valueTypeface,
|
|
1780
|
+
textColor = dataTextColor(metric.optString("tone"), theme),
|
|
1781
|
+
gravity = alignment,
|
|
1782
|
+
)
|
|
1783
|
+
val valueVisual = metric.optJSONObject("visual")
|
|
1784
|
+
val valueView = if (valueVisual != null && visualSlot in metricVisualImages.indices) {
|
|
1785
|
+
LinearLayout(context).apply {
|
|
1786
|
+
orientation = HORIZONTAL
|
|
1787
|
+
gravity = Gravity.CENTER_VERTICAL
|
|
1788
|
+
val image = metricVisualImages[visualSlot]
|
|
1789
|
+
image.visibility = VISIBLE
|
|
1790
|
+
image.outlineProvider = circleOutlineProvider
|
|
1791
|
+
image.clipToOutline = true
|
|
1792
|
+
valueVisual.optString("backgroundColor").takeIf(String::isNotEmpty)?.let { backgroundColor ->
|
|
1793
|
+
image.background = roundedFill(
|
|
1794
|
+
safeColor(backgroundColor, Color.WHITE),
|
|
1795
|
+
8f,
|
|
1796
|
+
)
|
|
1797
|
+
}
|
|
1798
|
+
visualSources(valueVisual).firstOrNull()?.let { (source, variant) ->
|
|
1799
|
+
bindImage(source, image, boundKey ?: "metric", visualSlot, variant)
|
|
1800
|
+
}
|
|
1801
|
+
addView(image, LayoutParams(dp(16), dp(16)).apply { marginEnd = dp(6) })
|
|
1802
|
+
addView(valueText, wrap())
|
|
1803
|
+
}
|
|
1804
|
+
} else {
|
|
1805
|
+
valueText
|
|
1806
|
+
}
|
|
1807
|
+
addView(
|
|
1808
|
+
valueView,
|
|
1809
|
+
LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply {
|
|
1810
|
+
topMargin = dp(2)
|
|
1811
|
+
},
|
|
1812
|
+
)
|
|
1813
|
+
}
|
|
1814
|
+
|
|
1815
|
+
private fun metricText(
|
|
1816
|
+
value: String,
|
|
1817
|
+
size: Float,
|
|
1818
|
+
lineHeight: Int,
|
|
1819
|
+
typeface: Typeface,
|
|
1820
|
+
textColor: Int,
|
|
1821
|
+
letterSpacingDp: Float = 0f,
|
|
1822
|
+
gravity: Int = Gravity.START,
|
|
1823
|
+
) = TextView(context).apply {
|
|
1824
|
+
includeFontPadding = false
|
|
1825
|
+
text = value
|
|
1826
|
+
textSize = sp(size)
|
|
1827
|
+
this.typeface = typeface
|
|
1828
|
+
setTextColor(textColor)
|
|
1829
|
+
this.gravity = gravity
|
|
1830
|
+
maxLines = 1
|
|
1831
|
+
ellipsize = TextUtils.TruncateAt.END
|
|
1832
|
+
fontFeatureSettings = "tnum"
|
|
1833
|
+
TextViewCompat.setLineHeight(this, dp(lineHeight))
|
|
1834
|
+
if (letterSpacingDp > 0f) {
|
|
1835
|
+
letterSpacing = letterSpacingDp / sp(size)
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
private fun weightedWidth() = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
|
1840
|
+
|
|
1841
|
+
private fun bindSectionHeader(
|
|
1842
|
+
item: NativeListItem,
|
|
1843
|
+
theme: JSONObject?,
|
|
1844
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1845
|
+
) {
|
|
1846
|
+
val variant = item.json.optString("variant")
|
|
1847
|
+
val isSummary = variant == "summary"
|
|
1848
|
+
val isGallery = variant == "gallery"
|
|
1849
|
+
val isTable = currentLayout == "table"
|
|
1850
|
+
val isNetworkSelector = item.json.optString("presentation") == "networkSelector"
|
|
1851
|
+
val isHistory = variant == "history" || item.sectionKey?.startsWith("history-") == true
|
|
1852
|
+
val isTokenManager = item.sectionKey in setOf("linear-tokens", "action-tokens")
|
|
1853
|
+
val hasDottedTitle = isSummary || isNetworkSelector ||
|
|
1854
|
+
(item.json.optString("value").isNotEmpty() && item.json.optJSONObject("checkbox") != null)
|
|
1855
|
+
// Linear/sectioned snapshots reserve the ListItem mx=8 at RecyclerView
|
|
1856
|
+
// level, so header-local insets below are source px minus that outer inset.
|
|
1857
|
+
val headerHorizontalInset = 12
|
|
1858
|
+
addView(mainColumn, weighted())
|
|
1859
|
+
showText(title, item.json.optString("title"), 1)
|
|
1860
|
+
if (hasDottedTitle) {
|
|
1861
|
+
title.showsDottedUnderline = true
|
|
1862
|
+
title.dottedUnderlineColor = color(theme, "secondaryText", "#0000009B")
|
|
1863
|
+
title.setPadding(0, 0, 0, dp(3))
|
|
1864
|
+
}
|
|
1865
|
+
title.setTextColor(
|
|
1866
|
+
color(
|
|
1867
|
+
theme,
|
|
1868
|
+
if (isSummary || isGallery) "primaryText" else "secondaryText",
|
|
1869
|
+
if (isSummary || isGallery) "#000000DF" else "#0000009B",
|
|
1870
|
+
),
|
|
1871
|
+
)
|
|
1872
|
+
if (isNetworkSelector) {
|
|
1873
|
+
setPadding(dp(headerHorizontalInset), dp(12), dp(headerHorizontalInset), dp(12))
|
|
1874
|
+
title.textSize = sp(14f)
|
|
1875
|
+
title.typeface = NativeListFonts.medium(context)
|
|
1876
|
+
TextViewCompat.setLineHeight(title, dp(20))
|
|
1877
|
+
} else if (isHistory) {
|
|
1878
|
+
setPadding(0, 0, 0, 0)
|
|
1879
|
+
title.text = item.json.optString("title").uppercase()
|
|
1880
|
+
title.textSize = sp(12f)
|
|
1881
|
+
title.typeface = NativeListFonts.semibold(context)
|
|
1882
|
+
title.letterSpacing = 0.8f / sp(12f)
|
|
1883
|
+
TextViewCompat.setLineHeight(title, dp(16))
|
|
1884
|
+
} else if (isTokenManager) {
|
|
1885
|
+
setPadding(dp(headerHorizontalInset), dp(10), dp(headerHorizontalInset), 0)
|
|
1886
|
+
title.textSize = sp(14f)
|
|
1887
|
+
title.typeface = NativeListFonts.regular(context)
|
|
1888
|
+
TextViewCompat.setLineHeight(title, dp(20))
|
|
1889
|
+
} else if (!isTable && !isGallery && !isSummary) {
|
|
1890
|
+
// SectionList.SectionHeader: h=36, px=20, headingSm 14/20 semibold.
|
|
1891
|
+
setPadding(dp(headerHorizontalInset), dp(8), dp(headerHorizontalInset), dp(8))
|
|
1892
|
+
TextViewCompat.setLineHeight(title, dp(20))
|
|
1893
|
+
} else if (isTable) {
|
|
1894
|
+
setPadding(dp(16), dp(12), dp(16), dp(2))
|
|
1895
|
+
title.includeFontPadding = false
|
|
1896
|
+
title.layoutParams = wrap()
|
|
1897
|
+
titleLine.layoutParams = wrap()
|
|
1898
|
+
item.json.optJSONObject("titleIcon")?.let { icon ->
|
|
1899
|
+
headerTitleIcon.visibility = VISIBLE
|
|
1900
|
+
headerTitleIcon.iconName = icon.optString("name")
|
|
1901
|
+
headerTitleIcon.tintColor = safeColor(
|
|
1902
|
+
icon.optString("tintColor"),
|
|
1903
|
+
color(theme, "iconSubdued", "#00000072"),
|
|
1904
|
+
)
|
|
1905
|
+
titleLine.addView(headerTitleIcon, LayoutParams(dp(12), dp(12)).apply {
|
|
1906
|
+
marginStart = dp(4)
|
|
1907
|
+
})
|
|
1908
|
+
if (icon.optString("name") != "ChevronGrabberVerOutline") {
|
|
1909
|
+
title.setTextColor(color(theme, "primaryText", "#000000DF"))
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
showText(subtitle, item.json.optString("subtitle"), 1)
|
|
1914
|
+
if (isGallery) {
|
|
1915
|
+
setPadding(dp(12), 0, dp(12), dp(8))
|
|
1916
|
+
} else if (isSummary) {
|
|
1917
|
+
addView(trailingColumn, wrap())
|
|
1918
|
+
// NetworkListHeader: outer mt=16/pb=12, inner XStack px=20/py=8.
|
|
1919
|
+
setPadding(dp(headerHorizontalInset), dp(24), dp(headerHorizontalInset), dp(20))
|
|
1920
|
+
showTrailing(
|
|
1921
|
+
0,
|
|
1922
|
+
item.json.optString("value"),
|
|
1923
|
+
true,
|
|
1924
|
+
item.json.optString("valueActionKey"),
|
|
1925
|
+
color(theme, "secondaryText", "#0000009B"),
|
|
1926
|
+
)
|
|
1927
|
+
trailingViews[0].textSize = sp(16f)
|
|
1928
|
+
trailingViews[0].typeface = NativeListFonts.medium(context)
|
|
1929
|
+
TextViewCompat.setLineHeight(trailingViews[0], dp(24))
|
|
1930
|
+
trailingViews[0].setPadding(dp(14), dp(6), dp(14), dp(6))
|
|
1931
|
+
trailingViews[0].layoutParams = wrap()
|
|
1932
|
+
} else if (!isGallery && !isHistory && !isTokenManager) {
|
|
1933
|
+
val value = item.json.optString("value")
|
|
1934
|
+
val checkboxData = item.json.optJSONObject("checkbox")
|
|
1935
|
+
if (checkboxData != null && value.isNotEmpty()) {
|
|
1936
|
+
// Value and checkbox share the trailing edge as one compound accessory.
|
|
1937
|
+
trailingColumn.orientation = HORIZONTAL
|
|
1938
|
+
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1939
|
+
trailingViews[0].layoutParams = wrap()
|
|
1940
|
+
checkbox.layoutParams = LayoutParams(dp(20), dp(20)).apply { marginStart = dp(12) }
|
|
1941
|
+
}
|
|
1942
|
+
addView(trailingColumn, wrap())
|
|
1943
|
+
showTrailing(0, value, true)
|
|
1944
|
+
if (isTable) {
|
|
1945
|
+
trailingColumn.orientation = HORIZONTAL
|
|
1946
|
+
trailingColumn.gravity = Gravity.END or Gravity.CENTER_VERTICAL
|
|
1947
|
+
trailingViews[0].includeFontPadding = false
|
|
1948
|
+
trailingViews[0].textSize = sp(11f)
|
|
1949
|
+
trailingViews[0].typeface = NativeListFonts.regular(context)
|
|
1950
|
+
trailingViews[0].setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
1951
|
+
item.json.optJSONObject("valueIcon")?.let { icon ->
|
|
1952
|
+
headerValueIcon.visibility = VISIBLE
|
|
1953
|
+
headerValueIcon.iconName = icon.optString("name")
|
|
1954
|
+
headerValueIcon.tintColor = safeColor(
|
|
1955
|
+
icon.optString("tintColor"),
|
|
1956
|
+
color(theme, "iconSubdued", "#00000072"),
|
|
1957
|
+
)
|
|
1958
|
+
trailingColumn.addView(
|
|
1959
|
+
headerValueIcon,
|
|
1960
|
+
LayoutParams(dp(12), dp(12)).apply { marginStart = dp(4) },
|
|
1961
|
+
)
|
|
1962
|
+
if (icon.optString("name") != "ChevronGrabberVerOutline") {
|
|
1963
|
+
trailingViews[0].setTextColor(color(theme, "primaryText", "#000000DF"))
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
checkboxData?.let { bindCheckbox(item, it, checkboxState) }
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
private fun bindAction(
|
|
1972
|
+
item: NativeListItem,
|
|
1973
|
+
theme: JSONObject?,
|
|
1974
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
1975
|
+
) {
|
|
1976
|
+
val isAccountSelector = item.json.optString("presentation") == "accountSelector"
|
|
1977
|
+
item.json.optJSONObject("icon")?.let { icon ->
|
|
1978
|
+
addLeading(icon, if (isAccountSelector) 32 else 40)
|
|
1979
|
+
leadingIcon.layoutParams = FrameLayout.LayoutParams(dp(24), dp(24), Gravity.CENTER)
|
|
1980
|
+
if (!icon.has("backgroundColor")) leadingFrame.background = null
|
|
1981
|
+
}
|
|
1982
|
+
addView(mainColumn, weighted())
|
|
1983
|
+
showText(title, item.json.optString("title"), 1)
|
|
1984
|
+
if (isAccountSelector) {
|
|
1985
|
+
title.typeface = NativeListFonts.regular(context)
|
|
1986
|
+
title.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
1987
|
+
} else if (item.json.optString("tone") == "danger") {
|
|
1988
|
+
title.setTextColor(color(theme, "negative", "#C40006D3"))
|
|
1989
|
+
}
|
|
1990
|
+
item.json.optJSONObject("checkbox")?.let {
|
|
1991
|
+
bindCheckbox(item, it, checkboxState)
|
|
1992
|
+
addView(trailingColumn, wrap())
|
|
1993
|
+
} ?: item.json.optJSONArray("trailing")?.let { accessories ->
|
|
1994
|
+
if (accessories.length() > 0) {
|
|
1995
|
+
addView(trailingColumn, wrap())
|
|
1996
|
+
bindAccessories(item, accessories, theme, checkboxState)
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
setOnClickListener {
|
|
2000
|
+
emitAction(item, item.json.optString("actionKey"), null, this, "row")
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
private fun bindSystem(item: NativeListItem, theme: JSONObject?) {
|
|
2005
|
+
val variant = item.json.optString("variant")
|
|
2006
|
+
if (variant == "spacer") {
|
|
2007
|
+
minimumHeight = dp(item.json.optInt("height", 0))
|
|
2008
|
+
return
|
|
2009
|
+
}
|
|
2010
|
+
if (variant == "end") {
|
|
2011
|
+
// components/ListEndIndicator: py=16, gap=8, 80x1 lines and a 4dp dot.
|
|
2012
|
+
gravity = Gravity.CENTER
|
|
2013
|
+
setPadding(0, dp(16), 0, dp(16))
|
|
2014
|
+
val indicatorColor = color(theme, "separator", "#0000001F")
|
|
2015
|
+
addView(View(context).apply { setBackgroundColor(indicatorColor) }, LayoutParams(dp(80), 1))
|
|
2016
|
+
addView(
|
|
2017
|
+
View(context).apply { background = roundedFill(indicatorColor, 2f) },
|
|
2018
|
+
LayoutParams(dp(4), dp(4)).apply {
|
|
2019
|
+
marginStart = dp(8)
|
|
2020
|
+
marginEnd = dp(8)
|
|
2021
|
+
},
|
|
2022
|
+
)
|
|
2023
|
+
addView(View(context).apply { setBackgroundColor(indicatorColor) }, LayoutParams(dp(80), 1))
|
|
2024
|
+
return
|
|
2025
|
+
}
|
|
2026
|
+
gravity = Gravity.CENTER
|
|
2027
|
+
if (variant == "loading") {
|
|
2028
|
+
addLeading(JSONObject().put("kind", "skeleton"), 40)
|
|
2029
|
+
leadingFallback.text = ""
|
|
2030
|
+
leadingFallback.background = roundedFill(
|
|
2031
|
+
color(theme, "strongBackground", "#0000000F"),
|
|
2032
|
+
20f,
|
|
2033
|
+
)
|
|
2034
|
+
addSkeleton(skeletonPrimary, 120, 12, theme, bottomMarginDp = 8)
|
|
2035
|
+
addSkeleton(skeletonSecondary, 80, 12, theme)
|
|
2036
|
+
}
|
|
2037
|
+
showText(title, item.json.optString("message"), 2)
|
|
2038
|
+
title.gravity = if (variant == "retry" || variant == "noMatch") {
|
|
2039
|
+
Gravity.START
|
|
2040
|
+
} else {
|
|
2041
|
+
Gravity.CENTER
|
|
2042
|
+
}
|
|
2043
|
+
mainColumn.gravity = Gravity.CENTER_VERTICAL
|
|
2044
|
+
if (variant == "noMatch") {
|
|
2045
|
+
setPadding(dp(12), 0, dp(12), 0)
|
|
2046
|
+
title.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
2047
|
+
} else if (variant == "retry") {
|
|
2048
|
+
setPadding(dp(12), dp(8), dp(12), dp(8))
|
|
2049
|
+
title.setTextColor(color(theme, "secondaryText", "#0000009B"))
|
|
2050
|
+
}
|
|
2051
|
+
addView(
|
|
2052
|
+
mainColumn,
|
|
2053
|
+
if (variant == "retry" || variant == "noMatch") weighted() else wrap(),
|
|
2054
|
+
)
|
|
2055
|
+
if (variant == "retry") {
|
|
2056
|
+
setOnClickListener {
|
|
2057
|
+
emitAction(item, item.json.optString("actionKey"), null, this, "row")
|
|
2058
|
+
}
|
|
2059
|
+
showTrailing(0, "Retry", true, item.json.optString("actionKey"))
|
|
2060
|
+
trailingViews[0].textSize = sp(14f)
|
|
2061
|
+
trailingViews[0].background = roundedFill(color(theme, "strongBackground", "#0000000F"), 16f)
|
|
2062
|
+
trailingViews[0].setPadding(dp(10), dp(4), dp(10), dp(4))
|
|
2063
|
+
TextViewCompat.setLineHeight(trailingViews[0], dp(20))
|
|
2064
|
+
addView(trailingColumn, wrap().apply { marginStart = dp(12) })
|
|
2065
|
+
}
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
private fun addLeading(
|
|
2069
|
+
visual: JSONObject?,
|
|
2070
|
+
sizeDp: Int = 40,
|
|
2071
|
+
secondaryVisual: JSONObject? = null,
|
|
2072
|
+
spacingDp: Int = 12,
|
|
2073
|
+
) {
|
|
2074
|
+
leadingFrame.visibility = VISIBLE
|
|
2075
|
+
leadingFrame.layoutParams = LayoutParams(dp(sizeDp), dp(sizeDp)).apply { marginEnd = dp(spacingDp) }
|
|
2076
|
+
addView(leadingFrame)
|
|
2077
|
+
leadingFallback.layoutParams = FrameLayout.LayoutParams(dp(sizeDp), dp(sizeDp))
|
|
2078
|
+
if (visual == null) return
|
|
2079
|
+
val kind = visual.optString("kind")
|
|
2080
|
+
val shape = visual.optString(
|
|
2081
|
+
"shape",
|
|
2082
|
+
if (kind == "image" || isMediaTile) "rounded" else "circle",
|
|
2083
|
+
)
|
|
2084
|
+
val sources = visualSources(visual).toMutableList()
|
|
2085
|
+
secondaryVisual?.let { sources.addAll(visualSources(it).take(1)) }
|
|
2086
|
+
val isIcon = kind == "icon"
|
|
2087
|
+
val cornerIconData = visual.optJSONObject("cornerIcon")
|
|
2088
|
+
val fallback = visual.optString("fallbackText").take(2)
|
|
2089
|
+
leadingFallback.text = fallback
|
|
2090
|
+
leadingFallback.setTextColor(parseNativeListColor("#00000072"))
|
|
2091
|
+
val visualBackground = safeColor(
|
|
2092
|
+
visual.optString("backgroundColor"),
|
|
2093
|
+
parseNativeListColor(if (isIcon) "#0000000F" else "#E0E0E0"),
|
|
2094
|
+
)
|
|
2095
|
+
if (!isIcon && visual.optString("backgroundColor").isNotEmpty()) {
|
|
2096
|
+
leadingFrame.background = roundedFill(
|
|
2097
|
+
visualBackground,
|
|
2098
|
+
leadingCornerRadius(shape, sizeDp),
|
|
2099
|
+
)
|
|
2100
|
+
}
|
|
2101
|
+
leadingFallback.background = roundedFill(visualBackground, leadingCornerRadius(shape, sizeDp))
|
|
2102
|
+
leadingFallback.visibility = if (!isIcon && sources.isEmpty()) VISIBLE else GONE
|
|
2103
|
+
if (isIcon) {
|
|
2104
|
+
leadingFrame.background = GradientDrawable().apply {
|
|
2105
|
+
setColor(visualBackground)
|
|
2106
|
+
setStroke(1, parseNativeListColor("#0000001F"))
|
|
2107
|
+
cornerRadius = NativeListScale.dp(resources, leadingCornerRadius(shape, sizeDp))
|
|
2108
|
+
}
|
|
2109
|
+
leadingIcon.iconName = visual.optString("name")
|
|
2110
|
+
leadingIcon.tintColor = safeColor(
|
|
2111
|
+
visual.optString("tintColor"),
|
|
2112
|
+
parseNativeListColor("#0000009B"),
|
|
2113
|
+
)
|
|
2114
|
+
leadingIcon.visibility = VISIBLE
|
|
2115
|
+
}
|
|
2116
|
+
val visibleSources = sources.take(leadingImages.size)
|
|
2117
|
+
val tokenPair = kind == "token" && visibleSources.size > 1
|
|
2118
|
+
if (tokenPair) {
|
|
2119
|
+
leadingOverlayBackground.visibility = VISIBLE
|
|
2120
|
+
leadingOverlayBackground.background = roundedFill(visualBackdropColor, 10f)
|
|
2121
|
+
leadingOverlayBackground.layoutParams = FrameLayout.LayoutParams(
|
|
2122
|
+
dp(20),
|
|
2123
|
+
dp(20),
|
|
2124
|
+
Gravity.END or Gravity.BOTTOM,
|
|
2125
|
+
).apply {
|
|
2126
|
+
marginEnd = -dp(4)
|
|
2127
|
+
bottomMargin = -dp(4)
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
if (cornerIconData != null) {
|
|
2131
|
+
leadingCornerIconFrame.visibility = VISIBLE
|
|
2132
|
+
leadingCornerIconFrame.background = roundedFill(
|
|
2133
|
+
safeColor(cornerIconData.optString("backgroundColor"), visualBackdropColor),
|
|
2134
|
+
10f,
|
|
2135
|
+
)
|
|
2136
|
+
leadingCornerIconFrame.layoutParams = FrameLayout.LayoutParams(
|
|
2137
|
+
dp(20),
|
|
2138
|
+
dp(20),
|
|
2139
|
+
Gravity.END or Gravity.BOTTOM,
|
|
2140
|
+
).apply {
|
|
2141
|
+
marginEnd = -dp(4)
|
|
2142
|
+
bottomMargin = -dp(4)
|
|
2143
|
+
}
|
|
2144
|
+
leadingCornerIcon.iconName = cornerIconData.optString("name")
|
|
2145
|
+
leadingCornerIcon.tintColor = safeColor(
|
|
2146
|
+
cornerIconData.optString("tintColor"),
|
|
2147
|
+
parseNativeListColor("#0000009B"),
|
|
2148
|
+
)
|
|
2149
|
+
}
|
|
2150
|
+
visibleSources.forEachIndexed { index, (source, variant) ->
|
|
2151
|
+
val image = leadingImages[index]
|
|
2152
|
+
image.visibility = VISIBLE
|
|
2153
|
+
image.layoutParams = leadingImageLayout(
|
|
2154
|
+
index = index,
|
|
2155
|
+
count = visibleSources.size,
|
|
2156
|
+
sizeDp = sizeDp,
|
|
2157
|
+
tokenPair = tokenPair,
|
|
2158
|
+
)
|
|
2159
|
+
image.outlineProvider = when {
|
|
2160
|
+
tokenPair && index == 1 -> circleOutlineProvider
|
|
2161
|
+
else -> leadingOutlineProvider(shape)
|
|
2162
|
+
}
|
|
2163
|
+
image.clipToOutline = true
|
|
2164
|
+
bindImage(source, image, boundKey ?: "", index, variant)
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
private fun visualSources(visual: JSONObject): List<Pair<JSONObject, String>> {
|
|
2169
|
+
return when (val kind = visual.optString("kind")) {
|
|
2170
|
+
"stackedImages" -> {
|
|
2171
|
+
val images = visual.optJSONArray("images") ?: return emptyList()
|
|
2172
|
+
(0 until minOf(3, images.length())).mapNotNull { index ->
|
|
2173
|
+
images.optJSONObject(index)?.let { it to "generic" }
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
"icon" -> emptyList()
|
|
2177
|
+
else -> buildList {
|
|
2178
|
+
visual.optJSONObject("image")?.let {
|
|
2179
|
+
val variant = when (kind) {
|
|
2180
|
+
"token" -> "token"
|
|
2181
|
+
"network" -> "network"
|
|
2182
|
+
"account", "wallet" -> "avatar"
|
|
2183
|
+
else -> "generic"
|
|
2184
|
+
}
|
|
2185
|
+
add(it to variant)
|
|
2186
|
+
}
|
|
2187
|
+
if (kind == "token") {
|
|
2188
|
+
visual.optJSONObject("networkImage")?.let { add(it to "network") }
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
private fun leadingImageLayout(
|
|
2195
|
+
index: Int,
|
|
2196
|
+
count: Int,
|
|
2197
|
+
sizeDp: Int,
|
|
2198
|
+
tokenPair: Boolean,
|
|
2199
|
+
): FrameLayout.LayoutParams {
|
|
2200
|
+
if (count == 1 || tokenPair && index == 0) {
|
|
2201
|
+
return FrameLayout.LayoutParams(dp(sizeDp), dp(sizeDp))
|
|
2202
|
+
}
|
|
2203
|
+
if (tokenPair && index == 1) {
|
|
2204
|
+
return FrameLayout.LayoutParams(dp(16), dp(16), Gravity.END or Gravity.BOTTOM).apply {
|
|
2205
|
+
marginEnd = -dp(2)
|
|
2206
|
+
bottomMargin = -dp(2)
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
val imageSize = (sizeDp * 0.72f).toInt()
|
|
2210
|
+
return FrameLayout.LayoutParams(dp(imageSize), dp(imageSize), Gravity.START or Gravity.CENTER_VERTICAL).apply {
|
|
2211
|
+
marginStart = dp(index * (sizeDp - imageSize) / maxOf(1, count - 1))
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
private fun bindAccessories(
|
|
2216
|
+
item: NativeListItem,
|
|
2217
|
+
accessories: JSONArray?,
|
|
2218
|
+
theme: JSONObject?,
|
|
2219
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
2220
|
+
) {
|
|
2221
|
+
if (accessories == null) return
|
|
2222
|
+
var textIndex = 0
|
|
2223
|
+
for (index in 0 until minOf(2, accessories.length())) {
|
|
2224
|
+
val accessory = accessories.getJSONObject(index)
|
|
2225
|
+
when (accessory.optString("kind")) {
|
|
2226
|
+
"value" -> showTrailing(textIndex++, accessory.optString("text"), !accessory.optBoolean("secondary", false))
|
|
2227
|
+
"valuePair" -> showTrailingValuePair(textIndex++, accessory, theme)
|
|
2228
|
+
"checkbox" -> bindCheckbox(item, accessory, checkboxState)
|
|
2229
|
+
"radio" -> showTrailing(
|
|
2230
|
+
textIndex++,
|
|
2231
|
+
if (accessory.optBoolean("checked")) "●" else "○",
|
|
2232
|
+
true,
|
|
2233
|
+
accessory.optString("actionKey").takeUnless { accessory.optBoolean("disabled", false) },
|
|
2234
|
+
)
|
|
2235
|
+
"switch" -> showTrailing(
|
|
2236
|
+
textIndex++,
|
|
2237
|
+
if (accessory.optBoolean("value")) "ON" else "OFF",
|
|
2238
|
+
true,
|
|
2239
|
+
accessory.optString("actionKey").takeUnless { accessory.optBoolean("disabled", false) },
|
|
2240
|
+
)
|
|
2241
|
+
"chevron" -> showTrailingIcon(
|
|
2242
|
+
textIndex++,
|
|
2243
|
+
item,
|
|
2244
|
+
JSONObject(accessory.toString()).apply {
|
|
2245
|
+
put("name", "ChevronRightSmallOutline")
|
|
2246
|
+
if (optString("actionKey").isEmpty()) put("actionKey", "press")
|
|
2247
|
+
},
|
|
2248
|
+
)
|
|
2249
|
+
"menu" -> showTrailingMenu(textIndex++, accessory.optString("actionKey"))
|
|
2250
|
+
"drag" -> showTrailingIcon(
|
|
2251
|
+
textIndex++,
|
|
2252
|
+
item,
|
|
2253
|
+
JSONObject(accessory.toString()).apply { put("name", "DragOutline") },
|
|
2254
|
+
)
|
|
2255
|
+
"icon" -> showTrailingIcon(textIndex++, item, accessory)
|
|
2256
|
+
"spinner" -> spinner.visibility = VISIBLE
|
|
2257
|
+
"progress" -> showTrailing(textIndex++, "${(accessory.optDouble("value") * 100).toInt()}%", false)
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
private fun bindCheckbox(
|
|
2263
|
+
item: NativeListItem,
|
|
2264
|
+
json: JSONObject,
|
|
2265
|
+
checkboxState: (NativeListItem, NativeSelectionTarget?, String) -> String,
|
|
2266
|
+
) {
|
|
2267
|
+
boundCheckboxData = json
|
|
2268
|
+
val targetJson = json.optJSONObject("target")
|
|
2269
|
+
val scope = targetJson?.optString("scope", "row") ?: "row"
|
|
2270
|
+
val target = NativeSelectionTarget(
|
|
2271
|
+
scope,
|
|
2272
|
+
when (scope) {
|
|
2273
|
+
"section" -> targetJson?.optString("sectionKey")
|
|
2274
|
+
"row" -> item.key
|
|
2275
|
+
else -> null
|
|
2276
|
+
},
|
|
2277
|
+
)
|
|
2278
|
+
val state = checkboxState(item, target, json.optString("state", "unchecked"))
|
|
2279
|
+
val accessoryDisabled = json.optBoolean("disabled", false)
|
|
2280
|
+
if (json.optBoolean("loading", false)) {
|
|
2281
|
+
checkbox.visibility = GONE
|
|
2282
|
+
checkbox.setOnClickListener(null)
|
|
2283
|
+
spinner.visibility = VISIBLE
|
|
2284
|
+
spinner.alpha = if (!item.json.optBoolean("disabled", false) && accessoryDisabled) 0.5f else 1f
|
|
2285
|
+
return
|
|
2286
|
+
}
|
|
2287
|
+
checkbox.visibility = VISIBLE
|
|
2288
|
+
checkbox.setState(state, checkboxUncheckedColor)
|
|
2289
|
+
checkbox.background = if (state == "unchecked") {
|
|
2290
|
+
roundedStroke(checkboxBorderColor, checkboxUncheckedColor, 4f)
|
|
2291
|
+
} else {
|
|
2292
|
+
roundedFill(checkboxCheckedColor, 4f)
|
|
2293
|
+
}
|
|
2294
|
+
// Row-level disabled opacity already applies to this child. Only apply a
|
|
2295
|
+
// local 0.5 when the accessory alone is disabled, never 0.5 * 0.5.
|
|
2296
|
+
checkbox.alpha = if (!item.json.optBoolean("disabled", false) && accessoryDisabled) 0.5f else 1f
|
|
2297
|
+
checkbox.isEnabled =
|
|
2298
|
+
!item.json.optBoolean("disabled", false) &&
|
|
2299
|
+
!accessoryDisabled &&
|
|
2300
|
+
!json.optBoolean("loading", false)
|
|
2301
|
+
checkbox.setOnClickListener {
|
|
2302
|
+
emitAction(
|
|
2303
|
+
item,
|
|
2304
|
+
json.optString("actionKey", "selection"),
|
|
2305
|
+
target,
|
|
2306
|
+
checkbox,
|
|
2307
|
+
"trailingAccessory",
|
|
2308
|
+
)
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
|
|
2312
|
+
private fun applySelectionState(
|
|
2313
|
+
item: NativeListItem,
|
|
2314
|
+
theme: JSONObject?,
|
|
2315
|
+
layout: String,
|
|
2316
|
+
itemIndex: Int?,
|
|
2317
|
+
selected: Boolean,
|
|
2318
|
+
) {
|
|
2319
|
+
var rowBackground = color(
|
|
2320
|
+
theme,
|
|
2321
|
+
if (selected) "rowSelectedBackground" else "rowBackground",
|
|
2322
|
+
if (selected) "#0000000F" else "#FFFFFF",
|
|
2323
|
+
)
|
|
2324
|
+
if (item.type == "rail" || item.type == "mediaTile") {
|
|
2325
|
+
// The source FavoriteTokenItem has no active/resting fill, and the NFT
|
|
2326
|
+
// tile changes only the image opacity while pressed.
|
|
2327
|
+
rowBackground = color(theme, "rowBackground", "#FFFFFF")
|
|
2328
|
+
} else if (item.type == "metricCard" && !selected) {
|
|
2329
|
+
rowBackground = color(theme, "subduedBackground", "#F9F9F9")
|
|
2330
|
+
}
|
|
2331
|
+
val groupPosition = when {
|
|
2332
|
+
item.type == "identity" && item.json.optString("presentation") == "walletSidebar" -> "single"
|
|
2333
|
+
else -> when (item.type) {
|
|
2334
|
+
"metricCard" -> "single"
|
|
2335
|
+
"rail" -> "rail"
|
|
2336
|
+
else -> item.json.optString("groupPosition")
|
|
2337
|
+
}
|
|
2338
|
+
}
|
|
2339
|
+
var backgroundGroupPosition = if (item.type == "mediaTile") "mediaTile" else groupPosition
|
|
2340
|
+
if (layout == "sectioned") {
|
|
2341
|
+
// Selection in sectioned lists is represented by the OneKey checkbox,
|
|
2342
|
+
// matching iOS and the app-monorepo network selector.
|
|
2343
|
+
rowBackground = color(theme, "rowBackground", "#FFFFFF")
|
|
2344
|
+
if (item.type == "sectionHeader") backgroundGroupPosition = ""
|
|
2345
|
+
} else if (
|
|
2346
|
+
layout == "table" &&
|
|
2347
|
+
item.type == "dataRow" &&
|
|
2348
|
+
(if (item.json.has("index")) item.json.optInt("index") else itemIndex)?.rem(2) == 0 &&
|
|
2349
|
+
!selected
|
|
2350
|
+
) {
|
|
2351
|
+
rowBackground = color(theme, "subduedBackground", "#F9F9F9")
|
|
2352
|
+
backgroundGroupPosition = ""
|
|
2353
|
+
}
|
|
2354
|
+
restingRowBackground = groupedBackground(backgroundGroupPosition, rowBackground)
|
|
2355
|
+
background = restingRowBackground
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
private fun showText(view: TextView, value: String, lines: Int) {
|
|
2359
|
+
if (value.isEmpty()) return
|
|
2360
|
+
view.text = value
|
|
2361
|
+
view.maxLines = lines
|
|
2362
|
+
view.visibility = VISIBLE
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
private fun showTrailing(
|
|
2366
|
+
index: Int,
|
|
2367
|
+
value: String,
|
|
2368
|
+
primary: Boolean,
|
|
2369
|
+
actionKey: String? = null,
|
|
2370
|
+
textColor: Int? = null,
|
|
2371
|
+
) {
|
|
2372
|
+
if (index !in trailingViews.indices || value.isEmpty()) return
|
|
2373
|
+
val view = trailingViews[index]
|
|
2374
|
+
view.maxLines = 1
|
|
2375
|
+
view.text = value
|
|
2376
|
+
view.textSize = sp(if (primary) 16f else 14f)
|
|
2377
|
+
view.typeface = if (primary) NativeListFonts.medium(context) else NativeListFonts.regular(context)
|
|
2378
|
+
view.includeFontPadding = false
|
|
2379
|
+
view.fontFeatureSettings = "tnum"
|
|
2380
|
+
TextViewCompat.setLineHeight(view, dp(if (primary) 24 else 20))
|
|
2381
|
+
textColor?.let(view::setTextColor)
|
|
2382
|
+
view.visibility = VISIBLE
|
|
2383
|
+
if (!actionKey.isNullOrEmpty()) {
|
|
2384
|
+
view.setOnClickListener {
|
|
2385
|
+
(tag as? NativeListItem)
|
|
2386
|
+
?.takeUnless { item -> item.json.optBoolean("disabled", false) }
|
|
2387
|
+
?.let { item ->
|
|
2388
|
+
emitAction(item, actionKey, null, view, "trailingAccessory", index)
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
private fun showTrailingValuePair(index: Int, accessory: JSONObject, theme: JSONObject?) {
|
|
2395
|
+
if (index !in trailingViews.indices) return
|
|
2396
|
+
val primary = accessory.optString("primary")
|
|
2397
|
+
val secondary = accessory.optString("secondary")
|
|
2398
|
+
if (primary.isEmpty() && secondary.isEmpty()) return
|
|
2399
|
+
val value = SpannableStringBuilder()
|
|
2400
|
+
val primaryStart = value.length
|
|
2401
|
+
value.append(primary)
|
|
2402
|
+
value.setSpan(
|
|
2403
|
+
ForegroundColorSpan(accessoryTextColor(accessory.optString("primaryTone"), "primary", theme)),
|
|
2404
|
+
primaryStart,
|
|
2405
|
+
value.length,
|
|
2406
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2407
|
+
)
|
|
2408
|
+
value.setSpan(
|
|
2409
|
+
AbsoluteSizeSpan(
|
|
2410
|
+
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp(16f), resources.displayMetrics).roundToInt(),
|
|
2411
|
+
),
|
|
2412
|
+
primaryStart,
|
|
2413
|
+
value.length,
|
|
2414
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2415
|
+
)
|
|
2416
|
+
if (primary.isNotEmpty() && secondary.isNotEmpty()) value.append('\n')
|
|
2417
|
+
val secondaryStart = value.length
|
|
2418
|
+
value.append(secondary)
|
|
2419
|
+
value.setSpan(
|
|
2420
|
+
ForegroundColorSpan(accessoryTextColor(accessory.optString("secondaryTone"), "secondary", theme)),
|
|
2421
|
+
secondaryStart,
|
|
2422
|
+
value.length,
|
|
2423
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2424
|
+
)
|
|
2425
|
+
value.setSpan(
|
|
2426
|
+
AbsoluteSizeSpan(
|
|
2427
|
+
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp(14f), resources.displayMetrics).roundToInt(),
|
|
2428
|
+
),
|
|
2429
|
+
secondaryStart,
|
|
2430
|
+
value.length,
|
|
2431
|
+
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
|
|
2432
|
+
)
|
|
2433
|
+
val view = trailingViews[index]
|
|
2434
|
+
view.text = value
|
|
2435
|
+
view.maxLines = 2
|
|
2436
|
+
view.gravity = Gravity.END
|
|
2437
|
+
view.includeFontPadding = false
|
|
2438
|
+
view.typeface = NativeListFonts.regular(context)
|
|
2439
|
+
view.fontFeatureSettings = "tnum"
|
|
2440
|
+
TextViewCompat.setLineHeight(view, dp(20))
|
|
2441
|
+
view.visibility = VISIBLE
|
|
2442
|
+
}
|
|
2443
|
+
|
|
2444
|
+
private fun showTrailingMenu(index: Int, actionKey: String) {
|
|
2445
|
+
showTrailing(index, "⋮", true, actionKey)
|
|
2446
|
+
if (index !in trailingViews.indices) return
|
|
2447
|
+
trailingViews[index].apply {
|
|
2448
|
+
gravity = Gravity.CENTER
|
|
2449
|
+
textSize = sp(20f)
|
|
2450
|
+
layoutParams = LayoutParams(dp(24), dp(24)).apply {
|
|
2451
|
+
if (trailingColumn.orientation == HORIZONTAL && index > 0) marginStart = dp(8)
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
}
|
|
2455
|
+
|
|
2456
|
+
private fun accessoryTextColor(tone: String, defaultTone: String, theme: JSONObject?): Int =
|
|
2457
|
+
when (tone.ifEmpty { defaultTone }) {
|
|
2458
|
+
"positive" -> color(theme, "positive", "#00713FDE")
|
|
2459
|
+
"negative" -> color(theme, "negative", "#C40006D3")
|
|
2460
|
+
"secondary" -> color(theme, "secondaryText", "#0000009B")
|
|
2461
|
+
else -> color(theme, "primaryText", "#000000DF")
|
|
2462
|
+
}
|
|
2463
|
+
|
|
2464
|
+
private fun showTrailingIcon(index: Int, item: NativeListItem, data: JSONObject) {
|
|
2465
|
+
if (index !in trailingIcons.indices) return
|
|
2466
|
+
val icon = trailingIcons[index]
|
|
2467
|
+
icon.iconName = data.optString("name")
|
|
2468
|
+
icon.tintColor = safeColor(data.optString("tintColor"), iconSubduedColor)
|
|
2469
|
+
if (icon.iconName == "ChevronRightSmallOutline") {
|
|
2470
|
+
icon.glyphSizeDp = null
|
|
2471
|
+
// ListItem.DrillIn is a 24dp icon with mx=-6, for a 12dp layout footprint.
|
|
2472
|
+
icon.layoutParams = LayoutParams(dp(24), dp(24)).apply {
|
|
2473
|
+
marginStart = -dp(6)
|
|
2474
|
+
marginEnd = -dp(6)
|
|
2475
|
+
}
|
|
2476
|
+
} else {
|
|
2477
|
+
// ListItem.IconButton medium keeps a 24dp glyph in a 36dp frame. Its
|
|
2478
|
+
// m=-7 moves the last frame 7dp through the row's trailing padding. For
|
|
2479
|
+
// the Bookmark XStack, gap=$6 plus both negative margins leaves 10dp
|
|
2480
|
+
// between the physical frames (46dp between glyph centers).
|
|
2481
|
+
icon.glyphSizeDp = 24
|
|
2482
|
+
setPadding(paddingLeft, paddingTop, dp(5), paddingBottom)
|
|
2483
|
+
icon.layoutParams = LayoutParams(dp(36), dp(36)).apply {
|
|
2484
|
+
gravity = Gravity.END
|
|
2485
|
+
if (trailingColumn.orientation == HORIZONTAL && index > 0) {
|
|
2486
|
+
marginStart = dp(10)
|
|
2487
|
+
}
|
|
2488
|
+
}
|
|
2489
|
+
}
|
|
2490
|
+
icon.visibility = VISIBLE
|
|
2491
|
+
icon.isEnabled = !data.optBoolean("disabled", false)
|
|
2492
|
+
icon.alpha = if (icon.isEnabled) 1f else 0.4f
|
|
2493
|
+
val actionKey = data.optString("actionKey")
|
|
2494
|
+
if (icon.isEnabled && actionKey.isNotEmpty()) {
|
|
2495
|
+
icon.setOnClickListener {
|
|
2496
|
+
emitAction(item, actionKey, null, icon, "trailingAccessory", index)
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
private fun applySize(item: NativeListItem) {
|
|
2502
|
+
val isWalletSidebar =
|
|
2503
|
+
item.type == "identity" && item.json.optString("presentation") == "walletSidebar"
|
|
2504
|
+
val isAccountSelectorIdentity =
|
|
2505
|
+
item.type == "identity" && item.json.optString("presentation") == "accountSelector"
|
|
2506
|
+
val isNetworkSelectorIdentity =
|
|
2507
|
+
item.type == "identity" && item.json.optString("presentation") == "networkSelector"
|
|
2508
|
+
val isAccountSelectorAction =
|
|
2509
|
+
item.type == "action" && item.json.optString("presentation") == "accountSelector"
|
|
2510
|
+
val isNetworkSelectorSection =
|
|
2511
|
+
item.type == "sectionHeader" && item.json.optString("presentation") == "networkSelector"
|
|
2512
|
+
title.textSize = sp(
|
|
2513
|
+
if (isWalletSidebar) {
|
|
2514
|
+
12f
|
|
2515
|
+
} else {
|
|
2516
|
+
when (item.type) {
|
|
2517
|
+
"message" -> 14f
|
|
2518
|
+
"sectionHeader" -> when {
|
|
2519
|
+
isNetworkSelectorSection -> 14f
|
|
2520
|
+
item.json.optString("variant") == "gallery" -> 18f
|
|
2521
|
+
item.json.optString("variant") == "summary" -> 16f
|
|
2522
|
+
currentLayout == "table" -> 11f
|
|
2523
|
+
item.json.optString("variant") == "history" || item.sectionKey?.startsWith("history-") == true -> 12f
|
|
2524
|
+
else -> 14f
|
|
2525
|
+
}
|
|
2526
|
+
"rail" -> 12f
|
|
2527
|
+
"system" -> 14f
|
|
2528
|
+
"metricCard" -> if (item.json.optString("size") == "large") 24f else 18f
|
|
2529
|
+
else -> 16f
|
|
2530
|
+
}
|
|
2531
|
+
},
|
|
2532
|
+
)
|
|
2533
|
+
title.typeface = if (isWalletSidebar || isAccountSelectorIdentity || isAccountSelectorAction) {
|
|
2534
|
+
NativeListFonts.regular(context)
|
|
2535
|
+
} else {
|
|
2536
|
+
when (item.type) {
|
|
2537
|
+
"sectionHeader" -> when {
|
|
2538
|
+
isNetworkSelectorSection -> NativeListFonts.medium(context)
|
|
2539
|
+
currentLayout == "table" -> NativeListFonts.regular(context)
|
|
2540
|
+
item.json.optString("variant") == "summary" -> NativeListFonts.medium(context)
|
|
2541
|
+
item.sectionKey in setOf("linear-tokens", "action-tokens") -> NativeListFonts.regular(context)
|
|
2542
|
+
else -> NativeListFonts.semibold(context)
|
|
2543
|
+
}
|
|
2544
|
+
"system" -> NativeListFonts.regular(context)
|
|
2545
|
+
"message", "metricCard" -> NativeListFonts.semibold(context)
|
|
2546
|
+
else -> NativeListFonts.medium(context)
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
subtitle.textSize = sp(when (item.type) {
|
|
2550
|
+
"mediaTile" -> 12f
|
|
2551
|
+
"metricCard" -> 11f
|
|
2552
|
+
else -> 14f
|
|
2553
|
+
})
|
|
2554
|
+
tertiary.textSize = sp(14f)
|
|
2555
|
+
if (isNetworkSelectorSection) {
|
|
2556
|
+
TextViewCompat.setLineHeight(title, dp(20))
|
|
2557
|
+
} else if (item.type == "sectionHeader" && item.json.optString("variant") == "gallery") {
|
|
2558
|
+
TextViewCompat.setLineHeight(title, dp(24))
|
|
2559
|
+
} else if (item.type == "sectionHeader" && item.json.optString("variant") == "summary") {
|
|
2560
|
+
TextViewCompat.setLineHeight(title, dp(24))
|
|
2561
|
+
} else if (item.type == "sectionHeader" && currentLayout == "table") {
|
|
2562
|
+
TextViewCompat.setLineHeight(title, dp(14))
|
|
2563
|
+
TextViewCompat.setLineHeight(trailingViews[0], dp(14))
|
|
2564
|
+
} else if (
|
|
2565
|
+
item.type == "sectionHeader" &&
|
|
2566
|
+
(item.json.optString("variant") == "history" || item.sectionKey?.startsWith("history-") == true)
|
|
2567
|
+
) {
|
|
2568
|
+
TextViewCompat.setLineHeight(title, dp(16))
|
|
2569
|
+
} else if (item.type == "sectionHeader" && item.sectionKey in setOf("linear-tokens", "action-tokens")) {
|
|
2570
|
+
TextViewCompat.setLineHeight(title, dp(20))
|
|
2571
|
+
} else if (item.type == "identity" && item.json.optString("presentation") == "walletSidebar") {
|
|
2572
|
+
TextViewCompat.setLineHeight(title, dp(16))
|
|
2573
|
+
} else if (item.type == "identity") {
|
|
2574
|
+
TextViewCompat.setLineHeight(title, dp(24))
|
|
2575
|
+
TextViewCompat.setLineHeight(subtitle, dp(20))
|
|
2576
|
+
TextViewCompat.setLineHeight(tertiary, dp(20))
|
|
2577
|
+
} else if (item.type == "action") {
|
|
2578
|
+
TextViewCompat.setLineHeight(title, dp(24))
|
|
2579
|
+
} else if (item.type == "system") {
|
|
2580
|
+
TextViewCompat.setLineHeight(title, dp(20))
|
|
2581
|
+
} else if (item.type == "mediaTile") {
|
|
2582
|
+
TextViewCompat.setLineHeight(title, dp(24))
|
|
2583
|
+
TextViewCompat.setLineHeight(subtitle, dp(16))
|
|
2584
|
+
}
|
|
2585
|
+
status.textSize = sp(12f)
|
|
2586
|
+
badgeLine.textSize = sp(12f)
|
|
2587
|
+
dataColumns.forEach { column ->
|
|
2588
|
+
column.textSize = sp(16f)
|
|
2589
|
+
column.typeface = NativeListFonts.medium(context)
|
|
2590
|
+
column.fontFeatureSettings = "tnum"
|
|
2591
|
+
}
|
|
2592
|
+
val baseHeight = when {
|
|
2593
|
+
item.type == "system" && item.json.optString("variant") == "spacer" -> item.json.optInt("height", 0)
|
|
2594
|
+
item.type == "walletGroup" -> {
|
|
2595
|
+
val childCount = item.json.optJSONArray("children")?.length() ?: 0
|
|
2596
|
+
(childCount + 1) * 68 + childCount * 12
|
|
2597
|
+
}
|
|
2598
|
+
isNetworkSelectorIdentity -> 47
|
|
2599
|
+
else -> when (item.type) {
|
|
2600
|
+
"rail" -> 28
|
|
2601
|
+
"activity" -> if ((item.json.optJSONArray("footerActions")?.length() ?: 0) > 0) 104 else 60
|
|
2602
|
+
"message" -> 0
|
|
2603
|
+
"mediaTile" -> 0
|
|
2604
|
+
"metricCard" -> when (item.json.optString("variant")) {
|
|
2605
|
+
"activity" -> 0
|
|
2606
|
+
"performance" -> 0
|
|
2607
|
+
else -> 132
|
|
2608
|
+
}
|
|
2609
|
+
"sectionHeader" -> when {
|
|
2610
|
+
isNetworkSelectorSection -> 44
|
|
2611
|
+
currentLayout == "table" -> 28
|
|
2612
|
+
item.json.optString("variant") == "summary" -> 80
|
|
2613
|
+
item.json.optString("variant") == "gallery" -> 32
|
|
2614
|
+
item.json.optString("variant") == "history" || item.sectionKey?.startsWith("history-") == true -> 16
|
|
2615
|
+
item.sectionKey in setOf("linear-tokens", "action-tokens") -> 30
|
|
2616
|
+
item.json.optString("value").isNotEmpty() && item.json.optJSONObject("checkbox") != null -> 40
|
|
2617
|
+
else -> 36
|
|
2618
|
+
}
|
|
2619
|
+
"system" -> when (item.json.optString("variant")) {
|
|
2620
|
+
"noMatch", "end" -> 36
|
|
2621
|
+
"retry" -> 44
|
|
2622
|
+
else -> 56
|
|
2623
|
+
}
|
|
2624
|
+
"action" -> when {
|
|
2625
|
+
item.json.optString("presentation") == "accountSelector" -> 48
|
|
2626
|
+
item.json.has("icon") -> 60
|
|
2627
|
+
else -> 44
|
|
2628
|
+
}
|
|
2629
|
+
"dataRow" -> if (currentLayout == "table") {
|
|
2630
|
+
60
|
|
2631
|
+
} else if ((0 until item.json.getJSONArray("columns").length()).any {
|
|
2632
|
+
item.json.getJSONArray("columns").getJSONObject(it).optString("secondaryText").isNotEmpty()
|
|
2633
|
+
}) {
|
|
2634
|
+
64
|
|
2635
|
+
} else {
|
|
2636
|
+
56
|
|
2637
|
+
}
|
|
2638
|
+
else -> when {
|
|
2639
|
+
item.type == "identity" && item.json.optString("presentation") == "walletSidebar" -> 68
|
|
2640
|
+
item.type == "identity" && item.json.optString("tertiary").isNotEmpty() -> 72
|
|
2641
|
+
item.type == "identity" && item.json.optString("subtitle").isNotEmpty() -> 60
|
|
2642
|
+
else -> 56
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
val modifier = if (isNetworkSelectorIdentity) {
|
|
2647
|
+
0
|
|
2648
|
+
} else if (
|
|
2649
|
+
item.type == "sectionHeader" && item.json.optString("variant") in listOf("summary", "gallery")
|
|
2650
|
+
) {
|
|
2651
|
+
0
|
|
2652
|
+
} else {
|
|
2653
|
+
when (item.json.optString("size", "medium")) { "small" -> -8; "large" -> 12; else -> 0 }
|
|
2654
|
+
}
|
|
2655
|
+
val sectionSpacing = 0
|
|
2656
|
+
val hasSecondaryColumn = item.type == "dataRow" &&
|
|
2657
|
+
(0 until item.json.getJSONArray("columns").length()).any {
|
|
2658
|
+
item.json.getJSONArray("columns").getJSONObject(it).optString("secondaryText").isNotEmpty()
|
|
2659
|
+
}
|
|
2660
|
+
val tableAdjustment = if (item.type == "dataRow" && currentLayout == "table" && !hasSecondaryColumn) -8 else 0
|
|
2661
|
+
minimumHeight = dp((baseHeight + modifier + sectionSpacing + tableAdjustment).coerceAtLeast(0))
|
|
2662
|
+
}
|
|
2663
|
+
|
|
2664
|
+
private fun addSkeleton(
|
|
2665
|
+
view: View,
|
|
2666
|
+
widthDp: Int,
|
|
2667
|
+
heightDp: Int,
|
|
2668
|
+
theme: JSONObject?,
|
|
2669
|
+
bottomMarginDp: Int = 0,
|
|
2670
|
+
) {
|
|
2671
|
+
view.background = roundedFill(color(theme, "strongBackground", "#0000000F"), 6f)
|
|
2672
|
+
mainColumn.addView(
|
|
2673
|
+
view,
|
|
2674
|
+
LayoutParams(dp(widthDp), dp(heightDp)).apply { bottomMargin = dp(bottomMarginDp) },
|
|
2675
|
+
)
|
|
2676
|
+
}
|
|
2677
|
+
|
|
2678
|
+
private fun applyListOrientation(item: NativeListItem, listOrientation: String) {
|
|
2679
|
+
val params = layoutParams ?: return
|
|
2680
|
+
if (listOrientation == "horizontal") {
|
|
2681
|
+
params.width = when (item.type) {
|
|
2682
|
+
"rail" -> railWidth()
|
|
2683
|
+
"mediaTile" -> dp(200)
|
|
2684
|
+
else -> dp(280)
|
|
2685
|
+
}
|
|
2686
|
+
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
2687
|
+
} else {
|
|
2688
|
+
params.width = ViewGroup.LayoutParams.MATCH_PARENT
|
|
2689
|
+
params.height = ViewGroup.LayoutParams.WRAP_CONTENT
|
|
2690
|
+
}
|
|
2691
|
+
layoutParams = params
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
private fun railWidth(): Int {
|
|
2695
|
+
var width = dp(4 + 20 + 6).toFloat() + title.paint.measureText(title.text.toString())
|
|
2696
|
+
if (badgeLine.visibility == VISIBLE) {
|
|
2697
|
+
width += dp(6) + badgeLine.paint.measureText(badgeLine.text.toString())
|
|
2698
|
+
}
|
|
2699
|
+
if (status.visibility == VISIBLE) {
|
|
2700
|
+
width += dp(6) + status.paint.measureText(status.text.toString())
|
|
2701
|
+
}
|
|
2702
|
+
width += dp(4)
|
|
2703
|
+
return width.roundToInt()
|
|
2704
|
+
}
|
|
2705
|
+
|
|
2706
|
+
private fun leadingCornerRadius(shape: String, sizeDp: Int): Float = when (shape) {
|
|
2707
|
+
"square" -> 0f
|
|
2708
|
+
"rounded" -> minOf(10f, sizeDp / 4f)
|
|
2709
|
+
else -> sizeDp / 2f
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
private fun leadingOutlineProvider(shape: String) = object : ViewOutlineProvider() {
|
|
2713
|
+
override fun getOutline(view: View, outline: Outline) {
|
|
2714
|
+
when (shape) {
|
|
2715
|
+
"square" -> outline.setRect(0, 0, view.width, view.height)
|
|
2716
|
+
"rounded" -> outline.setRoundRect(0, 0, view.width, view.height, dp(10).toFloat())
|
|
2717
|
+
else -> outline.setOval(0, 0, view.width, view.height)
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
private fun JSONArray?.hasAccessory(kind: String): Boolean {
|
|
2723
|
+
if (this == null) return false
|
|
2724
|
+
return (0 until length()).any { optJSONObject(it)?.optString("kind") == kind }
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
private fun JSONArray?.isBookmarkEditActions(): Boolean =
|
|
2728
|
+
this != null && length() == 2 &&
|
|
2729
|
+
optJSONObject(0)?.optString("kind") == "icon" &&
|
|
2730
|
+
optJSONObject(0)?.optString("name") == "PencilOutline" &&
|
|
2731
|
+
optJSONObject(1)?.optString("kind") == "icon" &&
|
|
2732
|
+
optJSONObject(1)?.optString("name") == "DragOutline"
|
|
2733
|
+
|
|
2734
|
+
private fun JSONArray?.isValuePairMenu(): Boolean =
|
|
2735
|
+
this != null && length() == 2 &&
|
|
2736
|
+
optJSONObject(0)?.optString("kind") == "valuePair" &&
|
|
2737
|
+
optJSONObject(1)?.optString("kind") == "menu"
|
|
2738
|
+
|
|
2739
|
+
private fun bindImage(
|
|
2740
|
+
source: JSONObject,
|
|
2741
|
+
imageView: OneKeyImageReusableView,
|
|
2742
|
+
token: String,
|
|
2743
|
+
slot: Int,
|
|
2744
|
+
variant: String,
|
|
2745
|
+
) {
|
|
2746
|
+
val uri = source.optString("uri").trim().takeIf(String::isNotEmpty)
|
|
2747
|
+
imageView.configure(
|
|
2748
|
+
sourceUri = uri,
|
|
2749
|
+
sourceHeadersJson = source.optJSONObject("headers")?.toString(),
|
|
2750
|
+
variant = variant,
|
|
2751
|
+
contentFit = source.optString("contentFit", "cover"),
|
|
2752
|
+
cachePolicy = source.optString("cachePolicy", "memory-disk"),
|
|
2753
|
+
autoplay = source.optBoolean("autoplay", false),
|
|
2754
|
+
recyclingKey = "$token:$slot",
|
|
2755
|
+
optimizeTos = source.optBoolean("optimizeTos", true),
|
|
2756
|
+
overscan = source.optDouble("overscan", 1.1),
|
|
2757
|
+
loadingStrategy = source.optString("loadingStrategy", "static"),
|
|
2758
|
+
)
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
private fun weighted() = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
|
|
2762
|
+
private fun wrap() = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
|
2763
|
+
private fun dp(value: Int): Int = NativeListScale.dp(resources, value)
|
|
2764
|
+
private fun sp(value: Float): Float = NativeListScale.font(resources, value)
|
|
2765
|
+
|
|
2766
|
+
private fun color(theme: JSONObject?, key: String, fallback: String): Int =
|
|
2767
|
+
safeColor(theme?.optString(key, fallback), parseNativeListColor(fallback))
|
|
2768
|
+
|
|
2769
|
+
private fun safeColor(value: String?, fallback: Int): Int = try {
|
|
2770
|
+
if (value.isNullOrEmpty()) fallback else parseNativeListColor(value)
|
|
2771
|
+
} catch (_: IllegalArgumentException) {
|
|
2772
|
+
fallback
|
|
2773
|
+
}
|
|
2774
|
+
|
|
2775
|
+
private fun roundedFill(color: Int, radiusDp: Float) = GradientDrawable().apply {
|
|
2776
|
+
setColor(color)
|
|
2777
|
+
cornerRadius = NativeListScale.dp(resources, radiusDp)
|
|
2778
|
+
}
|
|
2779
|
+
|
|
2780
|
+
private fun roundedStroke(stroke: Int, fill: Int, radiusDp: Float) = GradientDrawable().apply {
|
|
2781
|
+
setColor(fill)
|
|
2782
|
+
setStroke(dp(2), stroke)
|
|
2783
|
+
cornerRadius = NativeListScale.dp(resources, radiusDp)
|
|
2784
|
+
}
|
|
2785
|
+
|
|
2786
|
+
private fun roundedHairlineStroke(stroke: Int, radiusDp: Float) = GradientDrawable().apply {
|
|
2787
|
+
setColor(Color.TRANSPARENT)
|
|
2788
|
+
setStroke(1, stroke)
|
|
2789
|
+
cornerRadius = NativeListScale.dp(resources, radiusDp)
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
private fun groupedBackground(position: String, color: Int) = GradientDrawable().apply {
|
|
2793
|
+
setColor(color)
|
|
2794
|
+
val radius = NativeListScale.dp(resources, 12f)
|
|
2795
|
+
cornerRadii = when (position) {
|
|
2796
|
+
"first" -> floatArrayOf(radius, radius, radius, radius, 0f, 0f, 0f, 0f)
|
|
2797
|
+
"last" -> floatArrayOf(0f, 0f, 0f, 0f, radius, radius, radius, radius)
|
|
2798
|
+
"single" -> FloatArray(8) { radius }
|
|
2799
|
+
"rail" -> FloatArray(8) { NativeListScale.dp(resources, 8f) }
|
|
2800
|
+
"mediaTile" -> FloatArray(8) { NativeListScale.dp(resources, 16f) }
|
|
2801
|
+
else -> FloatArray(8)
|
|
2802
|
+
}
|
|
2803
|
+
}
|
|
2804
|
+
|
|
2805
|
+
}
|
|
2806
|
+
|
|
2807
|
+
private class OneKeyIconView(context: android.content.Context) : View(context) {
|
|
2808
|
+
var iconName: String = ""
|
|
2809
|
+
set(value) {
|
|
2810
|
+
field = value
|
|
2811
|
+
invalidate()
|
|
2812
|
+
}
|
|
2813
|
+
var tintColor: Int = Color.rgb(100, 100, 100)
|
|
2814
|
+
set(value) {
|
|
2815
|
+
field = value
|
|
2816
|
+
invalidate()
|
|
2817
|
+
}
|
|
2818
|
+
var glyphSizeDp: Int? = null
|
|
2819
|
+
set(value) {
|
|
2820
|
+
field = value
|
|
2821
|
+
invalidate()
|
|
2822
|
+
}
|
|
2823
|
+
|
|
2824
|
+
private val fill = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
2825
|
+
|
|
2826
|
+
override fun onDraw(canvas: Canvas) {
|
|
2827
|
+
super.onDraw(canvas)
|
|
2828
|
+
val pathData = iconPaths[iconName] ?: return
|
|
2829
|
+
val drawSize = minOf(
|
|
2830
|
+
minOf(width, height).toFloat(),
|
|
2831
|
+
glyphSizeDp?.let { NativeListScale.dp(resources, it).toFloat() } ?: Float.MAX_VALUE,
|
|
2832
|
+
)
|
|
2833
|
+
val scale = drawSize / 24f
|
|
2834
|
+
fill.color = tintColor
|
|
2835
|
+
canvas.save()
|
|
2836
|
+
canvas.translate((width - drawSize) / 2f, (height - drawSize) / 2f)
|
|
2837
|
+
canvas.scale(scale, scale)
|
|
2838
|
+
val sourceFillTypes = actionIconFillTypes[iconName]
|
|
2839
|
+
pathData.forEachIndexed { index, data ->
|
|
2840
|
+
PathParser.createPathFromPathData(data)?.let { path ->
|
|
2841
|
+
path.fillType = sourceFillTypes?.getOrNull(index) ?: Path.FillType.EVEN_ODD
|
|
2842
|
+
canvas.drawPath(path, fill)
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
canvas.restore()
|
|
2846
|
+
}
|
|
2847
|
+
|
|
2848
|
+
companion object {
|
|
2849
|
+
// Keep the SVG fill-rule used by each Action-row source path. React Native
|
|
2850
|
+
// SVG defaults to nonzero (WINDING); only paths declaring fillRule="evenodd"
|
|
2851
|
+
// use EVEN_ODD. Other existing icons retain their prior rendering behavior.
|
|
2852
|
+
private val actionIconFillTypes = mapOf(
|
|
2853
|
+
"ChevronRightSmallOutline" to listOf(Path.FillType.WINDING),
|
|
2854
|
+
"MinusCircleOutline" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
2855
|
+
"PlusCircleOutline" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
2856
|
+
"PlusSmallOutline" to listOf(Path.FillType.WINDING),
|
|
2857
|
+
"DotHorOutline" to listOf(Path.FillType.WINDING),
|
|
2858
|
+
"MinusCircleSolid" to listOf(Path.FillType.EVEN_ODD),
|
|
2859
|
+
"PencilOutline" to listOf(Path.FillType.EVEN_ODD),
|
|
2860
|
+
"DragOutline" to listOf(Path.FillType.WINDING),
|
|
2861
|
+
"StarSolid" to listOf(Path.FillType.WINDING),
|
|
2862
|
+
"ChevronGrabberVerOutline" to listOf(Path.FillType.WINDING),
|
|
2863
|
+
"ChevronBottomOutline" to listOf(Path.FillType.WINDING),
|
|
2864
|
+
"ChevronTopOutline" to listOf(Path.FillType.WINDING),
|
|
2865
|
+
"ImageSquareWavesOutline" to listOf(Path.FillType.WINDING, Path.FillType.EVEN_ODD),
|
|
2866
|
+
)
|
|
2867
|
+
|
|
2868
|
+
// Exact 24x24 paths from app-monorepo packages/components Icon sources.
|
|
2869
|
+
private val iconPaths = mapOf(
|
|
2870
|
+
"ArrowBottomOutline" to listOf("m13 17.586 5-5L19.414 14 12 21.414 4.586 14 6 12.586l5 5V3h2z"),
|
|
2871
|
+
"ArrowTopOutline" to listOf("M19.414 10 18 11.414l-5-5V21h-2V6.414l-5 5L4.586 10 12 2.586z"),
|
|
2872
|
+
"ChartTrendingUpOutline" to listOf("M22 13h-2V9.414l-7 7-4-4-6 6L1.586 17 9 9.586l4 4L18.586 8H15V6h7z"),
|
|
2873
|
+
"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"),
|
|
2874
|
+
"ShieldExclamationOutline" to listOf(
|
|
2875
|
+
"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",
|
|
2876
|
+
"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",
|
|
2877
|
+
),
|
|
2878
|
+
"InfoCircleOutline" to listOf(
|
|
2879
|
+
"M13 17h-2v-5h-1v-2h3zm0-8h-2V7h2z",
|
|
2880
|
+
"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",
|
|
2881
|
+
),
|
|
2882
|
+
"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"),
|
|
2883
|
+
"PlusCircleOutline" to listOf(
|
|
2884
|
+
"M13 11h4v2h-4v4h-2v-4l-4 .001v-2L11 11V7h2z",
|
|
2885
|
+
"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",
|
|
2886
|
+
),
|
|
2887
|
+
"PlusSmallOutline" to listOf("M13 11h5v2h-5v5h-2v-5H6v-2h5V6h2z"),
|
|
2888
|
+
"DotHorOutline" to listOf("M6 14H2v-4h4zm8 0h-4v-4h4zm8 0h-4v-4h4z"),
|
|
2889
|
+
"MinusCircleOutline" to listOf(
|
|
2890
|
+
"M17 13H7v-2h10z",
|
|
2891
|
+
"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",
|
|
2892
|
+
),
|
|
2893
|
+
"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"),
|
|
2894
|
+
"ErrorSolid" to listOf("M23.256 20H.742L12 1.041zM11 15v2h2v-2zm0-6v5h2V9z"),
|
|
2895
|
+
"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"),
|
|
2896
|
+
"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"),
|
|
2897
|
+
"ChevronRightSmallOutline" to listOf("M15.414 12 10 17.414 8.586 16l4-4-4-4L10 6.586z"),
|
|
2898
|
+
"DragOutline" to listOf("M11 21H7v-4h4zm6 0h-4v-4h4zm-6-7H7v-4h4zm6 0h-4v-4h4zm-6-7H7V3h4zm6 0h-4V3h4z"),
|
|
2899
|
+
"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"),
|
|
2900
|
+
"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"),
|
|
2901
|
+
"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"),
|
|
2902
|
+
"ChevronBottomOutline" to listOf("M20.707 9.707 12 18.414 3.293 9.707l1.414-1.414L12 15.586l7.293-7.293z"),
|
|
2903
|
+
"ChevronTopOutline" to listOf("m20.707 14.293-1.414 1.414L12 8.414l-7.293 7.293-1.414-1.414L12 5.586z"),
|
|
2904
|
+
"ImageSquareWavesOutline" to listOf(
|
|
2905
|
+
"M14.25 7a2 2 0 1 1 0 4 2 2 0 0 1 0-4",
|
|
2906
|
+
"M21 21H3V3h18zM5 16.414V19h12.586L14 15.414l-2 2-4-4zm0-2.828 3-3 4 4 2-2 5 5V5H5z",
|
|
2907
|
+
),
|
|
2908
|
+
)
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
private class OneKeyCheckboxView(context: android.content.Context) : View(context) {
|
|
2913
|
+
private val glyphPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL }
|
|
2914
|
+
private var state = "unchecked"
|
|
2915
|
+
|
|
2916
|
+
fun setState(value: String, color: Int) {
|
|
2917
|
+
state = value
|
|
2918
|
+
glyphPaint.color = color
|
|
2919
|
+
invalidate()
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
override fun onDraw(canvas: Canvas) {
|
|
2923
|
+
super.onDraw(canvas)
|
|
2924
|
+
val pathData = when (state) {
|
|
2925
|
+
"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"
|
|
2926
|
+
"indeterminate" -> "M4 8a1 1 0 0 1 1-1h6a1 1 0 0 1 0 2H5a1 1 0 0 1-1-1"
|
|
2927
|
+
else -> return
|
|
2928
|
+
}
|
|
2929
|
+
val drawSize = minOf(width, height) * 0.8f
|
|
2930
|
+
canvas.save()
|
|
2931
|
+
canvas.translate((width - drawSize) / 2f, (height - drawSize) / 2f)
|
|
2932
|
+
canvas.scale(drawSize / 16f, drawSize / 16f)
|
|
2933
|
+
PathParser.createPathFromPathData(pathData)?.let { canvas.drawPath(it, glyphPaint) }
|
|
2934
|
+
canvas.restore()
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
internal class NativeListViewHolder(val rowView: NativeListRowView) :
|
|
2939
|
+
androidx.recyclerview.widget.RecyclerView.ViewHolder(rowView)
|