@onekeyfe/react-native-native-list 3.0.105 → 3.0.106
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/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +16 -3
- package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +6 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +671 -52
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +282 -54
- package/ios/HybridNativeList.swift +8 -2
- package/ios/NativeListCell.swift +630 -31
- package/ios/NativeListDesignAssets.swift +24 -1
- package/ios/RNCNativeListView.swift +216 -42
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_account_error_custom.imageset/icon.svg +14 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_all_networks_solid.imageset/icon.svg +12 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_apple_brand.imageset/icon.svg +6 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_bot_illus.imageset/icon.svg +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_crossed_small_solid.imageset/icon.svg +3 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_globus_outline.imageset/icon.svg +7 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_google_illus.imageset/icon.svg +18 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/Contents.json +15 -0
- package/ios/Resources/NativeListIcons.xcassets/onekey_selector_lock_solid.imageset/icon.svg +7 -0
- package/lib/module/NativeList.js +102 -8
- package/lib/module/NativeList.web.js +20 -2
- package/lib/module/avatarPrefetch.js +174 -0
- package/lib/module/validation.js +45 -2
- package/lib/module/web/NativeListAvatarWorker.js +542 -0
- package/lib/module/web/NativeListWebAvatarCache.js +267 -0
- package/lib/module/web/NativeListWebEngine.js +958 -47
- package/lib/typescript/src/NativeList.web.d.ts +2 -2
- package/lib/typescript/src/avatarPrefetch.d.ts +28 -0
- package/lib/typescript/src/models.d.ts +72 -4
- package/lib/typescript/src/web/NativeListWebAvatarCache.d.ts +6 -0
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +10 -1
- package/package.json +2 -2
- package/src/NativeList.tsx +149 -13
- package/src/NativeList.web.tsx +30 -3
- package/src/avatarPrefetch.ts +236 -0
- package/src/models.ts +98 -7
- package/src/validation.ts +131 -2
- package/src/web/NativeListAvatarWorker.js +567 -0
- package/src/web/NativeListWebAvatarCache.ts +314 -0
- package/src/web/NativeListWebEngine.ts +1409 -60
|
@@ -14,6 +14,7 @@ import android.view.Choreographer
|
|
|
14
14
|
import android.view.Gravity
|
|
15
15
|
import android.view.MotionEvent
|
|
16
16
|
import android.view.View
|
|
17
|
+
import android.view.ViewConfiguration
|
|
17
18
|
import android.view.accessibility.AccessibilityEvent
|
|
18
19
|
import android.view.accessibility.AccessibilityNodeInfo
|
|
19
20
|
import android.widget.FrameLayout
|
|
@@ -31,6 +32,7 @@ import org.json.JSONArray
|
|
|
31
32
|
import org.json.JSONObject
|
|
32
33
|
import java.lang.ref.WeakReference
|
|
33
34
|
import java.util.UUID
|
|
35
|
+
import kotlin.math.abs
|
|
34
36
|
import kotlin.math.ceil
|
|
35
37
|
import kotlin.math.cos
|
|
36
38
|
import kotlin.math.exp
|
|
@@ -87,6 +89,36 @@ class NativeListView(
|
|
|
87
89
|
private val contentContainer = FrameLayout(context)
|
|
88
90
|
private val adapter = NativeListAdapter(reactContext)
|
|
89
91
|
private val layoutManager = GridLayoutManager(context, 1)
|
|
92
|
+
// OneKey patch: vertical lists retain vertical drags and leave horizontal drags to a parent pager.
|
|
93
|
+
private val pagerGestureTouchSlop = ViewConfiguration.get(context).scaledTouchSlop
|
|
94
|
+
private val pagerGestureTouchListener = object : RecyclerView.SimpleOnItemTouchListener() {
|
|
95
|
+
private var downX = 0f
|
|
96
|
+
private var downY = 0f
|
|
97
|
+
private var directionResolved = false
|
|
98
|
+
|
|
99
|
+
override fun onInterceptTouchEvent(recyclerView: RecyclerView, event: MotionEvent): Boolean {
|
|
100
|
+
if (layoutManager.orientation != RecyclerView.VERTICAL) return false
|
|
101
|
+
when (event.actionMasked) {
|
|
102
|
+
MotionEvent.ACTION_DOWN -> {
|
|
103
|
+
downX = event.x
|
|
104
|
+
downY = event.y
|
|
105
|
+
directionResolved = false
|
|
106
|
+
}
|
|
107
|
+
MotionEvent.ACTION_MOVE -> if (!directionResolved) {
|
|
108
|
+
val deltaX = abs(event.x - downX)
|
|
109
|
+
val deltaY = abs(event.y - downY)
|
|
110
|
+
if (max(deltaX, deltaY) > pagerGestureTouchSlop) {
|
|
111
|
+
directionResolved = true
|
|
112
|
+
recyclerView.parent?.requestDisallowInterceptTouchEvent(deltaY >= deltaX)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
116
|
+
recyclerView.parent?.requestDisallowInterceptTouchEvent(false)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
}
|
|
90
122
|
private val reorderPlaceholderDecoration = ReorderPlaceholderDecoration(
|
|
91
123
|
adapter = adapter,
|
|
92
124
|
insetPx = dp(REORDER_PLACEHOLDER_INSET_DP),
|
|
@@ -102,6 +134,7 @@ class NativeListView(
|
|
|
102
134
|
private val sectionIndexView = NativeListSectionIndexView(context)
|
|
103
135
|
private val sectionIndexPreview = TextView(context)
|
|
104
136
|
private var config: NativeListConfig? = null
|
|
137
|
+
private var usesSelectorSourceScale = false
|
|
105
138
|
private var stickyDecoration: StickySectionHeaderDecoration? = null
|
|
106
139
|
private var spacingDecoration: ItemSpacingDecoration? = null
|
|
107
140
|
private var itemTouchHelper: ItemTouchHelper? = null
|
|
@@ -118,7 +151,6 @@ class NativeListView(
|
|
|
118
151
|
private var sectionIndexProgrammaticScroll = false
|
|
119
152
|
private var sectionIndexHapticsEnabled = true
|
|
120
153
|
private var pendingScrollRequest: ScrollRequest? = null
|
|
121
|
-
private var contentScrollOffsetPx = 0
|
|
122
154
|
private val actionAnchorInstanceId = UUID.randomUUID().toString()
|
|
123
155
|
private var actionAnchorCounter = 0L
|
|
124
156
|
private var actionAnchor: ActionAnchorRecord? = null
|
|
@@ -132,7 +164,10 @@ class NativeListView(
|
|
|
132
164
|
recyclerView.adapter = adapter
|
|
133
165
|
recyclerView.layoutManager = layoutManager
|
|
134
166
|
recyclerView.itemAnimator = null
|
|
167
|
+
recyclerView.addOnItemTouchListener(pagerGestureTouchListener)
|
|
135
168
|
recyclerView.addItemDecoration(reorderPlaceholderDecoration)
|
|
169
|
+
// OneKey patch: full-width selector header backgrounds do not change row content insets.
|
|
170
|
+
recyclerView.addItemDecoration(SelectorBackgroundDecoration(adapter))
|
|
136
171
|
recyclerView.setHasFixedSize(false)
|
|
137
172
|
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
|
|
138
173
|
override fun getSpanSize(position: Int): Int {
|
|
@@ -158,11 +193,15 @@ class NativeListView(
|
|
|
158
193
|
)
|
|
159
194
|
contentContainer.addView(
|
|
160
195
|
sectionIndexView,
|
|
161
|
-
FrameLayout.LayoutParams(
|
|
196
|
+
FrameLayout.LayoutParams(
|
|
197
|
+
dp(SECTION_INDEX_RAIL_WIDTH_DP),
|
|
198
|
+
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
199
|
+
Gravity.END,
|
|
200
|
+
),
|
|
162
201
|
)
|
|
163
202
|
sectionIndexPreview.apply {
|
|
164
203
|
gravity = Gravity.CENTER
|
|
165
|
-
textSize = NativeListScale.font(resources,
|
|
204
|
+
textSize = NativeListScale.font(resources, 22f)
|
|
166
205
|
typeface = NativeListFonts.semibold(context)
|
|
167
206
|
visibility = GONE
|
|
168
207
|
alpha = 0f
|
|
@@ -170,7 +209,13 @@ class NativeListView(
|
|
|
170
209
|
}
|
|
171
210
|
contentContainer.addView(
|
|
172
211
|
sectionIndexPreview,
|
|
173
|
-
FrameLayout.LayoutParams(
|
|
212
|
+
FrameLayout.LayoutParams(
|
|
213
|
+
dp(SECTION_INDEX_PREVIEW_SIZE_DP),
|
|
214
|
+
dp(SECTION_INDEX_PREVIEW_SIZE_DP),
|
|
215
|
+
Gravity.CENTER_VERTICAL or Gravity.END,
|
|
216
|
+
).apply {
|
|
217
|
+
marginEnd = dp(SECTION_INDEX_PREVIEW_END_MARGIN_DP)
|
|
218
|
+
},
|
|
174
219
|
)
|
|
175
220
|
addView(contentContainer, LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f))
|
|
176
221
|
footerView.visibility = GONE
|
|
@@ -208,10 +253,6 @@ class NativeListView(
|
|
|
208
253
|
|
|
209
254
|
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
210
255
|
if (dx != 0 || dy != 0) invalidateActionAnchor("scroll")
|
|
211
|
-
contentScrollOffsetPx = (
|
|
212
|
-
contentScrollOffsetPx +
|
|
213
|
-
if (layoutManager.orientation == RecyclerView.VERTICAL) dy else dx
|
|
214
|
-
).coerceAtLeast(0)
|
|
215
256
|
syncSectionIndexToVisibleRows()
|
|
216
257
|
scheduleVisibleEvent()
|
|
217
258
|
checkEndReached()
|
|
@@ -244,17 +285,29 @@ class NativeListView(
|
|
|
244
285
|
invalidateActionAnchor("snapshot")
|
|
245
286
|
val previous = config
|
|
246
287
|
if (previous != null && canApplyStableContentUpdate(previous, next)) {
|
|
288
|
+
// OneKey patch: authorize a diff payload only against this exact validated baseline.
|
|
289
|
+
next.items.forEachIndexed { index, item ->
|
|
290
|
+
if (item.content != previous.items[index].content &&
|
|
291
|
+
adapter.currentList.getOrNull(index) === previous.items[index]
|
|
292
|
+
) {
|
|
293
|
+
item.selectionUpdateFromContent = previous.items[index].content
|
|
294
|
+
}
|
|
295
|
+
}
|
|
247
296
|
val changedSummaryKeys = previous.items.indices.mapNotNull { index ->
|
|
248
297
|
previous.items[index].key.takeIf {
|
|
249
298
|
previous.items[index].content != next.items[index].content
|
|
250
299
|
}
|
|
251
300
|
}.toSet()
|
|
252
301
|
config = next
|
|
302
|
+
usesSelectorSourceScale = next.items.any { it.usesSelectorSourceScale }
|
|
303
|
+
adapter.usesSelectorSourceScale = usesSelectorSourceScale
|
|
253
304
|
adapter.theme = next.theme
|
|
254
305
|
adapter.layout = next.layout
|
|
255
306
|
adapter.orientation = next.orientation
|
|
256
307
|
adapter.selectedKeys = next.selectedKeys
|
|
257
308
|
adapter.submitList(next.items) {
|
|
309
|
+
// OneKey patch: DiffUtil has dispatched its payloads; release the old serialized rows.
|
|
310
|
+
next.items.forEach { it.selectionUpdateFromContent = null }
|
|
258
311
|
recyclerView.post {
|
|
259
312
|
bindVisibleSelection(changedSummaryKeys)
|
|
260
313
|
bindFooterSelection()
|
|
@@ -264,7 +317,9 @@ class NativeListView(
|
|
|
264
317
|
return
|
|
265
318
|
}
|
|
266
319
|
config = next
|
|
267
|
-
|
|
320
|
+
usesSelectorSourceScale = next.items.any { it.usesSelectorSourceScale }
|
|
321
|
+
adapter.usesSelectorSourceScale = usesSelectorSourceScale
|
|
322
|
+
if (previous?.generation != next.generation) endReachedGeneration = null
|
|
268
323
|
pendingReorder = null
|
|
269
324
|
adapter.theme = next.theme
|
|
270
325
|
adapter.layout = next.layout
|
|
@@ -327,30 +382,98 @@ class NativeListView(
|
|
|
327
382
|
val newItem = next.items[index]
|
|
328
383
|
oldItem.key == newItem.key &&
|
|
329
384
|
oldItem.type == newItem.type &&
|
|
330
|
-
|
|
385
|
+
// OneKey patch: controlled echoes can also carry row and checkbox selection state.
|
|
386
|
+
// (oldItem.content == newItem.content || isStableSummaryUpdate(oldItem, newItem))
|
|
387
|
+
(oldItem.content == newItem.content || isStableSelectionUpdate(
|
|
388
|
+
oldItem,
|
|
389
|
+
newItem,
|
|
390
|
+
next.selectionMode == "single" || next.selectionMode == "multiple",
|
|
391
|
+
))
|
|
331
392
|
}
|
|
332
393
|
}
|
|
333
394
|
|
|
334
|
-
|
|
395
|
+
// OneKey patch: preserve the original summary-only comparison for upstream reference.
|
|
396
|
+
// private fun isStableSummaryUpdate(
|
|
397
|
+
// previous: NativeListItem,
|
|
398
|
+
// next: NativeListItem,
|
|
399
|
+
// ): Boolean {
|
|
400
|
+
// if (
|
|
401
|
+
// previous.type != "sectionHeader" ||
|
|
402
|
+
// previous.json.optString("variant") != "summary" ||
|
|
403
|
+
// next.json.optString("variant") != "summary"
|
|
404
|
+
// ) {
|
|
405
|
+
// return false
|
|
406
|
+
// }
|
|
407
|
+
// val previousStructure = JSONObject(previous.content).apply {
|
|
408
|
+
// remove("title")
|
|
409
|
+
// remove("value")
|
|
410
|
+
// }
|
|
411
|
+
// val nextStructure = JSONObject(next.content).apply {
|
|
412
|
+
// remove("title")
|
|
413
|
+
// remove("value")
|
|
414
|
+
// }
|
|
415
|
+
// return previousStructure.toString() == nextStructure.toString()
|
|
416
|
+
// }
|
|
417
|
+
|
|
418
|
+
private fun isStableSelectionUpdate(
|
|
335
419
|
previous: NativeListItem,
|
|
336
420
|
next: NativeListItem,
|
|
421
|
+
controlled: Boolean,
|
|
337
422
|
): Boolean {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
423
|
+
val previousStructure = selectionComparisonData(previous.json, controlled) ?: return false
|
|
424
|
+
val nextStructure = selectionComparisonData(next.json, controlled) ?: return false
|
|
425
|
+
return previousStructure.toString() == nextStructure.toString()
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// OneKey patch: ignore only fields refreshed by the lightweight selection binder.
|
|
429
|
+
private fun selectionComparisonData(data: JSONObject, controlled: Boolean): JSONObject? {
|
|
430
|
+
val type = data.opt("type") as? String ?: return null
|
|
431
|
+
val result = JSONObject(data.toString())
|
|
432
|
+
if (result.has("selected")) {
|
|
433
|
+
if (result.opt("selected") !is Boolean) return null
|
|
434
|
+
result.remove("selected")
|
|
344
435
|
}
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
436
|
+
if (type == "walletGroup") {
|
|
437
|
+
val parent = data.optJSONObject("parent") ?: return null
|
|
438
|
+
if (parent.optString("type") != "identity") return null
|
|
439
|
+
val children = data.optJSONArray("children") ?: return null
|
|
440
|
+
result.put("parent", selectionComparisonData(parent, false) ?: return null)
|
|
441
|
+
val normalizedChildren = JSONArray()
|
|
442
|
+
for (index in 0 until children.length()) {
|
|
443
|
+
val child = children.optJSONObject(index) ?: return null
|
|
444
|
+
if (child.optString("type") != "identity") return null
|
|
445
|
+
normalizedChildren.put(selectionComparisonData(child, false) ?: return null)
|
|
446
|
+
}
|
|
447
|
+
result.put("children", normalizedChildren)
|
|
348
448
|
}
|
|
349
|
-
|
|
350
|
-
remove("title")
|
|
351
|
-
remove("value")
|
|
449
|
+
if (type == "sectionHeader" && data.optString("variant") == "summary") {
|
|
450
|
+
result.remove("title")
|
|
451
|
+
result.remove("value")
|
|
352
452
|
}
|
|
353
|
-
|
|
453
|
+
if (!controlled) return result
|
|
454
|
+
fun checkboxData(value: Any?): JSONObject? {
|
|
455
|
+
val checkbox = value as? JSONObject ?: return null
|
|
456
|
+
if (checkbox.optString("kind") != "checkbox") return null
|
|
457
|
+
if (checkbox.has("state") && checkbox.opt("state") !in setOf("checked", "unchecked", "indeterminate")) return null
|
|
458
|
+
checkbox.remove("state")
|
|
459
|
+
return checkbox
|
|
460
|
+
}
|
|
461
|
+
if (type in setOf("dataRow", "sectionHeader", "action") && result.has("checkbox")) {
|
|
462
|
+
result.put("checkbox", checkboxData(result.opt("checkbox")) ?: return null)
|
|
463
|
+
}
|
|
464
|
+
if (type == "identity" && result.has("trailing")) {
|
|
465
|
+
val accessories = result.optJSONArray("trailing") ?: return null
|
|
466
|
+
if (accessories.length() > 2) return null
|
|
467
|
+
var checkboxCount = 0
|
|
468
|
+
for (index in 0 until accessories.length()) {
|
|
469
|
+
val accessory = accessories.optJSONObject(index) ?: return null
|
|
470
|
+
if (accessory.optString("kind") == "checkbox") {
|
|
471
|
+
if (++checkboxCount > 1) return null
|
|
472
|
+
accessories.put(index, checkboxData(accessory) ?: return null)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return result
|
|
354
477
|
}
|
|
355
478
|
|
|
356
479
|
fun applyPatches(patchesJson: String) {
|
|
@@ -389,6 +512,8 @@ class NativeListView(
|
|
|
389
512
|
invalidateActionAnchor("snapshot")
|
|
390
513
|
val next = current.copy(items = nextItems, selectedKeys = selected)
|
|
391
514
|
config = next
|
|
515
|
+
usesSelectorSourceScale = next.items.any { it.usesSelectorSourceScale }
|
|
516
|
+
adapter.usesSelectorSourceScale = usesSelectorSourceScale
|
|
392
517
|
adapter.selectedKeys = selected
|
|
393
518
|
adapter.submitList(nextItems) { relayoutContents() }
|
|
394
519
|
bindFooter(next)
|
|
@@ -574,16 +699,26 @@ class NativeListView(
|
|
|
574
699
|
private fun performOffsetScroll(offset: Double, animated: Boolean) {
|
|
575
700
|
val manager = recyclerView.layoutManager as? LinearLayoutManager ?: return
|
|
576
701
|
val target = (offset * density).roundToInt().coerceAtLeast(0)
|
|
702
|
+
if (!animated) {
|
|
703
|
+
recyclerView.stopScroll()
|
|
704
|
+
manager.scrollToPositionWithOffset(0, -target)
|
|
705
|
+
relayoutRecyclerView()
|
|
706
|
+
return
|
|
707
|
+
}
|
|
577
708
|
if (target == 0) {
|
|
578
|
-
|
|
579
|
-
else manager.scrollToPositionWithOffset(0, 0)
|
|
709
|
+
recyclerView.smoothScrollToPosition(0)
|
|
580
710
|
return
|
|
581
711
|
}
|
|
582
|
-
val
|
|
712
|
+
val currentOffset = if (manager.orientation == RecyclerView.VERTICAL) {
|
|
713
|
+
recyclerView.computeVerticalScrollOffset()
|
|
714
|
+
} else {
|
|
715
|
+
recyclerView.computeHorizontalScrollOffset()
|
|
716
|
+
}
|
|
717
|
+
val delta = target - currentOffset
|
|
583
718
|
if (manager.orientation == RecyclerView.VERTICAL) {
|
|
584
|
-
|
|
719
|
+
recyclerView.smoothScrollBy(0, delta)
|
|
585
720
|
} else {
|
|
586
|
-
|
|
721
|
+
recyclerView.smoothScrollBy(delta, 0)
|
|
587
722
|
}
|
|
588
723
|
}
|
|
589
724
|
|
|
@@ -714,6 +849,7 @@ class NativeListView(
|
|
|
714
849
|
reorderTouchHandler = null
|
|
715
850
|
reorderTouchListener?.let(recyclerView::removeOnItemTouchListener)
|
|
716
851
|
reorderTouchListener = null
|
|
852
|
+
recyclerView.removeOnItemTouchListener(pagerGestureTouchListener)
|
|
717
853
|
itemTouchHelper?.attachToRecyclerView(null)
|
|
718
854
|
itemTouchHelper = null
|
|
719
855
|
footerView.recycle()
|
|
@@ -731,7 +867,8 @@ class NativeListView(
|
|
|
731
867
|
val horizontalPadding = next.contentPaddingHorizontal ?: defaultPadding
|
|
732
868
|
val topPadding = next.contentPaddingTop ?: defaultPadding
|
|
733
869
|
val bottomPadding = next.contentPaddingBottom ?: defaultPadding
|
|
734
|
-
|
|
870
|
+
// OneKey patch: the section index overlays rows and keeps only an accessory-safe inset.
|
|
871
|
+
val indexGutter = if (sectionIndexEntries.isEmpty()) 0 else SECTION_INDEX_CONTENT_INSET_DP
|
|
735
872
|
recyclerView.setPaddingRelative(
|
|
736
873
|
dp(horizontalPadding),
|
|
737
874
|
dp(topPadding),
|
|
@@ -742,7 +879,7 @@ class NativeListView(
|
|
|
742
879
|
recyclerView.isVerticalScrollBarEnabled = sectionIndexEntries.isEmpty()
|
|
743
880
|
|
|
744
881
|
spacingDecoration?.let(recyclerView::removeItemDecoration)
|
|
745
|
-
spacingDecoration = ItemSpacingDecoration(dp(next.itemSpacing)).also(recyclerView::addItemDecoration)
|
|
882
|
+
spacingDecoration = ItemSpacingDecoration(dp(next.itemSpacing), next.itemSpacing, density).also(recyclerView::addItemDecoration)
|
|
746
883
|
stickyDecoration?.let(recyclerView::removeItemDecoration)
|
|
747
884
|
stickyDecoration = if (next.stickyHeaders && orientation == RecyclerView.VERTICAL) {
|
|
748
885
|
StickySectionHeaderDecoration(adapter, context, next.theme, density).also(recyclerView::addItemDecoration)
|
|
@@ -769,12 +906,13 @@ class NativeListView(
|
|
|
769
906
|
sectionIndexEntries.map { it.title },
|
|
770
907
|
themeColor(next.theme, "secondaryText", "#646464"),
|
|
771
908
|
themeColor(next.theme, "accent", "#108303"),
|
|
909
|
+
themeColor(next.theme, "inverseText", "#FCFCFC"),
|
|
772
910
|
)
|
|
773
911
|
sectionIndexView.visibility = if (sectionIndexEntries.isEmpty()) GONE else VISIBLE
|
|
774
912
|
sectionIndexPreview.setTextColor(themeColor(next.theme, "inverseText", "#FCFCFC"))
|
|
775
913
|
sectionIndexPreview.background = GradientDrawable().apply {
|
|
776
914
|
setColor(themeColor(next.theme, "inverseBackground", "#202020"))
|
|
777
|
-
cornerRadius = dp(
|
|
915
|
+
cornerRadius = dp(14).toFloat()
|
|
778
916
|
}
|
|
779
917
|
sectionIndexView.setActiveIndex(
|
|
780
918
|
previousKey?.let { key -> sectionIndexEntries.indexOfFirst { it.key == key }.takeIf { it >= 0 } },
|
|
@@ -789,6 +927,10 @@ class NativeListView(
|
|
|
789
927
|
sectionIndexView.setActiveIndex(index)
|
|
790
928
|
recyclerView.stopScroll()
|
|
791
929
|
scrollToIndex(entry.position, animated = false, alignment = "start")
|
|
930
|
+
recyclerView.post {
|
|
931
|
+
sectionIndexProgrammaticScroll = false
|
|
932
|
+
syncSectionIndexToVisibleRows()
|
|
933
|
+
}
|
|
792
934
|
if (interacting) {
|
|
793
935
|
sectionIndexPreview.animate().cancel()
|
|
794
936
|
sectionIndexPreview.text = entry.title
|
|
@@ -803,6 +945,7 @@ class NativeListView(
|
|
|
803
945
|
private fun finishSectionIndexInteraction(immediately: Boolean = false) {
|
|
804
946
|
sectionIndexScrubbing = false
|
|
805
947
|
if (immediately) sectionIndexProgrammaticScroll = false
|
|
948
|
+
syncSectionIndexToVisibleRows()
|
|
806
949
|
sectionIndexPreview.animate().cancel()
|
|
807
950
|
if (immediately) {
|
|
808
951
|
sectionIndexPreview.alpha = 0f
|
|
@@ -846,6 +989,7 @@ class NativeListView(
|
|
|
846
989
|
null,
|
|
847
990
|
next.selectedKeys.contains(footer.key),
|
|
848
991
|
::resolveCheckboxState,
|
|
992
|
+
useSourceScale = usesSelectorSourceScale,
|
|
849
993
|
)
|
|
850
994
|
}
|
|
851
995
|
}
|
|
@@ -898,15 +1042,17 @@ class NativeListView(
|
|
|
898
1042
|
origin.sourceView.getLocationInWindow(location)
|
|
899
1043
|
val record = ActionAnchorRecord(token, origin)
|
|
900
1044
|
actionAnchor = record
|
|
1045
|
+
// OneKey patch: selector menus anchor to the glyph slot, not its expanded press target.
|
|
1046
|
+
val anchorInset = origin.anchorInsetPixels
|
|
901
1047
|
return JSONObject()
|
|
902
1048
|
.put("token", token)
|
|
903
1049
|
.put(
|
|
904
1050
|
"windowRect",
|
|
905
1051
|
JSONObject()
|
|
906
|
-
.put("x", location[0] / density)
|
|
907
|
-
.put("y", location[1] / density)
|
|
908
|
-
.put("width", origin.sourceView.width / density)
|
|
909
|
-
.put("height", origin.sourceView.height / density),
|
|
1052
|
+
.put("x", (location[0] + anchorInset) / density)
|
|
1053
|
+
.put("y", (location[1] + anchorInset) / density)
|
|
1054
|
+
.put("width", (origin.sourceView.width - anchorInset * 2) / density)
|
|
1055
|
+
.put("height", (origin.sourceView.height - anchorInset * 2) / density),
|
|
910
1056
|
)
|
|
911
1057
|
.put("source", origin.source)
|
|
912
1058
|
.put("generation", generation)
|
|
@@ -1037,7 +1183,9 @@ class NativeListView(
|
|
|
1037
1183
|
current.theme,
|
|
1038
1184
|
current.layout,
|
|
1039
1185
|
position,
|
|
1040
|
-
|
|
1186
|
+
// OneKey patch: match the full binder when the snapshot carries explicit row selection.
|
|
1187
|
+
// current.selectedKeys.contains(item.key),
|
|
1188
|
+
item.json.optBoolean("selected", false) || current.selectedKeys.contains(item.key),
|
|
1041
1189
|
::resolveCheckboxState,
|
|
1042
1190
|
)
|
|
1043
1191
|
}
|
|
@@ -1297,12 +1445,14 @@ class NativeListView(
|
|
|
1297
1445
|
?.let(recyclerView::getChildViewHolder)
|
|
1298
1446
|
?.takeIf { holder ->
|
|
1299
1447
|
adapter.itemAt(holder.bindingAdapterPosition)?.let { item ->
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
)
|
|
1448
|
+
// OneKey patch: any wallet group member can initiate the group drag.
|
|
1449
|
+
// val holderLocation = IntArray(2)
|
|
1450
|
+
// holder.itemView.getLocationOnScreen(holderLocation)
|
|
1451
|
+
// item.isReorderable && (
|
|
1452
|
+
// item.type != "walletGroup" ||
|
|
1453
|
+
// event.rawY in holderLocation[1].toFloat()..(holderLocation[1] + dp(68)).toFloat()
|
|
1454
|
+
// )
|
|
1455
|
+
item.isReorderable
|
|
1306
1456
|
} == true
|
|
1307
1457
|
}
|
|
1308
1458
|
if (candidate != null) handler.postDelayed(startDrag, REORDER_LONG_PRESS_MS)
|
|
@@ -1460,9 +1610,13 @@ class NativeListView(
|
|
|
1460
1610
|
)
|
|
1461
1611
|
}
|
|
1462
1612
|
|
|
1463
|
-
private fun dp(value: Int): Int = NativeListScale.dp(resources, value)
|
|
1613
|
+
private fun dp(value: Int): Int = if (usesSelectorSourceScale) (value * resources.displayMetrics.density).roundToInt() else NativeListScale.dp(resources, value)
|
|
1464
1614
|
|
|
1465
1615
|
companion object {
|
|
1616
|
+
private const val SECTION_INDEX_CONTENT_INSET_DP = 16
|
|
1617
|
+
private const val SECTION_INDEX_RAIL_WIDTH_DP = 32
|
|
1618
|
+
private const val SECTION_INDEX_PREVIEW_SIZE_DP = 48
|
|
1619
|
+
private const val SECTION_INDEX_PREVIEW_END_MARGIN_DP = 40
|
|
1466
1620
|
private const val REORDER_LONG_PRESS_MS = 200L
|
|
1467
1621
|
private const val REORDER_ALLOWABLE_MOVEMENT_DP = 10
|
|
1468
1622
|
private const val REORDER_PLACEHOLDER_INSET_DP = 8
|
|
@@ -1546,7 +1700,9 @@ private class NativeListSectionIndexView(
|
|
|
1546
1700
|
private var titles: List<String> = emptyList()
|
|
1547
1701
|
private var normalColor = Color.GRAY
|
|
1548
1702
|
private var activeColor = Color.BLACK
|
|
1703
|
+
private var activeTextColor = Color.WHITE
|
|
1549
1704
|
private var lastTouchIndex: Int? = null
|
|
1705
|
+
private val activeBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
1550
1706
|
private val normalPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
1551
1707
|
textAlign = Paint.Align.CENTER
|
|
1552
1708
|
typeface = NativeListFonts.medium(context)
|
|
@@ -1563,10 +1719,16 @@ private class NativeListSectionIndexView(
|
|
|
1563
1719
|
contentDescription = ACCESSIBILITY_LABEL
|
|
1564
1720
|
}
|
|
1565
1721
|
|
|
1566
|
-
fun configure(
|
|
1722
|
+
fun configure(
|
|
1723
|
+
titles: List<String>,
|
|
1724
|
+
normalColor: Int,
|
|
1725
|
+
activeColor: Int,
|
|
1726
|
+
activeTextColor: Int,
|
|
1727
|
+
) {
|
|
1567
1728
|
this.titles = titles
|
|
1568
1729
|
this.normalColor = normalColor
|
|
1569
1730
|
this.activeColor = activeColor
|
|
1731
|
+
this.activeTextColor = activeTextColor
|
|
1570
1732
|
activeIndex = null
|
|
1571
1733
|
updateContentDescription()
|
|
1572
1734
|
invalidate()
|
|
@@ -1590,8 +1752,30 @@ private class NativeListSectionIndexView(
|
|
|
1590
1752
|
activePaint.color = activeColor
|
|
1591
1753
|
activePaint.textSize = textSize
|
|
1592
1754
|
titles.forEachIndexed { index, title ->
|
|
1593
|
-
val
|
|
1755
|
+
val active = index == activeIndex
|
|
1756
|
+
val paint = if (active) activePaint else normalPaint
|
|
1594
1757
|
val centerY = originY + cellHeight * (index + 0.5f)
|
|
1758
|
+
if (active && cellHeight >= NativeListScale.dp(resources, 12f)) {
|
|
1759
|
+
val badgeWidth = NativeListScale.dp(resources, 20f)
|
|
1760
|
+
val badgeHeight = minOf(cellHeight, NativeListScale.dp(resources, 16f))
|
|
1761
|
+
activeBackgroundPaint.color = activeColor
|
|
1762
|
+
canvas.drawRoundRect(
|
|
1763
|
+
width / 2f - badgeWidth / 2f,
|
|
1764
|
+
centerY - badgeHeight / 2f,
|
|
1765
|
+
width / 2f + badgeWidth / 2f,
|
|
1766
|
+
centerY + badgeHeight / 2f,
|
|
1767
|
+
badgeHeight / 2f,
|
|
1768
|
+
badgeHeight / 2f,
|
|
1769
|
+
activeBackgroundPaint,
|
|
1770
|
+
)
|
|
1771
|
+
}
|
|
1772
|
+
paint.color = if (active && cellHeight >= NativeListScale.dp(resources, 12f)) {
|
|
1773
|
+
activeTextColor
|
|
1774
|
+
} else if (active) {
|
|
1775
|
+
activeColor
|
|
1776
|
+
} else {
|
|
1777
|
+
normalColor
|
|
1778
|
+
}
|
|
1595
1779
|
val baseline = centerY - (paint.descent() + paint.ascent()) / 2f
|
|
1596
1780
|
canvas.drawText(title, width / 2f, baseline, paint)
|
|
1597
1781
|
}
|
|
@@ -1697,7 +1881,11 @@ private class NativeListSectionIndexView(
|
|
|
1697
1881
|
}
|
|
1698
1882
|
}
|
|
1699
1883
|
|
|
1700
|
-
private class ItemSpacingDecoration(
|
|
1884
|
+
private class ItemSpacingDecoration(
|
|
1885
|
+
private val spacing: Int,
|
|
1886
|
+
private val sourceSpacing: Int,
|
|
1887
|
+
private val density: Float,
|
|
1888
|
+
) : RecyclerView.ItemDecoration() {
|
|
1701
1889
|
override fun getItemOffsets(
|
|
1702
1890
|
outRect: android.graphics.Rect,
|
|
1703
1891
|
view: View,
|
|
@@ -1706,7 +1894,14 @@ private class ItemSpacingDecoration(private val spacing: Int) : RecyclerView.Ite
|
|
|
1706
1894
|
) {
|
|
1707
1895
|
if (spacing <= 0) return
|
|
1708
1896
|
val horizontal = (parent.layoutManager as? LinearLayoutManager)?.orientation == RecyclerView.HORIZONTAL
|
|
1709
|
-
|
|
1897
|
+
val item = view.tag as? NativeListItem
|
|
1898
|
+
val sourceWallet = item?.type == "identity" && item.json.optString("presentation") == "walletSidebar" && item.json.has("height")
|
|
1899
|
+
// OneKey patch: V1 measures the wallet and its bottom padding as one sortable row.
|
|
1900
|
+
val itemSpacing = if (!horizontal && sourceWallet) {
|
|
1901
|
+
val sourceHeight = item!!.json.optInt("height")
|
|
1902
|
+
((sourceHeight + sourceSpacing) * density).roundToInt() - (sourceHeight * density).roundToInt()
|
|
1903
|
+
} else spacing
|
|
1904
|
+
if (horizontal) outRect.right = itemSpacing else outRect.bottom = itemSpacing
|
|
1710
1905
|
}
|
|
1711
1906
|
}
|
|
1712
1907
|
|
|
@@ -1733,22 +1928,32 @@ private class StickySectionHeaderDecoration(
|
|
|
1733
1928
|
for (index in first downTo 0) {
|
|
1734
1929
|
val candidate = adapter.itemAt(index)
|
|
1735
1930
|
if (candidate?.type == "sectionHeader") {
|
|
1736
|
-
if (candidate.json.optString("variant") == "summary") continue
|
|
1931
|
+
if (candidate.json.optString("variant") == "summary" || !candidate.json.optBoolean("sticky", true)) continue
|
|
1737
1932
|
header = candidate.takeIf(::isSimpleStickySectionHeader)
|
|
1738
1933
|
break
|
|
1739
1934
|
}
|
|
1740
1935
|
}
|
|
1741
1936
|
val item = header ?: return
|
|
1937
|
+
// OneKey patch: a pinned selector heading must use the same text rasterization as its row.
|
|
1938
|
+
textPaint.flags = if (item.usesSelectorSourceScale) Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG else Paint.ANTI_ALIAS_FLAG
|
|
1742
1939
|
val isHistory = item.json.optString("variant") == "history" ||
|
|
1743
1940
|
item.sectionKey?.startsWith("history-") == true
|
|
1744
|
-
val
|
|
1745
|
-
val
|
|
1746
|
-
|
|
1747
|
-
|
|
1941
|
+
val sourceHeight = item.json.optInt("height", 36) * density
|
|
1942
|
+
val height = if (item.usesSelectorSourceScale) {
|
|
1943
|
+
if (item.json.optString("heightRounding") == "nearest") sourceHeight.roundToInt() else sourceHeight.toInt()
|
|
1944
|
+
} else NativeListScale.dp(context.resources, if (isHistory) 16 else 36)
|
|
1945
|
+
val textSize = if (item.usesSelectorSourceScale) 14f else NativeListScale.font(context.resources, if (isHistory) 12f else 14f)
|
|
1946
|
+
textPaint.textSize = if (item.usesSelectorSourceScale) kotlin.math.ceil((textSize * density).toDouble()).toFloat() else textSize * density
|
|
1947
|
+
val horizontalInset = if (item.usesSelectorSourceScale) ((20 * density).toInt() - parent.paddingLeft).toFloat() else NativeListScale.dp(context.resources, if (isHistory) 8 else 20).toFloat()
|
|
1748
1948
|
val left = parent.paddingLeft.toFloat()
|
|
1749
1949
|
val right = (parent.width - parent.paddingRight).toFloat()
|
|
1750
1950
|
canvas.drawRect(left, 0f, right, height.toFloat(), backgroundPaint)
|
|
1751
|
-
val baseline =
|
|
1951
|
+
val baseline = if (item.usesSelectorSourceScale) {
|
|
1952
|
+
val metrics = textPaint.fontMetricsInt
|
|
1953
|
+
val lineHeight = kotlin.math.ceil(20 * density.toDouble()).toInt()
|
|
1954
|
+
val leading = lineHeight - (metrics.descent - metrics.ascent)
|
|
1955
|
+
(height - lineHeight) / 2 - metrics.ascent + kotlin.math.ceil(leading / 2.0).toFloat()
|
|
1956
|
+
} else height / 2f - (textPaint.descent() + textPaint.ascent()) / 2f
|
|
1752
1957
|
val value = item.json.optString("title").let { if (isHistory) it.uppercase() else it }
|
|
1753
1958
|
val isRightToLeft = parent.layoutDirection == View.LAYOUT_DIRECTION_RTL
|
|
1754
1959
|
val textWidth = if (isHistory) {
|
|
@@ -1789,6 +1994,29 @@ private class StickySectionHeaderDecoration(
|
|
|
1789
1994
|
|
|
1790
1995
|
internal fun isSimpleStickySectionHeader(item: NativeListItem): Boolean =
|
|
1791
1996
|
item.type == "sectionHeader" &&
|
|
1997
|
+
item.json.optBoolean("sticky", true) &&
|
|
1792
1998
|
item.json.optString("variant") != "summary" &&
|
|
1793
1999
|
item.json.optString("value").isEmpty() &&
|
|
1794
2000
|
item.json.optJSONObject("checkbox") == null
|
|
2001
|
+
|
|
2002
|
+
// OneKey patch: paint only explicitly requested backgrounds into list side padding.
|
|
2003
|
+
private class SelectorBackgroundDecoration(private val adapter: NativeListAdapter) : RecyclerView.ItemDecoration() {
|
|
2004
|
+
private val paint = Paint()
|
|
2005
|
+
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
|
2006
|
+
for (index in 0 until parent.childCount) {
|
|
2007
|
+
val child = parent.getChildAt(index)
|
|
2008
|
+
val item = adapter.itemAt(parent.getChildAdapterPosition(child)) ?: continue
|
|
2009
|
+
if (!item.json.optBoolean("backgroundFullWidth", false)) continue
|
|
2010
|
+
val color = item.json.optString("backgroundColor")
|
|
2011
|
+
if (color.isEmpty()) continue
|
|
2012
|
+
paint.color = try { parseNativeListColor(color) } catch (_: IllegalArgumentException) { Color.TRANSPARENT }
|
|
2013
|
+
val top = child.y
|
|
2014
|
+
canvas.drawRect(0f, top, parent.width.toFloat(), top + child.height, paint)
|
|
2015
|
+
if (item.type == "system" && item.json.optString("variant") == "warning") {
|
|
2016
|
+
paint.color = try { parseNativeListColor(item.json.optString("borderColor", "#E0E0E0")) } catch (_: IllegalArgumentException) { Color.TRANSPARENT }
|
|
2017
|
+
canvas.drawRect(0f, top, parent.width.toFloat(), top + 1, paint)
|
|
2018
|
+
canvas.drawRect(0f, top + child.height - 1, parent.width.toFloat(), top + child.height, paint)
|
|
2019
|
+
}
|
|
2020
|
+
}
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
@@ -21,7 +21,14 @@ final class HybridNativeList: HybridNativeListSpec {
|
|
|
21
21
|
var onSelectionDelta: ((_ payloadJson: String) -> Void)?
|
|
22
22
|
var onReorder: ((_ payloadJson: String) -> Void)?
|
|
23
23
|
var onEndReached: ((_ payloadJson: String) -> Void)?
|
|
24
|
-
var onVisibleRangeChanged: ((_ payloadJson: String) -> Void)?
|
|
24
|
+
var onVisibleRangeChanged: ((_ payloadJson: String) -> Void)? {
|
|
25
|
+
didSet {
|
|
26
|
+
runOnMain { [weak self] in
|
|
27
|
+
guard let self else { return }
|
|
28
|
+
self.hostView.onVisibleRangeChanged = self.onVisibleRangeChanged
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
25
32
|
|
|
26
33
|
override init() {
|
|
27
34
|
super.init()
|
|
@@ -30,7 +37,6 @@ final class HybridNativeList: HybridNativeListSpec {
|
|
|
30
37
|
hostView.onSelectionDelta = { [weak self] in self?.onSelectionDelta?($0) }
|
|
31
38
|
hostView.onReorder = { [weak self] in self?.onReorder?($0) }
|
|
32
39
|
hostView.onEndReached = { [weak self] in self?.onEndReached?($0) }
|
|
33
|
-
hostView.onVisibleRangeChanged = { [weak self] in self?.onVisibleRangeChanged?($0) }
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
func applySnapshot(snapshotJson: String) throws {
|