@onekeyfe/react-native-native-list 3.0.157-alpha.249 → 3.0.157-alpha.250

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +21 -5
  2. package/android/src/main/java/com/onekey/nativelist/NativeListAccessoryStack.kt +711 -0
  3. package/android/src/main/java/com/onekey/nativelist/NativeListActionRenderer.kt +169 -0
  4. package/android/src/main/java/com/onekey/nativelist/NativeListActivityRenderer.kt +243 -0
  5. package/android/src/main/java/com/onekey/nativelist/NativeListAdapter.kt +20 -6
  6. package/android/src/main/java/com/onekey/nativelist/NativeListDataRowRenderer.kt +487 -0
  7. package/android/src/main/java/com/onekey/nativelist/NativeListIdentityRenderer.kt +820 -0
  8. package/android/src/main/java/com/onekey/nativelist/NativeListImageSlot.kt +147 -0
  9. package/android/src/main/java/com/onekey/nativelist/NativeListLeadingVisual.kt +453 -0
  10. package/android/src/main/java/com/onekey/nativelist/NativeListMarketRenderer.kt +997 -0
  11. package/android/src/main/java/com/onekey/nativelist/NativeListMediaTileRenderer.kt +234 -0
  12. package/android/src/main/java/com/onekey/nativelist/NativeListMessageRenderer.kt +232 -0
  13. package/android/src/main/java/com/onekey/nativelist/NativeListMetricCardRenderer.kt +551 -0
  14. package/android/src/main/java/com/onekey/nativelist/NativeListModels.kt +24 -0
  15. package/android/src/main/java/com/onekey/nativelist/NativeListPrimitives.kt +475 -0
  16. package/android/src/main/java/com/onekey/nativelist/NativeListRailRenderer.kt +143 -0
  17. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRegistry.kt +42 -0
  18. package/android/src/main/java/com/onekey/nativelist/NativeListRendererRowView.kt +415 -0
  19. package/android/src/main/java/com/onekey/nativelist/NativeListResolvedText.kt +163 -0
  20. package/android/src/main/java/com/onekey/nativelist/NativeListRowHost.kt +70 -0
  21. package/android/src/main/java/com/onekey/nativelist/NativeListSectionHeaderRenderer.kt +748 -0
  22. package/android/src/main/java/com/onekey/nativelist/NativeListSystemRenderer.kt +464 -0
  23. package/android/src/main/java/com/onekey/nativelist/NativeListTextView.kt +25 -0
  24. package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +174 -104
  25. package/android/src/main/java/com/onekey/nativelist/NativeListWalletGroupRenderer.kt +407 -0
  26. package/android/src/test/java/com/margelo/nitro/nativelist/NativeListSelectorBackgroundColorTest.kt +34 -0
  27. package/docs/DESIGN.md +518 -9
  28. package/docs/MIGRATION_BASELINE.md +117 -0
  29. package/docs/ROW_TEMPLATES.md +359 -0
  30. package/docs/SPEC.md +358 -0
  31. package/docs/STYLE_SPEC.md +978 -0
  32. package/docs/images/list-capabilities.svg +39 -0
  33. package/docs/images/row-action.svg +17 -0
  34. package/docs/images/row-activity.svg +24 -0
  35. package/docs/images/row-data.svg +24 -0
  36. package/docs/images/row-identity.svg +25 -0
  37. package/docs/images/row-market.svg +23 -0
  38. package/docs/images/row-media-tile.svg +26 -0
  39. package/docs/images/row-message.svg +20 -0
  40. package/docs/images/row-metric-card.svg +33 -0
  41. package/docs/images/row-rail.svg +18 -0
  42. package/docs/images/row-section-header.svg +19 -0
  43. package/docs/images/row-style-regions.svg +30 -0
  44. package/docs/images/row-system.svg +30 -0
  45. package/docs/images/row-wallet-group.svg +26 -0
  46. package/ios/NativeListAccessoryStack.swift +680 -0
  47. package/ios/NativeListActionRenderer.swift +108 -0
  48. package/ios/NativeListActivityRenderer.swift +179 -0
  49. package/ios/NativeListDataRowRenderer.swift +444 -0
  50. package/ios/NativeListIdentityRenderer.swift +685 -0
  51. package/ios/NativeListImageSlot.swift +140 -0
  52. package/ios/NativeListLeadingVisual.swift +372 -0
  53. package/ios/NativeListMarketRenderer.swift +698 -0
  54. package/ios/NativeListMediaTileRenderer.swift +184 -0
  55. package/ios/NativeListMessageRenderer.swift +168 -0
  56. package/ios/NativeListMetricCardRenderer.swift +420 -0
  57. package/ios/NativeListModels.swift +32 -0
  58. package/ios/NativeListRailRenderer.swift +152 -0
  59. package/ios/NativeListRendererCell.swift +311 -0
  60. package/ios/NativeListRendererRegistry.swift +50 -0
  61. package/ios/NativeListResolvedText.swift +122 -0
  62. package/ios/NativeListRowHost.swift +63 -0
  63. package/ios/NativeListSectionHeaderRenderer.swift +708 -0
  64. package/ios/NativeListSystemRenderer.swift +568 -0
  65. package/ios/NativeListTextLabel.swift +42 -0
  66. package/ios/NativeListTextStyles.swift +236 -0
  67. package/ios/NativeListWalletGroupRenderer.swift +255 -0
  68. package/ios/RNCNativeListView.swift +111 -180
  69. package/lib/module/models.js +54 -0
  70. package/lib/module/validation.js +324 -26
  71. package/lib/module/web/NativeListWebEngine.js +148 -936
  72. package/lib/module/web/templates/ActionRowRenderer.js +97 -0
  73. package/lib/module/web/templates/ActivityRowRenderer.js +95 -0
  74. package/lib/module/web/templates/DataRowRenderer.js +145 -0
  75. package/lib/module/web/templates/IdentityRowRenderer.js +289 -0
  76. package/lib/module/web/templates/MarketRowRenderer.js +310 -0
  77. package/lib/module/web/templates/MediaTileRowRenderer.js +132 -0
  78. package/lib/module/web/templates/MessageRowRenderer.js +165 -0
  79. package/lib/module/web/templates/MetricCardRowRenderer.js +172 -0
  80. package/lib/module/web/templates/RailRowRenderer.js +98 -0
  81. package/lib/module/web/templates/RowContainerStyle.js +100 -0
  82. package/lib/module/web/templates/RowElements.js +93 -0
  83. package/lib/module/web/templates/RowRendererRegistry.js +70 -0
  84. package/lib/module/web/templates/RowText.js +121 -0
  85. package/lib/module/web/templates/RowVisual.js +43 -0
  86. package/lib/module/web/templates/SectionHeaderRowRenderer.js +146 -0
  87. package/lib/module/web/templates/SystemRowRenderer.js +161 -0
  88. package/lib/module/web/templates/WalletGroupRowRenderer.js +96 -0
  89. package/lib/typescript/src/models.d.ts +166 -13
  90. package/lib/typescript/src/web/NativeListWebEngine.d.ts +18 -16
  91. package/lib/typescript/src/web/templates/ActionRowRenderer.d.ts +15 -0
  92. package/lib/typescript/src/web/templates/ActivityRowRenderer.d.ts +15 -0
  93. package/lib/typescript/src/web/templates/DataRowRenderer.d.ts +15 -0
  94. package/lib/typescript/src/web/templates/IdentityRowRenderer.d.ts +16 -0
  95. package/lib/typescript/src/web/templates/MarketRowRenderer.d.ts +30 -0
  96. package/lib/typescript/src/web/templates/MediaTileRowRenderer.d.ts +15 -0
  97. package/lib/typescript/src/web/templates/MessageRowRenderer.d.ts +44 -0
  98. package/lib/typescript/src/web/templates/MetricCardRowRenderer.d.ts +15 -0
  99. package/lib/typescript/src/web/templates/RailRowRenderer.d.ts +28 -0
  100. package/lib/typescript/src/web/templates/RowContainerStyle.d.ts +9 -0
  101. package/lib/typescript/src/web/templates/RowElements.d.ts +38 -0
  102. package/lib/typescript/src/web/templates/RowRendererRegistry.d.ts +83 -0
  103. package/lib/typescript/src/web/templates/RowText.d.ts +9 -0
  104. package/lib/typescript/src/web/templates/RowVisual.d.ts +20 -0
  105. package/lib/typescript/src/web/templates/SectionHeaderRowRenderer.d.ts +15 -0
  106. package/lib/typescript/src/web/templates/SystemRowRenderer.d.ts +16 -0
  107. package/lib/typescript/src/web/templates/WalletGroupRowRenderer.d.ts +20 -0
  108. package/package.json +3 -3
  109. package/src/models.ts +281 -19
  110. package/src/validation.ts +501 -46
  111. package/src/web/NativeListWebEngine.ts +361 -1932
  112. package/src/web/templates/ActionRowRenderer.ts +123 -0
  113. package/src/web/templates/ActivityRowRenderer.ts +159 -0
  114. package/src/web/templates/DataRowRenderer.ts +212 -0
  115. package/src/web/templates/IdentityRowRenderer.ts +527 -0
  116. package/src/web/templates/MarketRowRenderer.ts +467 -0
  117. package/src/web/templates/MediaTileRowRenderer.ts +182 -0
  118. package/src/web/templates/MessageRowRenderer.ts +306 -0
  119. package/src/web/templates/MetricCardRowRenderer.ts +337 -0
  120. package/src/web/templates/RailRowRenderer.ts +136 -0
  121. package/src/web/templates/RowContainerStyle.ts +131 -0
  122. package/src/web/templates/RowElements.ts +160 -0
  123. package/src/web/templates/RowRendererRegistry.ts +111 -0
  124. package/src/web/templates/RowText.ts +181 -0
  125. package/src/web/templates/RowVisual.ts +115 -0
  126. package/src/web/templates/SectionHeaderRowRenderer.ts +243 -0
  127. package/src/web/templates/SystemRowRenderer.ts +282 -0
  128. package/src/web/templates/WalletGroupRowRenderer.ts +131 -0
  129. package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +0 -4538
  130. package/ios/NativeListCell.swift +0 -4300
