@onekeyfe/react-native-native-list 3.0.113 → 3.0.115
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/build.gradle +1 -0
- package/android/src/main/java/com/onekey/nativelist/NativeListRowView.kt +8 -2
- package/android/src/main/java/com/onekey/nativelist/NativeListView.kt +378 -88
- package/ios/NativeListCell.swift +2 -0
- package/ios/RNCNativeListView.swift +186 -35
- package/lib/module/web/NativeListWebEngine.js +104 -46
- package/lib/typescript/src/web/NativeListWebEngine.d.ts +11 -2
- package/package.json +4 -2
- package/src/web/NativeListWebEngine.ts +190 -89
|
@@ -4,11 +4,14 @@ import android.animation.TimeInterpolator
|
|
|
4
4
|
import android.graphics.Canvas
|
|
5
5
|
import android.graphics.Color
|
|
6
6
|
import android.graphics.Paint
|
|
7
|
+
import android.graphics.Path
|
|
7
8
|
import android.graphics.RectF
|
|
8
|
-
import android.graphics.drawable.
|
|
9
|
+
import android.graphics.drawable.ShapeDrawable
|
|
10
|
+
import android.graphics.drawable.shapes.PathShape
|
|
9
11
|
import android.os.Bundle
|
|
10
12
|
import android.os.Handler
|
|
11
13
|
import android.os.Looper
|
|
14
|
+
import android.os.SystemClock
|
|
12
15
|
import android.view.HapticFeedbackConstants
|
|
13
16
|
import android.view.Choreographer
|
|
14
17
|
import android.view.Gravity
|
|
@@ -30,6 +33,7 @@ import androidx.recyclerview.widget.LinearSmoothScroller
|
|
|
30
33
|
import androidx.recyclerview.widget.RecyclerView
|
|
31
34
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
32
35
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
36
|
+
import com.margelo.nitro.nativelogger.OneKeyLog
|
|
33
37
|
import org.json.JSONArray
|
|
34
38
|
import org.json.JSONObject
|
|
35
39
|
import java.lang.ref.WeakReference
|
|
@@ -150,6 +154,9 @@ class NativeListView(
|
|
|
150
154
|
private var dragFrom = RecyclerView.NO_POSITION
|
|
151
155
|
private var dragTo = RecyclerView.NO_POSITION
|
|
152
156
|
private var pendingReorder: List<NativeListItem>? = null
|
|
157
|
+
private var reorderRelayoutActive = false
|
|
158
|
+
private var reorderRelayoutScheduled = false
|
|
159
|
+
private var lastReorderRelayoutLogAtMs = 0L
|
|
153
160
|
private var sectionIndexEntries: List<NativeListSectionIndexEntry> = emptyList()
|
|
154
161
|
private var sectionIndexScrubbing = false
|
|
155
162
|
private var sectionIndexProgrammaticScroll = false
|
|
@@ -198,27 +205,28 @@ class NativeListView(
|
|
|
198
205
|
contentContainer.addView(
|
|
199
206
|
sectionIndexView,
|
|
200
207
|
FrameLayout.LayoutParams(
|
|
201
|
-
|
|
208
|
+
sectionIndexDp(SECTION_INDEX_RAIL_WIDTH_DP),
|
|
202
209
|
FrameLayout.LayoutParams.MATCH_PARENT,
|
|
203
210
|
Gravity.END,
|
|
204
211
|
),
|
|
205
212
|
)
|
|
206
213
|
sectionIndexPreview.apply {
|
|
207
214
|
gravity = Gravity.CENTER
|
|
208
|
-
textSize =
|
|
209
|
-
typeface = NativeListFonts.
|
|
210
|
-
|
|
215
|
+
textSize = 30f
|
|
216
|
+
typeface = NativeListFonts.regular(context)
|
|
217
|
+
setPadding(0, 0, sectionIndexDp(10), 0)
|
|
218
|
+
visibility = INVISIBLE
|
|
211
219
|
alpha = 0f
|
|
212
220
|
importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
213
221
|
}
|
|
214
222
|
contentContainer.addView(
|
|
215
223
|
sectionIndexPreview,
|
|
216
224
|
FrameLayout.LayoutParams(
|
|
217
|
-
|
|
218
|
-
|
|
225
|
+
sectionIndexDp(SECTION_INDEX_PREVIEW_WIDTH_DP),
|
|
226
|
+
sectionIndexDp(SECTION_INDEX_PREVIEW_HEIGHT_DP),
|
|
219
227
|
Gravity.CENTER_VERTICAL or Gravity.END,
|
|
220
228
|
).apply {
|
|
221
|
-
marginEnd =
|
|
229
|
+
marginEnd = sectionIndexDp(SECTION_INDEX_PREVIEW_END_MARGIN_DP)
|
|
222
230
|
},
|
|
223
231
|
)
|
|
224
232
|
addView(contentContainer, LayoutParams(LayoutParams.MATCH_PARENT, 0, 1f))
|
|
@@ -517,9 +525,13 @@ class NativeListView(
|
|
|
517
525
|
}
|
|
518
526
|
val nextItems = current.items.toMutableList()
|
|
519
527
|
val selected = LinkedHashSet(current.selectedKeys)
|
|
528
|
+
var marketQuoteOnly = pending.isNotEmpty()
|
|
520
529
|
try {
|
|
521
530
|
pending.forEach { (index, changes) ->
|
|
522
531
|
val previous = nextItems[index]
|
|
532
|
+
marketQuoteOnly = marketQuoteOnly &&
|
|
533
|
+
previous.type == "market" &&
|
|
534
|
+
changes.keys().asSequence().all(MARKET_QUOTE_FIELDS::contains)
|
|
523
535
|
val merged = NativeListItem.parse(mergeRow(previous.json, changes))
|
|
524
536
|
nextItems[index] = merged
|
|
525
537
|
if (changes.has("selected")) {
|
|
@@ -529,7 +541,7 @@ class NativeListView(
|
|
|
529
541
|
} catch (_: Exception) {
|
|
530
542
|
return
|
|
531
543
|
}
|
|
532
|
-
invalidateActionAnchor("snapshot")
|
|
544
|
+
if (!marketQuoteOnly) invalidateActionAnchor("snapshot")
|
|
533
545
|
val next = current.copy(items = nextItems, selectedKeys = selected)
|
|
534
546
|
config = next
|
|
535
547
|
usesSelectorSourceScale = next.items.any { it.usesSelectorSourceScale }
|
|
@@ -860,6 +872,7 @@ class NativeListView(
|
|
|
860
872
|
|
|
861
873
|
fun dispose() {
|
|
862
874
|
if (disposed) return
|
|
875
|
+
stopReorderRelayoutLoop()
|
|
863
876
|
invalidateActionAnchor("destroy")
|
|
864
877
|
actionAnchor = null
|
|
865
878
|
disposed = true
|
|
@@ -930,17 +943,14 @@ class NativeListView(
|
|
|
930
943
|
sectionIndexHapticsEnabled = next.sectionIndexHapticsEnabled
|
|
931
944
|
sectionIndexView.configure(
|
|
932
945
|
sectionIndexEntries.map { it.title },
|
|
933
|
-
themeColor(next.theme, "
|
|
934
|
-
themeColor(next.theme, "
|
|
946
|
+
themeColor(next.theme, "disabledText", "#8D8D8D"),
|
|
947
|
+
themeColor(next.theme, "positive", "#218358"),
|
|
935
948
|
themeColor(next.theme, "inverseText", "#FCFCFC"),
|
|
936
949
|
next.sectionIndexCenteredInWindow,
|
|
937
950
|
)
|
|
938
951
|
sectionIndexView.visibility = if (sectionIndexEntries.isEmpty()) GONE else VISIBLE
|
|
939
|
-
sectionIndexPreview.setTextColor(
|
|
940
|
-
sectionIndexPreview.background =
|
|
941
|
-
setColor(themeColor(next.theme, "inverseBackground", "#202020"))
|
|
942
|
-
cornerRadius = dp(14).toFloat()
|
|
943
|
-
}
|
|
952
|
+
sectionIndexPreview.setTextColor(Color.WHITE)
|
|
953
|
+
sectionIndexPreview.background = sectionIndexPreviewBackground()
|
|
944
954
|
sectionIndexView.setActiveIndex(
|
|
945
955
|
previousKey?.let { key -> sectionIndexEntries.indexOfFirst { it.key == key }.takeIf { it >= 0 } },
|
|
946
956
|
)
|
|
@@ -961,6 +971,7 @@ class NativeListView(
|
|
|
961
971
|
if (interacting) {
|
|
962
972
|
sectionIndexPreview.animate().cancel()
|
|
963
973
|
sectionIndexPreview.text = entry.title
|
|
974
|
+
positionSectionIndexPreview(index)
|
|
964
975
|
sectionIndexPreview.visibility = VISIBLE
|
|
965
976
|
sectionIndexPreview.alpha = 1f
|
|
966
977
|
if (changed && sectionIndexHapticsEnabled) {
|
|
@@ -976,16 +987,41 @@ class NativeListView(
|
|
|
976
987
|
sectionIndexPreview.animate().cancel()
|
|
977
988
|
if (immediately) {
|
|
978
989
|
sectionIndexPreview.alpha = 0f
|
|
979
|
-
sectionIndexPreview.visibility =
|
|
990
|
+
sectionIndexPreview.visibility = INVISIBLE
|
|
980
991
|
} else {
|
|
981
992
|
sectionIndexPreview.animate()
|
|
982
993
|
.alpha(0f)
|
|
983
994
|
.setDuration(150)
|
|
984
|
-
.withEndAction { sectionIndexPreview.visibility =
|
|
995
|
+
.withEndAction { sectionIndexPreview.visibility = INVISIBLE }
|
|
985
996
|
.start()
|
|
986
997
|
}
|
|
987
998
|
}
|
|
988
999
|
|
|
1000
|
+
private fun positionSectionIndexPreview(index: Int) {
|
|
1001
|
+
val halfHeight = sectionIndexDp(SECTION_INDEX_PREVIEW_HEIGHT_DP) / 2f
|
|
1002
|
+
val maximumY = (contentContainer.height - halfHeight).coerceAtLeast(halfHeight)
|
|
1003
|
+
val targetY = sectionIndexView.top + sectionIndexView.centerYForIndex(index)
|
|
1004
|
+
val clampedY = targetY.coerceIn(halfHeight, maximumY)
|
|
1005
|
+
sectionIndexPreview.translationY = clampedY - contentContainer.height / 2f
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
private fun sectionIndexPreviewBackground(): ShapeDrawable {
|
|
1009
|
+
val path = Path().apply {
|
|
1010
|
+
moveTo(25f, 0f)
|
|
1011
|
+
cubicTo(11f, 0f, 0f, 11f, 0f, 25f)
|
|
1012
|
+
cubicTo(0f, 39f, 11f, 50f, 25f, 50f)
|
|
1013
|
+
cubicTo(36f, 50f, 43f, 44f, 46f, 37.5f)
|
|
1014
|
+
lineTo(60f, 25f)
|
|
1015
|
+
lineTo(46f, 12.5f)
|
|
1016
|
+
cubicTo(43f, 6f, 36f, 0f, 25f, 0f)
|
|
1017
|
+
close()
|
|
1018
|
+
}
|
|
1019
|
+
return ShapeDrawable(PathShape(path, 60f, 50f)).apply {
|
|
1020
|
+
paint.color = Color.rgb(194, 194, 194)
|
|
1021
|
+
paint.isAntiAlias = true
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
989
1025
|
private fun syncSectionIndexToVisibleRows() {
|
|
990
1026
|
if (sectionIndexScrubbing || sectionIndexProgrammaticScroll || sectionIndexEntries.isEmpty()) return
|
|
991
1027
|
val firstVisible = layoutManager.findFirstVisibleItemPosition()
|
|
@@ -1235,6 +1271,7 @@ class NativeListView(
|
|
|
1235
1271
|
}
|
|
1236
1272
|
|
|
1237
1273
|
private fun updateReordering(next: NativeListConfig) {
|
|
1274
|
+
stopReorderRelayoutLoop()
|
|
1238
1275
|
reorderTouchHandler?.removeCallbacksAndMessages(null)
|
|
1239
1276
|
reorderTouchHandler = null
|
|
1240
1277
|
reorderTouchListener?.let(recyclerView::removeOnItemTouchListener)
|
|
@@ -1249,6 +1286,48 @@ class NativeListView(
|
|
|
1249
1286
|
) {
|
|
1250
1287
|
private var compactWalletGroupDrag = false
|
|
1251
1288
|
private var compactWalletGroupTop = Float.NaN
|
|
1289
|
+
private var dragType: String? = null
|
|
1290
|
+
private var dragStartedAtMs = 0L
|
|
1291
|
+
private var lastAutoScrollLogAtMs = 0L
|
|
1292
|
+
private var lastDragFrameLogAtMs = 0L
|
|
1293
|
+
private var lastMoveAttemptLogAtMs = 0L
|
|
1294
|
+
private var lastMoveAttemptSignature = ""
|
|
1295
|
+
private var latestDragDx = 0f
|
|
1296
|
+
private var latestDragDy = 0f
|
|
1297
|
+
|
|
1298
|
+
private fun logMoveAttempt(
|
|
1299
|
+
recyclerView: RecyclerView,
|
|
1300
|
+
from: Int,
|
|
1301
|
+
to: Int,
|
|
1302
|
+
fromItem: NativeListItem?,
|
|
1303
|
+
toItem: NativeListItem?,
|
|
1304
|
+
outcome: String,
|
|
1305
|
+
targetTop: Int,
|
|
1306
|
+
targetBottom: Int,
|
|
1307
|
+
) {
|
|
1308
|
+
val now = SystemClock.uptimeMillis()
|
|
1309
|
+
val signature = "$from:$to:$outcome"
|
|
1310
|
+
if (
|
|
1311
|
+
signature == lastMoveAttemptSignature &&
|
|
1312
|
+
now - lastMoveAttemptLogAtMs < REORDER_MOVE_ATTEMPT_LOG_INTERVAL_MS
|
|
1313
|
+
) {
|
|
1314
|
+
return
|
|
1315
|
+
}
|
|
1316
|
+
lastMoveAttemptSignature = signature
|
|
1317
|
+
lastMoveAttemptLogAtMs = now
|
|
1318
|
+
OneKeyLog.info(
|
|
1319
|
+
REORDER_LOG_TAG,
|
|
1320
|
+
"moveAttempt outcome=$outcome from=$from to=$to " +
|
|
1321
|
+
"fromType=${fromItem?.type.orEmpty()} toType=${toItem?.type.orEmpty()} " +
|
|
1322
|
+
"fromReorderable=${fromItem?.isReorderable} toReorderable=${toItem?.isReorderable} " +
|
|
1323
|
+
"sameSection=${fromItem != null && toItem != null && fromItem.sectionKey == toItem.sectionKey} " +
|
|
1324
|
+
"elapsedDragMs=${now - dragStartedAtMs} " +
|
|
1325
|
+
"scrollOffset=${recyclerView.computeVerticalScrollOffset()} " +
|
|
1326
|
+
"targetTop=$targetTop targetBottom=$targetBottom " +
|
|
1327
|
+
"viewportTop=${recyclerView.paddingTop} " +
|
|
1328
|
+
"viewportBottom=${recyclerView.height - recyclerView.paddingBottom}",
|
|
1329
|
+
)
|
|
1330
|
+
}
|
|
1252
1331
|
|
|
1253
1332
|
override fun isLongPressDragEnabled(): Boolean = false
|
|
1254
1333
|
|
|
@@ -1260,6 +1339,23 @@ class NativeListView(
|
|
|
1260
1339
|
reorderPlaceholderDecoration.position = position
|
|
1261
1340
|
reorderPlaceholderDecoration.color = reorderActiveBackground(next.theme)
|
|
1262
1341
|
compactWalletGroupDrag = item?.type == "walletGroup" && viewHolder != null
|
|
1342
|
+
dragType = item?.type
|
|
1343
|
+
dragStartedAtMs = SystemClock.uptimeMillis()
|
|
1344
|
+
lastAutoScrollLogAtMs = 0L
|
|
1345
|
+
lastDragFrameLogAtMs = 0L
|
|
1346
|
+
lastMoveAttemptLogAtMs = 0L
|
|
1347
|
+
lastMoveAttemptSignature = ""
|
|
1348
|
+
latestDragDx = 0f
|
|
1349
|
+
latestDragDy = 0f
|
|
1350
|
+
startReorderRelayoutLoop()
|
|
1351
|
+
OneKeyLog.info(
|
|
1352
|
+
REORDER_LOG_TAG,
|
|
1353
|
+
"start type=${dragType.orEmpty()} position=$position " +
|
|
1354
|
+
"compact=$compactWalletGroupDrag itemHeight=${viewHolder?.itemView?.height ?: -1} " +
|
|
1355
|
+
"viewport=${recyclerView.width}x${recyclerView.height} " +
|
|
1356
|
+
"padding=${recyclerView.paddingLeft},${recyclerView.paddingTop}," +
|
|
1357
|
+
"${recyclerView.paddingRight},${recyclerView.paddingBottom} density=$density",
|
|
1358
|
+
)
|
|
1263
1359
|
recyclerView.invalidate()
|
|
1264
1360
|
(viewHolder as? NativeListViewHolder)?.rowView?.setReorderActive(true)
|
|
1265
1361
|
if (compactWalletGroupDrag && viewHolder != null) {
|
|
@@ -1287,22 +1383,63 @@ class NativeListView(
|
|
|
1287
1383
|
val from = source.bindingAdapterPosition
|
|
1288
1384
|
val to = target.bindingAdapterPosition
|
|
1289
1385
|
val base = pendingReorder ?: adapter.currentList
|
|
1290
|
-
val fromItem = base.getOrNull(from)
|
|
1291
|
-
val toItem = base.getOrNull(to)
|
|
1292
|
-
|
|
1386
|
+
val fromItem = base.getOrNull(from)
|
|
1387
|
+
val toItem = base.getOrNull(to)
|
|
1388
|
+
val targetTop = layoutManager.getDecoratedTop(target.itemView)
|
|
1389
|
+
val targetBottom = layoutManager.getDecoratedBottom(target.itemView)
|
|
1390
|
+
val targetClipped =
|
|
1391
|
+
next.orientation != "horizontal" &&
|
|
1392
|
+
from != RecyclerView.NO_POSITION &&
|
|
1393
|
+
to != RecyclerView.NO_POSITION &&
|
|
1394
|
+
when {
|
|
1395
|
+
to > from -> targetBottom > recyclerView.height - recyclerView.paddingBottom
|
|
1396
|
+
to < from -> targetTop < recyclerView.paddingTop
|
|
1397
|
+
else -> false
|
|
1398
|
+
}
|
|
1399
|
+
val outcome = when {
|
|
1400
|
+
fromItem == null -> "missingSource"
|
|
1401
|
+
toItem == null -> "missingTarget"
|
|
1402
|
+
!fromItem.isReorderable -> "sourceNotReorderable"
|
|
1403
|
+
!toItem.isReorderable -> "targetNotReorderable"
|
|
1404
|
+
fromItem.sectionKey != toItem.sectionKey -> "sectionMismatch"
|
|
1405
|
+
targetClipped -> "targetClipped"
|
|
1406
|
+
else -> "accepted"
|
|
1407
|
+
}
|
|
1408
|
+
logMoveAttempt(recyclerView, from, to, fromItem, toItem, outcome, targetTop, targetBottom)
|
|
1409
|
+
if (outcome != "accepted" || fromItem == null || toItem == null) return false
|
|
1293
1410
|
val crossedPosition = dragTo != to
|
|
1294
1411
|
if (dragFrom == RecyclerView.NO_POSITION) dragFrom = from
|
|
1295
1412
|
dragTo = to
|
|
1296
1413
|
if (crossedPosition) {
|
|
1297
1414
|
recyclerView.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK)
|
|
1415
|
+
OneKeyLog.info(
|
|
1416
|
+
REORDER_LOG_TAG,
|
|
1417
|
+
"move type=${dragType.orEmpty()} from=$from to=$to " +
|
|
1418
|
+
"scrollOffset=${recyclerView.computeVerticalScrollOffset()} " +
|
|
1419
|
+
"dragDx=$latestDragDx dragDy=$latestDragDy",
|
|
1420
|
+
)
|
|
1298
1421
|
}
|
|
1299
1422
|
val displacedView = target.itemView
|
|
1300
1423
|
prepareDisplacedReorderAnimation(displacedView)
|
|
1301
|
-
val reordered = adapter.moveReordered(from, to)
|
|
1424
|
+
val reordered = adapter.moveReordered(from, to)
|
|
1425
|
+
if (reordered == null) {
|
|
1426
|
+
logMoveAttempt(
|
|
1427
|
+
recyclerView,
|
|
1428
|
+
from,
|
|
1429
|
+
to,
|
|
1430
|
+
fromItem,
|
|
1431
|
+
toItem,
|
|
1432
|
+
"adapterRejected",
|
|
1433
|
+
targetTop,
|
|
1434
|
+
targetBottom,
|
|
1435
|
+
)
|
|
1436
|
+
return false
|
|
1437
|
+
}
|
|
1302
1438
|
pendingReorder = reordered
|
|
1303
1439
|
reorderPlaceholderDecoration.position = to
|
|
1304
1440
|
recyclerView.invalidate()
|
|
1305
|
-
|
|
1441
|
+
// OneKey patch: the drag-scoped loop handles deferred nested RecyclerView layouts.
|
|
1442
|
+
// recyclerView.postOnAnimation(::relayoutRecyclerViewImmediately)
|
|
1306
1443
|
return true
|
|
1307
1444
|
}
|
|
1308
1445
|
|
|
@@ -1318,11 +1455,40 @@ class NativeListView(
|
|
|
1318
1455
|
isCurrentlyActive: Boolean,
|
|
1319
1456
|
) {
|
|
1320
1457
|
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
|
|
1458
|
+
latestDragDx = dX
|
|
1459
|
+
latestDragDy = dY
|
|
1321
1460
|
if (compactWalletGroupDrag) {
|
|
1322
1461
|
compactWalletGroupTop = viewHolder.itemView.top + dY
|
|
1323
1462
|
}
|
|
1324
1463
|
}
|
|
1325
1464
|
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive)
|
|
1465
|
+
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
|
|
1466
|
+
val now = SystemClock.uptimeMillis()
|
|
1467
|
+
if (
|
|
1468
|
+
lastDragFrameLogAtMs == 0L ||
|
|
1469
|
+
now - lastDragFrameLogAtMs >= REORDER_DRAG_FRAME_LOG_INTERVAL_MS
|
|
1470
|
+
) {
|
|
1471
|
+
lastDragFrameLogAtMs = now
|
|
1472
|
+
val itemView = viewHolder.itemView
|
|
1473
|
+
val visualTop = itemView.top + dY
|
|
1474
|
+
val visualBottom = itemView.bottom + dY
|
|
1475
|
+
OneKeyLog.info(
|
|
1476
|
+
REORDER_LOG_TAG,
|
|
1477
|
+
"frame position=${viewHolder.bindingAdapterPosition} active=$isCurrentlyActive " +
|
|
1478
|
+
"dragDx=$dX dragDy=$dY itemTop=${itemView.top} itemBottom=${itemView.bottom} " +
|
|
1479
|
+
"translationY=${itemView.translationY} visualTop=$visualTop visualBottom=$visualBottom " +
|
|
1480
|
+
"viewportTop=${recyclerView.paddingTop} " +
|
|
1481
|
+
"viewportBottom=${recyclerView.height - recyclerView.paddingBottom} " +
|
|
1482
|
+
"firstVisible=${layoutManager.findFirstVisibleItemPosition()} " +
|
|
1483
|
+
"lastVisible=${layoutManager.findLastVisibleItemPosition()} " +
|
|
1484
|
+
"canScrollUp=${recyclerView.canScrollVertically(-1)} " +
|
|
1485
|
+
"canScrollDown=${recyclerView.canScrollVertically(1)} " +
|
|
1486
|
+
"scrollOffset=${recyclerView.computeVerticalScrollOffset()} " +
|
|
1487
|
+
"layoutRequested=${recyclerView.isLayoutRequested} " +
|
|
1488
|
+
"computingLayout=${recyclerView.isComputingLayout}",
|
|
1489
|
+
)
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1326
1492
|
}
|
|
1327
1493
|
|
|
1328
1494
|
override fun interpolateOutOfBoundsScroll(
|
|
@@ -1332,35 +1498,60 @@ class NativeListView(
|
|
|
1332
1498
|
totalSize: Int,
|
|
1333
1499
|
msSinceStartScroll: Long,
|
|
1334
1500
|
): Int {
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
else -> 0
|
|
1501
|
+
val effectiveViewSize: Int
|
|
1502
|
+
val effectiveOutOfBounds: Int
|
|
1503
|
+
if (compactWalletGroupDrag) {
|
|
1504
|
+
if (compactWalletGroupTop.isNaN()) return 0
|
|
1505
|
+
effectiveViewSize = dp(68)
|
|
1506
|
+
val compactTop = compactWalletGroupTop.roundToInt()
|
|
1507
|
+
effectiveOutOfBounds = when {
|
|
1508
|
+
compactTop < recyclerView.paddingTop -> compactTop - recyclerView.paddingTop
|
|
1509
|
+
compactTop + effectiveViewSize > recyclerView.height - recyclerView.paddingBottom ->
|
|
1510
|
+
compactTop + effectiveViewSize - (recyclerView.height - recyclerView.paddingBottom)
|
|
1511
|
+
else -> 0
|
|
1512
|
+
}
|
|
1513
|
+
if (effectiveOutOfBounds == 0) return 0
|
|
1514
|
+
} else {
|
|
1515
|
+
effectiveViewSize = viewSize
|
|
1516
|
+
effectiveOutOfBounds = viewSizeOutOfBounds
|
|
1352
1517
|
}
|
|
1353
|
-
|
|
1354
|
-
|
|
1518
|
+
// OneKey patch: warm-start AndroidX's two-second quintic edge-scroll ramp.
|
|
1519
|
+
val acceleratedElapsedOutMs =
|
|
1520
|
+
msSinceStartScroll + REORDER_AUTOSCROLL_ACCELERATION_OFFSET_MS
|
|
1521
|
+
val result = super.interpolateOutOfBoundsScroll(
|
|
1355
1522
|
recyclerView,
|
|
1356
|
-
|
|
1357
|
-
|
|
1523
|
+
effectiveViewSize,
|
|
1524
|
+
effectiveOutOfBounds,
|
|
1358
1525
|
totalSize,
|
|
1359
|
-
msSinceStartScroll,
|
|
1526
|
+
// msSinceStartScroll,
|
|
1527
|
+
acceleratedElapsedOutMs,
|
|
1360
1528
|
)
|
|
1529
|
+
val now = SystemClock.uptimeMillis()
|
|
1530
|
+
if (
|
|
1531
|
+
lastAutoScrollLogAtMs == 0L ||
|
|
1532
|
+
now - lastAutoScrollLogAtMs >= REORDER_AUTOSCROLL_LOG_INTERVAL_MS
|
|
1533
|
+
) {
|
|
1534
|
+
lastAutoScrollLogAtMs = now
|
|
1535
|
+
OneKeyLog.info(
|
|
1536
|
+
REORDER_LOG_TAG,
|
|
1537
|
+
"autoScroll type=${dragType.orEmpty()} " +
|
|
1538
|
+
"compact=$compactWalletGroupDrag rawViewSize=$viewSize " +
|
|
1539
|
+
"effectiveViewSize=$effectiveViewSize rawOut=$viewSizeOutOfBounds " +
|
|
1540
|
+
"effectiveOut=$effectiveOutOfBounds totalSize=$totalSize " +
|
|
1541
|
+
"elapsedOutMs=$msSinceStartScroll " +
|
|
1542
|
+
"acceleratedElapsedOutMs=$acceleratedElapsedOutMs " +
|
|
1543
|
+
"elapsedDragMs=${now - dragStartedAtMs} " +
|
|
1544
|
+
"resultPx=$result scrollOffset=${recyclerView.computeVerticalScrollOffset()} " +
|
|
1545
|
+
"canScrollUp=${recyclerView.canScrollVertically(-1)} " +
|
|
1546
|
+
"canScrollDown=${recyclerView.canScrollVertically(1)} " +
|
|
1547
|
+
"dragDx=$latestDragDx dragDy=$latestDragDy compactTop=$compactWalletGroupTop",
|
|
1548
|
+
)
|
|
1549
|
+
}
|
|
1550
|
+
return result
|
|
1361
1551
|
}
|
|
1362
1552
|
|
|
1363
1553
|
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
|
|
1554
|
+
stopReorderRelayoutLoop()
|
|
1364
1555
|
val rowView = (viewHolder as? NativeListViewHolder)?.rowView
|
|
1365
1556
|
val draggedGroupKey = (rowView?.tag as? NativeListItem)?.key
|
|
1366
1557
|
super.clearView(recyclerView, viewHolder)
|
|
@@ -1373,6 +1564,18 @@ class NativeListView(
|
|
|
1373
1564
|
val from = dragFrom
|
|
1374
1565
|
val to = dragTo
|
|
1375
1566
|
val reordered = pendingReorder
|
|
1567
|
+
OneKeyLog.info(
|
|
1568
|
+
REORDER_LOG_TAG,
|
|
1569
|
+
"end type=${dragType.orEmpty()} from=$from to=$to " +
|
|
1570
|
+
"compact=$wasCompactWalletGroupDrag elapsedDragMs=" +
|
|
1571
|
+
"${SystemClock.uptimeMillis() - dragStartedAtMs} " +
|
|
1572
|
+
"scrollOffset=${recyclerView.computeVerticalScrollOffset()} " +
|
|
1573
|
+
"dragDx=$latestDragDx dragDy=$latestDragDy " +
|
|
1574
|
+
"firstVisible=${layoutManager.findFirstVisibleItemPosition()} " +
|
|
1575
|
+
"lastVisible=${layoutManager.findLastVisibleItemPosition()} " +
|
|
1576
|
+
"canScrollUp=${recyclerView.canScrollVertically(-1)} " +
|
|
1577
|
+
"canScrollDown=${recyclerView.canScrollVertically(1)}",
|
|
1578
|
+
)
|
|
1376
1579
|
var reorderPayload: JSONObject? = null
|
|
1377
1580
|
val destinationPosition = if (to != RecyclerView.NO_POSITION) {
|
|
1378
1581
|
to
|
|
@@ -1428,6 +1631,7 @@ class NativeListView(
|
|
|
1428
1631
|
dragFrom = RecyclerView.NO_POSITION
|
|
1429
1632
|
dragTo = RecyclerView.NO_POSITION
|
|
1430
1633
|
pendingReorder = null
|
|
1634
|
+
dragType = null
|
|
1431
1635
|
// The gesture is committed now; a later snapshot may supersede its async diff.
|
|
1432
1636
|
reorderPayload?.let { emit(REORDER, it) }
|
|
1433
1637
|
}
|
|
@@ -1624,6 +1828,53 @@ class NativeListView(
|
|
|
1624
1828
|
}
|
|
1625
1829
|
}
|
|
1626
1830
|
|
|
1831
|
+
private fun startReorderRelayoutLoop() {
|
|
1832
|
+
reorderRelayoutActive = true
|
|
1833
|
+
lastReorderRelayoutLogAtMs = 0L
|
|
1834
|
+
scheduleReorderRelayout()
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
private fun stopReorderRelayoutLoop() {
|
|
1838
|
+
reorderRelayoutActive = false
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
private fun scheduleReorderRelayout() {
|
|
1842
|
+
if (!reorderRelayoutActive || reorderRelayoutScheduled) return
|
|
1843
|
+
reorderRelayoutScheduled = true
|
|
1844
|
+
recyclerView.postOnAnimation {
|
|
1845
|
+
reorderRelayoutScheduled = false
|
|
1846
|
+
if (
|
|
1847
|
+
!reorderRelayoutActive ||
|
|
1848
|
+
disposed ||
|
|
1849
|
+
recyclerView.width <= 0 ||
|
|
1850
|
+
recyclerView.height <= 0
|
|
1851
|
+
) {
|
|
1852
|
+
return@postOnAnimation
|
|
1853
|
+
}
|
|
1854
|
+
val requestedBefore = recyclerView.isLayoutRequested
|
|
1855
|
+
val computingBefore = recyclerView.isComputingLayout
|
|
1856
|
+
if (requestedBefore && !computingBefore) {
|
|
1857
|
+
relayoutRecyclerViewImmediately()
|
|
1858
|
+
}
|
|
1859
|
+
val requestedAfter = recyclerView.isLayoutRequested
|
|
1860
|
+
val now = SystemClock.uptimeMillis()
|
|
1861
|
+
if (
|
|
1862
|
+
(requestedBefore || computingBefore || requestedAfter) &&
|
|
1863
|
+
(lastReorderRelayoutLogAtMs == 0L ||
|
|
1864
|
+
now - lastReorderRelayoutLogAtMs >= REORDER_RELAYOUT_LOG_INTERVAL_MS)
|
|
1865
|
+
) {
|
|
1866
|
+
lastReorderRelayoutLogAtMs = now
|
|
1867
|
+
OneKeyLog.info(
|
|
1868
|
+
REORDER_LOG_TAG,
|
|
1869
|
+
"relayout requestedBefore=$requestedBefore computingBefore=$computingBefore " +
|
|
1870
|
+
"requestedAfter=$requestedAfter computingAfter=${recyclerView.isComputingLayout} " +
|
|
1871
|
+
"scrollOffset=${recyclerView.computeVerticalScrollOffset()}",
|
|
1872
|
+
)
|
|
1873
|
+
}
|
|
1874
|
+
scheduleReorderRelayout()
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1627
1878
|
private fun relayoutRecyclerViewImmediately() {
|
|
1628
1879
|
if (disposed || recyclerView.isComputingLayout || recyclerView.width <= 0 || recyclerView.height <= 0) {
|
|
1629
1880
|
return
|
|
@@ -1643,10 +1894,20 @@ class NativeListView(
|
|
|
1643
1894
|
|
|
1644
1895
|
private fun dp(value: Int): Int = if (usesSelectorSourceScale) (value * resources.displayMetrics.density).roundToInt() else NativeListScale.dp(resources, value)
|
|
1645
1896
|
|
|
1897
|
+
private fun sectionIndexDp(value: Int): Int = (value * density).roundToInt()
|
|
1898
|
+
|
|
1646
1899
|
companion object {
|
|
1900
|
+
private val MARKET_QUOTE_FIELDS = setOf(
|
|
1901
|
+
"revision",
|
|
1902
|
+
"price",
|
|
1903
|
+
"priceSegments",
|
|
1904
|
+
"change",
|
|
1905
|
+
"accessibilityLabel",
|
|
1906
|
+
)
|
|
1647
1907
|
private const val SECTION_INDEX_CONTENT_INSET_DP = 16
|
|
1648
1908
|
private const val SECTION_INDEX_RAIL_WIDTH_DP = 32
|
|
1649
|
-
private const val
|
|
1909
|
+
private const val SECTION_INDEX_PREVIEW_WIDTH_DP = 60
|
|
1910
|
+
private const val SECTION_INDEX_PREVIEW_HEIGHT_DP = 50
|
|
1650
1911
|
private const val SECTION_INDEX_PREVIEW_END_MARGIN_DP = 40
|
|
1651
1912
|
private const val REORDER_LONG_PRESS_MS = 200L
|
|
1652
1913
|
private const val REORDER_ALLOWABLE_MOVEMENT_DP = 10
|
|
@@ -1656,6 +1917,12 @@ class NativeListView(
|
|
|
1656
1917
|
private const val REORDER_SPRING_STIFFNESS = 400.0
|
|
1657
1918
|
private const val REORDER_SPRING_MASS = 0.4
|
|
1658
1919
|
private const val REORDER_SPRING_DURATION_MS = 300L
|
|
1920
|
+
private const val REORDER_AUTOSCROLL_ACCELERATION_OFFSET_MS = 1_500L
|
|
1921
|
+
private const val REORDER_AUTOSCROLL_LOG_INTERVAL_MS = 100L
|
|
1922
|
+
private const val REORDER_DRAG_FRAME_LOG_INTERVAL_MS = 100L
|
|
1923
|
+
private const val REORDER_MOVE_ATTEMPT_LOG_INTERVAL_MS = 250L
|
|
1924
|
+
private const val REORDER_RELAYOUT_LOG_INTERVAL_MS = 100L
|
|
1925
|
+
private const val REORDER_LOG_TAG = "NativeListReorder"
|
|
1659
1926
|
private const val ROW_ACTION = "rowAction"
|
|
1660
1927
|
private const val ACTION_ANCHOR_INVALIDATED = "actionAnchorInvalidated"
|
|
1661
1928
|
private const val SELECTION_DELTA = "selectionDelta"
|
|
@@ -1739,11 +2006,11 @@ private class NativeListSectionIndexView(
|
|
|
1739
2006
|
private val activeBackgroundPaint = Paint(Paint.ANTI_ALIAS_FLAG)
|
|
1740
2007
|
private val normalPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
1741
2008
|
textAlign = Paint.Align.CENTER
|
|
1742
|
-
typeface = NativeListFonts.
|
|
2009
|
+
typeface = NativeListFonts.regular(context)
|
|
1743
2010
|
}
|
|
1744
2011
|
private val activePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
|
1745
2012
|
textAlign = Paint.Align.CENTER
|
|
1746
|
-
typeface = NativeListFonts.
|
|
2013
|
+
typeface = NativeListFonts.medium(context)
|
|
1747
2014
|
}
|
|
1748
2015
|
|
|
1749
2016
|
init {
|
|
@@ -1780,40 +2047,27 @@ private class NativeListSectionIndexView(
|
|
|
1780
2047
|
override fun onDraw(canvas: Canvas) {
|
|
1781
2048
|
super.onDraw(canvas)
|
|
1782
2049
|
if (titles.isEmpty()) return
|
|
1783
|
-
val
|
|
1784
|
-
val
|
|
1785
|
-
val textSize =
|
|
2050
|
+
val metrics = indexMetrics()
|
|
2051
|
+
val visibleIndices = visibleLabelIndices(metrics.trackHeight)
|
|
2052
|
+
val textSize = 10f * resources.displayMetrics.scaledDensity
|
|
2053
|
+
val centerX = width - dp(10f)
|
|
2054
|
+
val badgeRadius = dp(7f)
|
|
1786
2055
|
normalPaint.color = normalColor
|
|
1787
2056
|
normalPaint.textSize = textSize
|
|
1788
2057
|
activePaint.color = activeColor
|
|
1789
2058
|
activePaint.textSize = textSize
|
|
1790
|
-
|
|
2059
|
+
visibleIndices.forEachIndexed { visibleIndex, index ->
|
|
2060
|
+
val title = titles[index]
|
|
1791
2061
|
val active = index == activeIndex
|
|
1792
2062
|
val paint = if (active) activePaint else normalPaint
|
|
1793
|
-
val centerY =
|
|
1794
|
-
if (active
|
|
1795
|
-
val badgeWidth = NativeListScale.dp(resources, 20f)
|
|
1796
|
-
val badgeHeight = minOf(cellHeight, NativeListScale.dp(resources, 16f))
|
|
2063
|
+
val centerY = visibleLabelCenterY(visibleIndex, visibleIndices.size, metrics)
|
|
2064
|
+
if (active) {
|
|
1797
2065
|
activeBackgroundPaint.color = activeColor
|
|
1798
|
-
canvas.
|
|
1799
|
-
width / 2f - badgeWidth / 2f,
|
|
1800
|
-
centerY - badgeHeight / 2f,
|
|
1801
|
-
width / 2f + badgeWidth / 2f,
|
|
1802
|
-
centerY + badgeHeight / 2f,
|
|
1803
|
-
badgeHeight / 2f,
|
|
1804
|
-
badgeHeight / 2f,
|
|
1805
|
-
activeBackgroundPaint,
|
|
1806
|
-
)
|
|
1807
|
-
}
|
|
1808
|
-
paint.color = if (active && cellHeight >= NativeListScale.dp(resources, 12f)) {
|
|
1809
|
-
activeTextColor
|
|
1810
|
-
} else if (active) {
|
|
1811
|
-
activeColor
|
|
1812
|
-
} else {
|
|
1813
|
-
normalColor
|
|
2066
|
+
canvas.drawCircle(centerX, centerY, badgeRadius, activeBackgroundPaint)
|
|
1814
2067
|
}
|
|
2068
|
+
paint.color = if (active) activeTextColor else normalColor
|
|
1815
2069
|
val baseline = centerY - (paint.descent() + paint.ascent()) / 2f
|
|
1816
|
-
canvas.drawText(title,
|
|
2070
|
+
canvas.drawText(title, centerX, baseline, paint)
|
|
1817
2071
|
}
|
|
1818
2072
|
}
|
|
1819
2073
|
|
|
@@ -1888,9 +2142,14 @@ private class NativeListSectionIndexView(
|
|
|
1888
2142
|
}
|
|
1889
2143
|
|
|
1890
2144
|
private fun selectAt(y: Float, interacting: Boolean) {
|
|
1891
|
-
val
|
|
1892
|
-
|
|
1893
|
-
val
|
|
2145
|
+
val metrics = indexMetrics()
|
|
2146
|
+
if (metrics.trackHeight <= 0f) return
|
|
2147
|
+
val visibleIndices = visibleLabelIndices(metrics.trackHeight).sorted()
|
|
2148
|
+
val visibleTrackHeight = minOf(metrics.trackHeight, dp(16f) * visibleIndices.size)
|
|
2149
|
+
val visibleOriginY = metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2f
|
|
2150
|
+
val progress = ((y - visibleOriginY) / visibleTrackHeight).coerceIn(0f, 1f)
|
|
2151
|
+
val slot = (progress * visibleIndices.size).toInt().coerceIn(0, visibleIndices.lastIndex)
|
|
2152
|
+
val index = visibleIndices[slot]
|
|
1894
2153
|
if (interacting && lastTouchIndex == index) return
|
|
1895
2154
|
lastTouchIndex = index.takeIf { interacting }
|
|
1896
2155
|
select(index, interacting)
|
|
@@ -1901,29 +2160,60 @@ private class NativeListSectionIndexView(
|
|
|
1901
2160
|
setActiveIndex(index)
|
|
1902
2161
|
}
|
|
1903
2162
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
.coerceAtLeast(1f)
|
|
2163
|
+
fun centerYForIndex(index: Int): Float = entryCenterY(index, indexMetrics())
|
|
2164
|
+
|
|
2165
|
+
private data class Metrics(val originY: Float, val trackHeight: Float)
|
|
1908
2166
|
|
|
1909
|
-
private fun
|
|
1910
|
-
|
|
1911
|
-
val
|
|
1912
|
-
|
|
2167
|
+
private fun indexMetrics(): Metrics {
|
|
2168
|
+
if (titles.isEmpty()) return Metrics(height / 2f, 0f)
|
|
2169
|
+
val edgePadding = dp(8f)
|
|
2170
|
+
val labelSpacing = dp(16f)
|
|
2171
|
+
val availableHeight = (height - edgePadding * 2f).coerceAtLeast(0f)
|
|
2172
|
+
val trackHeight = minOf(availableHeight, labelSpacing * titles.size)
|
|
2173
|
+
val centeredOriginY = (height - trackHeight) / 2f
|
|
2174
|
+
if (!centeredInWindow || !isAttachedToWindow) {
|
|
2175
|
+
return Metrics(centeredOriginY, trackHeight)
|
|
2176
|
+
}
|
|
1913
2177
|
val systemBarInsets = ViewCompat.getRootWindowInsets(rootView)
|
|
1914
2178
|
?.getInsetsIgnoringVisibility(
|
|
1915
2179
|
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout(),
|
|
1916
|
-
) ?: return centeredOriginY
|
|
2180
|
+
) ?: return Metrics(centeredOriginY, trackHeight)
|
|
1917
2181
|
rootView.getLocationOnScreen(rootLocationOnScreen)
|
|
1918
2182
|
getLocationOnScreen(locationOnScreen)
|
|
1919
2183
|
val safeTop = rootLocationOnScreen[1] + systemBarInsets.top
|
|
1920
2184
|
val safeBottom = rootLocationOnScreen[1] + rootView.height - systemBarInsets.bottom
|
|
1921
|
-
if (safeBottom <= safeTop) return centeredOriginY
|
|
2185
|
+
if (safeBottom <= safeTop) return Metrics(centeredOriginY, trackHeight)
|
|
1922
2186
|
val localCenterY = (safeTop + safeBottom) / 2f - locationOnScreen[1]
|
|
1923
|
-
|
|
1924
|
-
|
|
2187
|
+
val maximumOrigin = (height - edgePadding - trackHeight).coerceAtLeast(edgePadding)
|
|
2188
|
+
return Metrics(
|
|
2189
|
+
(localCenterY - trackHeight / 2f).coerceIn(edgePadding, maximumOrigin),
|
|
2190
|
+
trackHeight,
|
|
2191
|
+
)
|
|
1925
2192
|
}
|
|
1926
2193
|
|
|
2194
|
+
private fun entryCenterY(index: Int, metrics: Metrics): Float {
|
|
2195
|
+
if (titles.isEmpty()) return height / 2f
|
|
2196
|
+
return metrics.originY + metrics.trackHeight * (index + 0.5f) / titles.size
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
private fun visibleLabelCenterY(visibleIndex: Int, visibleCount: Int, metrics: Metrics): Float {
|
|
2200
|
+
val visibleTrackHeight = minOf(metrics.trackHeight, dp(16f) * visibleCount)
|
|
2201
|
+
val visibleOriginY = metrics.originY + (metrics.trackHeight - visibleTrackHeight) / 2f
|
|
2202
|
+
return visibleOriginY + visibleTrackHeight * (visibleIndex + 0.5f) / visibleCount.coerceAtLeast(1)
|
|
2203
|
+
}
|
|
2204
|
+
|
|
2205
|
+
private fun visibleLabelIndices(trackHeight: Float): Set<Int> {
|
|
2206
|
+
if (titles.isEmpty()) return emptySet()
|
|
2207
|
+
val maxVisible = max(1, (trackHeight / dp(16f)).toInt())
|
|
2208
|
+
if (titles.size <= maxVisible) return titles.indices.toSet()
|
|
2209
|
+
if (maxVisible == 1) return setOf(0)
|
|
2210
|
+
return (0 until maxVisible).mapTo(mutableSetOf()) { slot ->
|
|
2211
|
+
(slot.toFloat() * titles.lastIndex / (maxVisible - 1)).roundToInt()
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
private fun dp(value: Float): Float = value * resources.displayMetrics.density
|
|
2216
|
+
|
|
1927
2217
|
private fun updateContentDescription() {
|
|
1928
2218
|
contentDescription = activeIndex?.let { titles.getOrNull(it) }
|
|
1929
2219
|
?.let { "$ACCESSIBILITY_LABEL, $it" }
|