@@ -226,12 +226,69 @@ class NativeListView(
226
226
  mass = REORDER_SPRING_MASS,
227
227
  durationSeconds = REORDER_SPRING_DURATION_MS / 1_000.0,
228
228
  )
229
- private val footerView = NativeListRowView(reactContext)
229
+ private var footerView: NativeListRowHost = NativeListRendererRegistry.create(reactContext, NativeListRendererKey.ACTION.ordinal)
230
+ private var footerRendererKey = NativeListRendererKey.ACTION
230
231
  private val sectionIndexView = NativeListSectionIndexView(context)
231
232
  private val sectionIndexPreview = TextView(context)
232
233
  private var config: NativeListConfig? = null
233
234
  private var usesSelectorSourceScale = false
234
- private var stickyDecoration: StickySectionHeaderDecoration? = null
235
+ private val stickyHeaderView = NativeListRendererRegistry.create(reactContext, NativeListRendererKey.SECTION_HEADER.ordinal)
236
+ private var stickyHeaderSignature: String? = null
237
+ private var stickyHeaderConfig: NativeListConfig? = null
238
+ private val stickyHeaderHost = object : FrameLayout(reactContext) {
239
+ private var down: MotionEvent? = null
240
+ private var forwarding = false
241
+
242
+ private fun forward(event: MotionEvent) {
243
+ val copy = MotionEvent.obtain(event)
244
+ copy.offsetLocation(x - refreshLayout.x - recyclerView.x, y - refreshLayout.y - recyclerView.y)
245
+ recyclerView.dispatchTouchEvent(copy)
246
+ copy.recycle()
247
+ }
248
+
249
+ override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
250
+ when (event.actionMasked) {
251
+ MotionEvent.ACTION_DOWN -> { down?.recycle(); down = MotionEvent.obtain(event); forwarding = false }
252
+ MotionEvent.ACTION_MOVE -> {
253
+ val initial = down
254
+ if (initial != null && kotlin.math.abs(event.y - initial.y) > pagerGestureTouchSlop) {
255
+ startForwarding(initial, event)
256
+ return true
257
+ }
258
+ }
259
+ MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { down?.recycle(); down = null }
260
+ }
261
+ return false
262
+ }
263
+
264
+ // The intercepting MOVE is not redelivered to onTouchEvent, so forward it
265
+ // together with the initial DOWN to keep the list's drag continuous.
266
+ private fun startForwarding(initial: MotionEvent, event: MotionEvent) {
267
+ forward(initial)
268
+ forward(event)
269
+ forwarding = true
270
+ parent?.requestDisallowInterceptTouchEvent(true)
271
+ }
272
+
273
+ override fun onTouchEvent(event: MotionEvent): Boolean {
274
+ if (!forwarding) return super.onTouchEvent(event)
275
+ forward(event)
276
+ if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_CANCEL) {
277
+ forwarding = false
278
+ down?.recycle(); down = null
279
+ }
280
+ return true
281
+ }
282
+
283
+ override fun onDetachedFromWindow() { down?.recycle(); down = null; super.onDetachedFromWindow() }
284
+ }
285
+ private val stickyPreDraw = android.view.ViewTreeObserver.OnPreDrawListener {
286
+ updateStickyHeader()
287
+ true
288
+ }
289
+ // Registered only while attached, on the captured window observer, so detach
290
+ // removes the same instance the listener was merged into.
291
+ private var stickyObservedTree: ViewTreeObserver? = null
235
292
  private var spacingDecoration: ItemSpacingDecoration? = null
236
293
  private var itemTouchHelper: ItemTouchHelper? = null
237
294
  private var reorderTouchListener: RecyclerView.OnItemTouchListener? = null
@@ -307,6 +364,9 @@ class NativeListView(
307
364
  FrameLayout.LayoutParams.MATCH_PARENT,
308
365
  ),
309
366
  )
367
+ stickyHeaderHost.visibility = GONE
368
+ stickyHeaderHost.addView(stickyHeaderView, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT))
369
+ contentContainer.addView(stickyHeaderHost, FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT, Gravity.TOP))
310
370
  contentContainer.addView(
311
371
  sectionIndexView,
312
372
  FrameLayout.LayoutParams(
@@ -342,6 +402,9 @@ class NativeListView(
342
402
  adapter.onAction = ::handleAction
343
403
  adapter.onBindingInvalidated = ::handleBindingInvalidated
344
404
  adapter.checkboxState = ::resolveCheckboxState
405
+ stickyHeaderView.onRowPress = ::handleRowPress
406
+ stickyHeaderView.onAction = ::handleAction
407
+ stickyHeaderView.onBindingInvalidated = ::handleBindingInvalidated
345
408
  footerView.onRowPress = ::handleRowPress
346
409
  footerView.onAction = ::handleAction
347
410
  footerView.onBindingInvalidated = ::handleBindingInvalidated
@@ -459,6 +522,7 @@ class NativeListView(
459
522
 
460
523
  override fun onAttachedToWindow() {
461
524
  super.onAttachedToWindow()
525
+ startStickyHeaderTracking()
462
526
  updateSectionIndexAttachment()
463
527
  val first = config?.items?.firstOrNull()
464
528
  if (recyclerView.isLayoutRequested && (first?.type == "market" || first?.json?.optString("presentation") == "market")) {
@@ -467,6 +531,7 @@ class NativeListView(
467
531
  }
468
532
 
469
533
  override fun onDetachedFromWindow() {
534
+ stopStickyHeaderTracking()
470
535
  stopSectionIndexAttachmentTracking()
471
536
  scheduleSectionIndexAttachmentToList()
472
537
  updateRefreshIndicatorOffset(0)
@@ -500,9 +565,13 @@ class NativeListView(
500
565
  usesSelectorSourceScale = next.items.any { it.usesSelectorSourceScale }
501
566
  adapter.usesSelectorSourceScale = usesSelectorSourceScale
502
567
  adapter.theme = next.theme
568
+ adapter.listStyle = next.listStyle
503
569
  adapter.layout = next.layout
504
570
  adapter.orientation = next.orientation
505
571
  adapter.selectedKeys = next.selectedKeys
572
+ // Rows rebind through the adapter's listStyle invalidation; the fixed footer is bound
573
+ // outside the adapter, so a listStyle-only snapshot must rebind it here.
574
+ if (previous.listStyle?.toString() != next.listStyle?.toString()) bindFooter(next)
506
575
  adapter.submitList(next.items) {
507
576
  // OneKey patch: DiffUtil has dispatched its payloads; release the old serialized rows.
508
577
  next.items.forEach { it.selectionUpdateFromContent = null }
@@ -535,6 +604,7 @@ class NativeListView(
535
604
  if (previous?.generation != next.generation) endReachedGeneration = null
536
605
  pendingReorder = null
537
606
  adapter.theme = next.theme
607
+ adapter.listStyle = next.listStyle
538
608
  adapter.layout = next.layout
539
609
  adapter.orientation = next.orientation
540
610
  adapter.selectedKeys = next.selectedKeys
@@ -1119,6 +1189,9 @@ class NativeListView(
1119
1189
  itemTouchHelper = null
1120
1190
  footerView.recycle()
1121
1191
  footerView.dispose()
1192
+ stopStickyHeaderTracking()
1193
+ stickyHeaderView.dispose()
1194
+ stickyHeaderHost.visibility = GONE
1122
1195
  recyclerView.swapAdapter(null, false)
1123
1196
  adapter.dispose()
1124
1197
  }
@@ -1151,10 +1224,63 @@ class NativeListView(
1151
1224
 
1152
1225
  spacingDecoration?.let(recyclerView::removeItemDecoration)
1153
1226
  spacingDecoration = ItemSpacingDecoration(dp(next.itemSpacing), next.itemSpacing, density).also(recyclerView::addItemDecoration)
1154
- stickyDecoration?.let(recyclerView::removeItemDecoration)
1155
- stickyDecoration = if (next.stickyHeaders && orientation == RecyclerView.VERTICAL) {
1156
- StickySectionHeaderDecoration(adapter, context, next.theme, density).also(recyclerView::addItemDecoration)
1157
- } else null
1227
+ stickyHeaderSignature = null
1228
+ if (!next.stickyHeaders || orientation != RecyclerView.VERTICAL) stickyHeaderHost.visibility = GONE
1229
+ }
1230
+
1231
+ private fun updateStickyHeader() {
1232
+ val current = config
1233
+ if (current == null || !current.stickyHeaders || layoutManager.orientation != RecyclerView.VERTICAL || recyclerView.width <= 0) {
1234
+ stickyHeaderHost.visibility = GONE
1235
+ return
1236
+ }
1237
+ val first = layoutManager.findFirstVisibleItemPosition()
1238
+ var position = first
1239
+ while (position >= 0 && adapter.itemAt(position)?.let(::isStickySectionHeader) != true) position--
1240
+ val item = adapter.itemAt(position)
1241
+ if (item == null || first == RecyclerView.NO_POSITION || (layoutManager.findViewByPosition(position)?.top ?: -1) > 0) {
1242
+ stickyHeaderHost.visibility = GONE
1243
+ return
1244
+ }
1245
+ val width = recyclerView.width - recyclerView.paddingLeft - recyclerView.paddingRight
1246
+ if (width <= 0) {
1247
+ stickyHeaderHost.visibility = GONE
1248
+ return
1249
+ }
1250
+ val signature = "${item.content}:$width:${recyclerView.layoutDirection}:$usesSelectorSourceScale"
1251
+ if (stickyHeaderConfig !== current || stickyHeaderSignature != signature) {
1252
+ stickyHeaderConfig = current
1253
+ stickyHeaderSignature = signature
1254
+ stickyHeaderView.listStyle = current.listStyle
1255
+ stickyHeaderView.bind(item, current.theme, current.layout, current.orientation, position,
1256
+ item.json.optBoolean("selected", false) || current.selectedKeys.contains(item.key), ::resolveCheckboxState,
1257
+ useSourceScale = usesSelectorSourceScale)
1258
+ stickyHeaderView.measure(View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED))
1259
+ stickyHeaderHost.layoutParams = (stickyHeaderHost.layoutParams as FrameLayout.LayoutParams).apply {
1260
+ this.width = width
1261
+ height = stickyHeaderView.measuredHeight
1262
+ leftMargin = recyclerView.paddingLeft
1263
+ rightMargin = recyclerView.paddingRight
1264
+ }
1265
+ }
1266
+ val height = stickyHeaderView.measuredHeight
1267
+ var nextTop = Int.MAX_VALUE
1268
+ for (index in 0 until recyclerView.childCount) {
1269
+ val child = recyclerView.getChildAt(index)
1270
+ val childPosition = recyclerView.getChildAdapterPosition(child)
1271
+ if (childPosition > position && adapter.itemAt(childPosition)?.let(::isStickySectionHeader) == true) nextTop = minOf(nextTop, child.top)
1272
+ }
1273
+ stickyHeaderHost.translationY = minOf(0, nextTop - height).toFloat()
1274
+ stickyHeaderHost.visibility = VISIBLE
1275
+ // React Native can retain the measured parent while this overlay becomes
1276
+ // visible in pre-draw. Lay out the container-owned header immediately.
1277
+ if (stickyHeaderHost.isLayoutRequested || stickyHeaderHost.width != width || stickyHeaderHost.height != height) {
1278
+ stickyHeaderHost.measure(
1279
+ View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY),
1280
+ View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY),
1281
+ )
1282
+ stickyHeaderHost.layout(recyclerView.paddingLeft, 0, recyclerView.paddingLeft + width, height)
1283
+ }
1158
1284
  }
1159
1285
 
1160
1286
  private fun configureSectionIndex(next: NativeListConfig) {
@@ -1264,6 +1390,20 @@ class NativeListView(
1264
1390
  }
1265
1391
  }
1266
1392
 
1393
+ private fun startStickyHeaderTracking() {
1394
+ if (resourcesDisposed) return
1395
+ val observer = viewTreeObserver
1396
+ if (stickyObservedTree === observer) return
1397
+ stopStickyHeaderTracking()
1398
+ observer.addOnPreDrawListener(stickyPreDraw)
1399
+ stickyObservedTree = observer
1400
+ }
1401
+
1402
+ private fun stopStickyHeaderTracking() {
1403
+ stickyObservedTree?.takeIf(ViewTreeObserver::isAlive)?.removeOnPreDrawListener(stickyPreDraw)
1404
+ stickyObservedTree = null
1405
+ }
1406
+
1267
1407
  private fun startSectionIndexAttachmentTracking() {
1268
1408
  val observer = viewTreeObserver
1269
1409
  if (sectionIndexObservedTree === observer) return
@@ -1380,7 +1520,21 @@ class NativeListView(
1380
1520
  footerView.visibility = GONE
1381
1521
  footerView.recycle()
1382
1522
  } else {
1523
+ if (footer.rendererKey != footerRendererKey) {
1524
+ val previous = footerView
1525
+ val position = indexOfChild(previous)
1526
+ previous.recycle()
1527
+ previous.dispose()
1528
+ footerView = NativeListRendererRegistry.create(reactContext, footer.rendererKey.ordinal)
1529
+ footerView.onRowPress = ::handleRowPress
1530
+ footerView.onAction = ::handleAction
1531
+ footerView.onBindingInvalidated = ::handleBindingInvalidated
1532
+ removeView(previous)
1533
+ addView(footerView, position, previous.layoutParams)
1534
+ footerRendererKey = footer.rendererKey
1535
+ }
1383
1536
  footerView.visibility = VISIBLE
1537
+ footerView.listStyle = next.listStyle
1384
1538
  footerView.bind(
1385
1539
  footer,
1386
1540
  next.theme,
@@ -1490,7 +1644,7 @@ class NativeListView(
1490
1644
  return false
1491
1645
  }
1492
1646
 
1493
- private fun handleBindingInvalidated(row: NativeListRowView, epoch: Long) {
1647
+ private fun handleBindingInvalidated(row: NativeListRowHost, epoch: Long) {
1494
1648
  val anchor = actionAnchor ?: return
1495
1649
  if (anchor.ownerRowView.get() === row && anchor.bindingEpoch == epoch) {
1496
1650
  invalidateActionAnchor("rebind")
@@ -2593,109 +2747,18 @@ private class ItemSpacingDecoration(
2593
2747
  if (spacing <= 0) return
2594
2748
  val horizontal = (parent.layoutManager as? LinearLayoutManager)?.orientation == RecyclerView.HORIZONTAL
2595
2749
  val item = view.tag as? NativeListItem
2596
- val sourceWallet = item?.type == "identity" && item.json.optString("presentation") == "walletSidebar" && item.json.has("height")
2750
+ val sourceWallet = item?.type == "identity" && item.json.optString("presentation") == "walletSidebar" && item.hasExplicitHeight
2597
2751
  // OneKey patch: V1 measures the wallet and its bottom padding as one sortable row.
2598
2752
  val itemSpacing = if (!horizontal && sourceWallet) {
2599
- val sourceHeight = item!!.json.optInt("height")
2753
+ val sourceHeight = item!!.styledHeight ?: item.json.optDouble("height")
2600
2754
  ((sourceHeight + sourceSpacing) * density).roundToInt() - (sourceHeight * density).roundToInt()
2601
2755
  } else spacing
2602
2756
  if (horizontal) outRect.right = itemSpacing else outRect.bottom = itemSpacing
2603
2757
  }
2604
2758
  }
2605
2759
 
2606
- private class StickySectionHeaderDecoration(
2607
- private val adapter: NativeListAdapter,
2608
- private val context: android.content.Context,
2609
- theme: JSONObject?,
2610
- private val density: Float,
2611
- ) : RecyclerView.ItemDecoration() {
2612
- private val backgroundPaint = Paint().apply {
2613
- color = parseColor(theme?.optString("rowBackground"), "#FFFFFF")
2614
- }
2615
- private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
2616
- color = parseColor(theme?.optString("secondaryText"), "#0000009B")
2617
- textSize = NativeListScale.font(context.resources, 14f) * density
2618
- typeface = NativeListFonts.semibold(context)
2619
- }
2620
-
2621
- override fun onDrawOver(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
2622
- val manager = parent.layoutManager as? LinearLayoutManager ?: return
2623
- val first = manager.findFirstVisibleItemPosition()
2624
- if (first == RecyclerView.NO_POSITION) return
2625
- var header: NativeListItem? = null
2626
- for (index in first downTo 0) {
2627
- val candidate = adapter.itemAt(index)
2628
- if (candidate?.type == "sectionHeader") {
2629
- if (candidate.json.optString("variant") == "summary" || !candidate.json.optBoolean("sticky", true)) continue
2630
- header = candidate.takeIf(::isSimpleStickySectionHeader)
2631
- break
2632
- }
2633
- }
2634
- val item = header ?: return
2635
- // OneKey patch: a pinned selector heading must use the same text rasterization as its row.
2636
- textPaint.flags = if (item.usesSelectorSourceScale) Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG or Paint.LINEAR_TEXT_FLAG else Paint.ANTI_ALIAS_FLAG
2637
- val isHistory = item.json.optString("variant") == "history" ||
2638
- item.sectionKey?.startsWith("history-") == true
2639
- val sourceHeight = item.json.optInt("height", 36) * density
2640
- val height = if (item.usesSelectorSourceScale) {
2641
- if (item.json.optString("heightRounding") == "nearest") sourceHeight.roundToInt() else sourceHeight.toInt()
2642
- } else NativeListScale.dp(context.resources, if (isHistory) 16 else 36)
2643
- val textSize = if (item.usesSelectorSourceScale) 14f else NativeListScale.font(context.resources, if (isHistory) 12f else 14f)
2644
- textPaint.textSize = if (item.usesSelectorSourceScale) kotlin.math.ceil((textSize * density).toDouble()).toFloat() else textSize * density
2645
- val horizontalInset = if (item.usesSelectorSourceScale) ((20 * density).toInt() - parent.paddingLeft).toFloat() else NativeListScale.dp(context.resources, if (isHistory) 8 else 20).toFloat()
2646
- val left = parent.paddingLeft.toFloat()
2647
- val right = (parent.width - parent.paddingRight).toFloat()
2648
- canvas.drawRect(left, 0f, right, height.toFloat(), backgroundPaint)
2649
- val baseline = if (item.usesSelectorSourceScale) {
2650
- val metrics = textPaint.fontMetricsInt
2651
- val lineHeight = kotlin.math.ceil(20 * density.toDouble()).toInt()
2652
- val leading = lineHeight - (metrics.descent - metrics.ascent)
2653
- (height - lineHeight) / 2 - metrics.ascent + kotlin.math.ceil(leading / 2.0).toFloat()
2654
- } else height / 2f - (textPaint.descent() + textPaint.ascent()) / 2f
2655
- val value = item.json.optString("title").let { if (isHistory) it.uppercase() else it }
2656
- val isRightToLeft = parent.layoutDirection == View.LAYOUT_DIRECTION_RTL
2657
- val textWidth = if (isHistory) {
2658
- spacedTextWidth(value, NativeListScale.dp(context.resources, 1).toFloat())
2659
- } else {
2660
- textPaint.measureText(value)
2661
- }
2662
- val x = if (isRightToLeft) right - horizontalInset - textWidth else left + horizontalInset
2663
- if (isHistory) {
2664
- drawSpacedText(canvas, value, x, baseline, NativeListScale.dp(context.resources, 1).toFloat())
2665
- } else {
2666
- canvas.drawText(value, x, baseline, textPaint)
2667
- }
2668
- }
2669
-
2670
- private fun drawSpacedText(canvas: Canvas, value: String, x: Float, baseline: Float, spacing: Float) {
2671
- var cursor = x
2672
- value.forEachIndexed { index, character ->
2673
- val glyph = character.toString()
2674
- canvas.drawText(glyph, cursor, baseline, textPaint)
2675
- cursor += textPaint.measureText(glyph)
2676
- if (index < value.lastIndex) cursor += spacing * 0.8f
2677
- }
2678
- }
2679
-
2680
- private fun spacedTextWidth(value: String, spacing: Float): Float =
2681
- value.sumOf { textPaint.measureText(it.toString()).toDouble() }.toFloat() +
2682
- max(0, value.length - 1) * spacing * 0.8f
2683
-
2684
- companion object {
2685
- private fun parseColor(value: String?, fallback: String): Int = try {
2686
- parseNativeListColor(if (value.isNullOrEmpty()) fallback else value)
2687
- } catch (_: IllegalArgumentException) {
2688
- parseNativeListColor(fallback)
2689
- }
2690
- }
2691
- }
2692
-
2693
- internal fun isSimpleStickySectionHeader(item: NativeListItem): Boolean =
2694
- item.type == "sectionHeader" &&
2695
- item.json.optBoolean("sticky", true) &&
2696
- item.json.optString("variant") != "summary" &&
2697
- item.json.optString("value").isEmpty() &&
2698
- item.json.optJSONObject("checkbox") == null
2760
+ internal fun isStickySectionHeader(item: NativeListItem): Boolean =
2761
+ item.type == "sectionHeader" && item.json.optBoolean("sticky", true) && item.json.optString("variant") != "summary"
2699
2762
 
2700
2763
  // OneKey patch: paint only explicitly requested backgrounds into list side padding.
2701
2764
  private class SelectorBackgroundDecoration(private val adapter: NativeListAdapter) : RecyclerView.ItemDecoration() {
@@ -2705,9 +2768,16 @@ private class SelectorBackgroundDecoration(private val adapter: NativeListAdapte
2705
2768
  val child = parent.getChildAt(index)
2706
2769
  val item = adapter.itemAt(parent.getChildAdapterPosition(child)) ?: continue
2707
2770
  if (!item.json.optBoolean("backgroundFullWidth", false)) continue
2708
- val color = item.json.optString("backgroundColor")
2709
- if (color.isEmpty()) continue
2771
+ val color = resolveNativeListSelectorBackgroundColor(
2772
+ rowType = item.type,
2773
+ styleBackgroundColor = item.json.optJSONObject("style")
2774
+ ?.optJSONObject("container")
2775
+ ?.optString("backgroundColor"),
2776
+ rowBackgroundColor = item.json.optString("backgroundColor"),
2777
+ ) ?: continue
2710
2778
  paint.color = try { parseNativeListColor(color) } catch (_: IllegalArgumentException) { Color.TRANSPARENT }
2779
+ val opacity = item.json.optJSONObject("style")?.optJSONObject("container")?.optDouble("opacity", item.json.optDouble("opacity", 1.0)) ?: item.json.optDouble("opacity", 1.0)
2780
+ paint.alpha = (Color.alpha(paint.color) * opacity * (if (item.json.optBoolean("disabled", false)) 0.5 else 1.0)).toInt()
2711
2781
  val top = child.y
2712
2782
  canvas.drawRect(0f, top, parent.width.toFloat(), top + child.height, paint)
2713
2783
  if (item.type == "system" && item.json.optString("variant") == "warning") {