@onekeyfe/react-native-pager-view 3.0.171 → 3.0.173

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.
@@ -123,7 +123,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
123
123
  private val originalScrollerPadding = WeakHashMap<ScrollView, ScrollerPadding>()
124
124
  private val restoredScrollerKeys = WeakHashMap<ScrollView, String>()
125
125
  private val ownedScrollerKeys = WeakHashMap<NativeScrollerViewport, String>()
126
- private val ownedScrollerRestoreRequests = HashMap<String, Pair<WeakReference<NativeScrollerViewport>, Int?>>()
127
126
  private var foregroundRefreshIndicator: WeakReference<ImageView>? = null
128
127
  private var refreshForegroundWasVisible = false
129
128
  private val refreshForegroundMatrix = Matrix()
@@ -131,23 +130,13 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
131
130
  private val refreshForegroundAncestors = ArrayList<View>()
132
131
  private var refreshForegroundAncestorAlpha = 1f
133
132
  private val pageOffsets = HashMap<String, Int>()
134
- private val pageHeaderOffsets = HashMap<String, Int>()
135
- private var transitionHeaderOffsets: Map<String, Int>? = null
136
- private var transitionPageOffsets: Map<String, Int>? = null
137
- private var transitionSourcePage = 0
138
- private var transitionTargetPage: Int? = null
139
- private var syntheticPageRestore = false
140
- private var resumeTransitionSource: Int? = null
141
- private val pendingPageRestores = HashSet<String>()
142
133
  private var observedRecyclerView: RecyclerView? = null
143
- private var observedRecyclerKey: String? = null
144
134
  private var observedScrollListener: RecyclerView.OnScrollListener? = null
145
135
  private var observedOwnedScroller: NativeScrollerViewport? = null
146
136
  private var observedOwnedScrollerKey: String? = null
147
137
  private var observedOwnedScrollerListener: ViewTreeObserver.OnScrollChangedListener? = null
148
138
  private var observedOwnedScrollerTreeObserver: ViewTreeObserver? = null
149
139
  private var observedNativeScroller: ScrollView? = null
150
- private var observedNativeScrollerKey: String? = null
151
140
  private var observedNativeScrollerListener: ViewTreeObserver.OnScrollChangedListener? = null
152
141
  private var observedNativeScrollerTreeObserver: ViewTreeObserver? = null
153
142
  private var observedPageRemoved = false
@@ -241,19 +230,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
241
230
  OneKeyLog.debug("CollapsiblePager", "host=$hostIdentity $message")
242
231
  }
243
232
 
244
- private var offsetDiagSequence = 0L
245
-
246
- private fun offsetDiag(phase: String, view: View? = null, key: String = currentPageKey()) {
247
- if ((context.applicationInfo.flags and android.content.pm.ApplicationInfo.FLAG_DEBUGGABLE) == 0) return
248
- val safeKey = if (key in setOf("portfolio", "perps", "defi", "nft", "history")) key else "other"
249
- val observed = observedOwnedScroller ?: observedNativeScroller ?: observedRecyclerView
250
- OneKeyLog.debug("CollapsiblePager", "[HOME-OFFSET-DIAG] phase=$phase seq=${++offsetDiagSequence} " +
251
- "wallMs=${System.currentTimeMillis()} monoMs=${SystemClock.uptimeMillis()} host=$hostIdentity " +
252
- "generation=$attachmentGeneration page=$selectedPage key=$safeKey header=$headerOffsetPx headerHeight=$headerHeightPx " +
253
- "view=${view?.let { System.identityHashCode(it) }} tag=${view?.id} observed=${observed?.let { System.identityHashCode(it) }} " +
254
- "offset=${view?.let(::verticalScrollOffset)} cache=${pageOffsets[key]} boundsHeight=${view?.height}")
255
- }
256
-
257
233
  private fun outerPagerIndex(): Int {
258
234
  var ancestor = parent as? View
259
235
  while (ancestor != null) {
@@ -410,80 +386,16 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
410
386
 
411
387
  fun reactChildAt(index: Int) = logicalChildren[index]
412
388
 
413
- internal fun beginPageTransition(target: Int? = null) {
414
- if (!nativeSmoothHeaderScrollEnabled || headerHeightPx <= 0) return
415
- if (transitionHeaderOffsets == null) {
416
- pageHeaderOffsets[currentPageKey()] = headerOffsetPx
417
- detachPrimaryScrollObserver()
418
- transitionSourcePage = selectedPage
419
- transitionHeaderOffsets = pageHeaderOffsets.toMap()
420
- transitionPageOffsets = pageOffsets.toMap()
421
- }
422
- if (target != null) transitionTargetPage = target
423
- }
424
-
425
- internal fun onPagerScrolled(position: Int, offset: Float) {
426
- val headers = transitionHeaderOffsets ?: return
427
- val target = transitionTargetPage
428
- val first = if (target != null) transitionSourcePage else position
429
- val second = target ?: (position + 1).coerceAtMost(adapter.itemCount - 1)
430
- val fraction = if (target != null && target != first) {
431
- ((position + offset - first) / (target - first)).coerceIn(0f, 1f)
432
- } else offset
433
- val firstH = headers[pageKey(first)] ?: 0
434
- val secondH = headers[pageKey(second)] ?: 0
435
- syntheticPageRestore = true
436
- setHeaderOffset((firstH + (secondH - firstH) * fraction).roundToInt())
437
- syntheticPageRestore = false
438
- updateNativeTabProgress(position, offset)
439
- }
440
-
441
- internal fun onPagerScrollStateChanged(state: Int) {
442
- if (state == ViewPager2.SCROLL_STATE_DRAGGING || state == ViewPager2.SCROLL_STATE_SETTLING) {
443
- beginPageTransition()
444
- } else if (state == ViewPager2.SCROLL_STATE_IDLE && transitionHeaderOffsets != null) {
445
- finishPageTransition(pager.currentItem)
446
- }
447
- }
448
-
449
- private fun finishPageTransition(position: Int) {
450
- val headers = transitionHeaderOffsets ?: return
451
- detachPrimaryScrollObserver(recordOffset = false)
452
- releaseRefreshForeground()
453
- selectedPage = position.coerceIn(0, adapter.itemCount - 1)
454
- syntheticPageRestore = true
455
- setHeaderOffset(headers[currentPageKey()] ?: 0)
456
- transitionHeaderOffsets = null
457
- transitionPageOffsets = null
458
- transitionTargetPage = null
459
- syntheticPageRestore = false
460
- pageHeaderOffsets[currentPageKey()] = headerOffsetPx
461
- prepareAdjacentPages()
462
- attachPrimaryScrollObserver()
463
- }
464
-
465
389
  fun selectPage(position: Int) {
466
- offsetDiag("select-begin-target=$position", observedOwnedScroller ?: observedNativeScroller ?: observedRecyclerView)
467
390
  pendingRefreshPull = null
468
391
  if (adapter.itemCount == 0) return
469
- if (nativeSmoothHeaderScrollEnabled && headerHeightPx > 0) {
470
- if (transitionHeaderOffsets != null) return
471
- beginPageTransition(position)
472
- finishPageTransition(position)
473
- return
474
- }
475
392
  detachPrimaryScrollObserver()
476
393
  releaseRefreshForeground()
477
394
  selectedPage = position.coerceIn(0, adapter.itemCount - 1)
478
395
  val savedOffset = pageOffsets[currentPageKey()]
479
396
  ?: primaryScrollableForPage(selectedPage)?.let(::verticalScrollOffset)
480
397
  ?: 0
481
- offsetDiag("select-offset=$savedOffset-source=${if (pageOffsets[currentPageKey()] != null) "saved" else "live"}")
482
- if (savedOffset > 0) {
483
- offsetDiag("select-force-collapse-before")
484
- setHeaderOffset(headerHeightPx)
485
- offsetDiag("select-force-collapse-after")
486
- }
398
+ if (savedOffset > 0) setHeaderOffset(headerHeightPx)
487
399
  postForCurrentAttachment {
488
400
  prepareAdjacentPages()
489
401
  attachPrimaryScrollObserver()
@@ -625,14 +537,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
625
537
 
626
538
  private fun setHeaderOffset(offset: Int) {
627
539
  // val next = offset.coerceIn(0, headerHeightPx)
628
- val pending = nativeSmoothHeaderScrollEnabled && currentPageKey() in pendingPageRestores
629
- val maximum = if (transitionHeaderOffsets != null || pending) headerHeightPx else maximumHeaderOffset()
630
- val next = offset.coerceIn(0, maximum)
540
+ val next = offset.coerceIn(0, maximumHeaderOffset())
631
541
  if (headerOffsetPx == next) return
632
542
  headerOffsetPx = next
633
- if (nativeSmoothHeaderScrollEnabled && transitionHeaderOffsets == null && !syntheticPageRestore && !pending) {
634
- pageHeaderOffsets[currentPageKey()] = next
635
- }
636
543
  applyHeaderOffset()
637
544
  }
638
545
 
@@ -749,17 +656,13 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
749
656
  }
750
657
 
751
658
  private fun attachPrimaryScrollObserver() {
752
- if (transitionHeaderOffsets != null) return
753
659
  if (selectedPage !in 0 until adapter.itemCount) return
754
660
  val previous = observedRecyclerView ?: observedOwnedScroller ?: observedNativeScroller
755
661
  if (observedPageRemoved || previous != null && !isDescendant(previous, adapter.pageAt(selectedPage))) {
756
- offsetDiag("primary-replacement-before-removed=$observedPageRemoved", previous)
757
662
  observedPageRemoved = false
758
- detachPrimaryScrollObserver(recordOffset = !nativeSmoothHeaderScrollEnabled)
663
+ detachPrimaryScrollObserver()
759
664
  // A replacement scroller starts at the beginning while retaining header collapse.
760
- offsetDiag("primary-replacement-cache-before", previous)
761
- if (!nativeSmoothHeaderScrollEnabled) pageOffsets[currentPageKey()] = 0
762
- offsetDiag("primary-replacement-cache-after", previous)
665
+ pageOffsets[currentPageKey()] = 0
763
666
  }
764
667
  when (val scrollable = primaryScrollableForPage(selectedPage)) {
765
668
  is RecyclerView -> attachRecyclerObserver(scrollable)
@@ -769,33 +672,24 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
769
672
  }
770
673
 
771
674
  private fun attachRecyclerObserver(recycler: RecyclerView) {
772
- if (observedRecyclerView === recycler && observedRecyclerKey == currentPageKey()) {
675
+ if (observedRecyclerView === recycler) {
773
676
  applyRecyclerInsets(recycler, selectedPage)
774
677
  publishConsumedOffset(recycler)
775
678
  return
776
679
  }
777
680
 
778
681
  detachPrimaryScrollObserver()
779
- offsetDiag("recycler-attach-before", recycler)
780
682
  observedRecyclerView = recycler
781
- observedRecyclerKey = currentPageKey()
782
683
  applyRecyclerInsets(recycler, selectedPage)
783
684
  publishConsumedOffset(recycler)
784
- val key = currentPageKey()
785
685
  val listener = object : RecyclerView.OnScrollListener() {
786
686
  override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
787
- if (observedScrollListener !== this || observedRecyclerKey != key ||
788
- key != currentPageKey() || !isAttachedToWindow ||
789
- transitionHeaderOffsets != null || syntheticPageRestore || key in pendingPageRestores) return
790
687
  val contentOffset = recyclerView.computeVerticalScrollOffset()
791
- if (pageOffsets[currentPageKey()] != contentOffset) offsetDiag("recycler-cache-before-new=$contentOffset", recyclerView)
792
- pageOffsets[key] = contentOffset
688
+ pageOffsets[currentPageKey()] = contentOffset
793
689
  // Nested touch scrolling moves list content only after the header has
794
690
  // collapsed. Programmatic list jumps bypass those parent callbacks.
795
691
  if (contentOffset > 0 && headerOffsetPx < headerHeightPx) {
796
- offsetDiag("recycler-positive-collapse-before", recyclerView)
797
692
  setHeaderOffset(headerHeightPx)
798
- offsetDiag("recycler-positive-collapse-after", recyclerView)
799
693
  }
800
694
  }
801
695
  }
@@ -811,11 +705,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
811
705
  val recycler = observedRecyclerView
812
706
  val listener = observedScrollListener
813
707
  if (recycler != null) {
814
- offsetDiag("recycler-detach-save-before-record=$recordOffset", recycler)
815
- if (recordOffset && transitionPageOffsets == null) observedRecyclerKey?.let {
816
- if (it !in pendingPageRestores) pageOffsets[it] = recycler.computeVerticalScrollOffset()
817
- }
818
- offsetDiag("recycler-detach-save-after", recycler)
708
+ if (recordOffset) pageOffsets[currentPageKey()] = recycler.computeVerticalScrollOffset()
819
709
  publishConsumedOffset(recycler, remove = true)
820
710
  }
821
711
  if (recycler != null && listener != null) recycler.removeOnScrollListener(listener)
@@ -826,37 +716,28 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
826
716
  )
827
717
  }
828
718
  observedRecyclerView = null
829
- observedRecyclerKey = null
830
719
  observedScrollListener = null
831
720
  }
832
721
 
833
722
  private fun attachNativeScrollerObserver(scroller: ScrollView) {
834
- if (observedNativeScroller === scroller && observedNativeScrollerKey == currentPageKey()) {
723
+ if (observedNativeScroller === scroller) {
835
724
  applyNativeScrollerInsets(scroller, selectedPage)
836
725
  return
837
726
  }
838
727
 
839
728
  detachPrimaryScrollObserver()
840
- offsetDiag("scrollview-attach-before", scroller)
841
729
  observedNativeScroller = scroller
842
- observedNativeScrollerKey = currentPageKey()
843
730
  applyNativeScrollerInsets(scroller, selectedPage)
844
- val key = currentPageKey()
845
731
  val listener = object : ViewTreeObserver.OnScrollChangedListener {
846
732
  override fun onScrollChanged() {
847
733
  if (observedNativeScrollerListener !== this) {
848
734
  removeScrollChangedListener(null, scroller, this)
849
735
  return
850
736
  }
851
- if (key != currentPageKey() || !isAttachedToWindow ||
852
- transitionHeaderOffsets != null || syntheticPageRestore || key in pendingPageRestores) return
853
737
  val contentOffset = scroller.scrollY
854
- if (pageOffsets[currentPageKey()] != contentOffset) offsetDiag("scrollview-cache-before-new=$contentOffset", scroller)
855
- pageOffsets[key] = contentOffset
738
+ pageOffsets[currentPageKey()] = contentOffset
856
739
  if (contentOffset > 0 && headerOffsetPx < headerHeightPx) {
857
- offsetDiag("scrollview-positive-collapse-before", scroller)
858
740
  setHeaderOffset(headerHeightPx)
859
- offsetDiag("scrollview-positive-collapse-after", scroller)
860
741
  }
861
742
  }
862
743
  }
@@ -873,11 +754,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
873
754
  private fun detachNativeScrollerObserver(recordOffset: Boolean) {
874
755
  val scroller = observedNativeScroller
875
756
  val listener = observedNativeScrollerListener
876
- offsetDiag("scrollview-detach-save-before-record=$recordOffset", scroller)
877
- if (scroller != null && recordOffset && transitionPageOffsets == null) observedNativeScrollerKey?.let {
878
- if (it !in pendingPageRestores) pageOffsets[it] = scroller.scrollY
879
- }
880
- offsetDiag("scrollview-detach-save-after", scroller)
757
+ if (scroller != null && recordOffset) pageOffsets[currentPageKey()] = scroller.scrollY
881
758
  if (scroller != null && listener != null) {
882
759
  removeScrollChangedListener(observedNativeScrollerTreeObserver, scroller, listener)
883
760
  }
@@ -888,7 +765,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
888
765
  )
889
766
  }
890
767
  observedNativeScroller = null
891
- observedNativeScrollerKey = null
892
768
  observedNativeScrollerListener = null
893
769
  observedNativeScrollerTreeObserver = null
894
770
  }
@@ -917,23 +793,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
917
793
  val key = pageKey(pageIndex)
918
794
  if (ownedScrollerKeys[scroller] == key) return
919
795
  ownedScrollerKeys[scroller] = key
920
- if (nativeSmoothHeaderScrollEnabled) {
921
- pendingPageRestores.add(key)
922
- ownedScrollerRestoreRequests[key] = WeakReference(scroller) to null
923
- }
924
- val generation = attachmentGeneration
925
796
  postForCurrentAttachment {
926
- if (ownedScrollerKeys[scroller] == key && generation == attachmentGeneration &&
927
- scroller.isAttachedToWindow && (!nativeSmoothHeaderScrollEnabled ||
928
- (key in pendingPageRestores && ownedScrollerRestoreRequests[key]?.first?.get() === scroller))) {
929
- offsetDiag("owned-restore-before", scroller, key)
930
- val saved = transitionPageOffsets?.get(key) ?: pageOffsets[key] ?: 0
931
- scroller.host.restoreScrollOffset(saved)
932
- if (nativeSmoothHeaderScrollEnabled) {
933
- ownedScrollerRestoreRequests[key] = WeakReference(scroller) to generation
934
- recordOwnedScrollerOffset(scroller, key)
935
- }
936
- offsetDiag("owned-restore-after", scroller, key)
797
+ if (ownedScrollerKeys[scroller] == key && scroller.isAttachedToWindow) {
798
+ scroller.host.restoreScrollOffset(pageOffsets[key] ?: 0)
937
799
  }
938
800
  }
939
801
  }
@@ -941,9 +803,7 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
941
803
  private fun restoreOwnedScrollerInsets(scroller: NativeScrollerViewport) {
942
804
  scroller.stopMotion()
943
805
  scroller.host.cancelPendingScrollRestore()
944
- ownedScrollerKeys.remove(scroller)?.let { key ->
945
- if (ownedScrollerRestoreRequests[key]?.first?.get() === scroller) ownedScrollerRestoreRequests.remove(key)
946
- }
806
+ ownedScrollerKeys.remove(scroller)
947
807
  publishConsumedOffset(scroller, remove = true)
948
808
  scroller.host.setPagerInset(this, null)
949
809
  }
@@ -951,11 +811,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
951
811
  private fun attachOwnedScrollerObserver(scroller: NativeScrollerViewport) {
952
812
  if (observedOwnedScroller === scroller && observedOwnedScrollerKey == currentPageKey()) {
953
813
  applyOwnedScrollerInsets(scroller, selectedPage)
954
- if (nativeSmoothHeaderScrollEnabled) recordOwnedScrollerOffset(scroller, currentPageKey())
955
814
  return
956
815
  }
957
816
  detachPrimaryScrollObserver()
958
- offsetDiag("owned-attach-before", scroller)
959
817
  observedOwnedScroller = scroller
960
818
  observedOwnedScrollerKey = currentPageKey()
961
819
  val key = currentPageKey()
@@ -966,7 +824,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
966
824
  removeScrollChangedListener(null, scroller, this)
967
825
  return
968
826
  }
969
- recordOwnedScrollerOffset(scroller, key)
827
+ val contentOffset = scroller.scrollY
828
+ pageOffsets[key] = contentOffset
829
+ if (contentOffset > 0 && headerOffsetPx < headerHeightPx) setHeaderOffset(headerHeightPx)
970
830
  }
971
831
  }
972
832
  observedOwnedScrollerListener = listener
@@ -975,37 +835,11 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
975
835
  }
976
836
  }
977
837
 
978
- private fun recordOwnedScrollerOffset(scroller: NativeScrollerViewport, key: String) {
979
- if (ownedScrollerKeys[scroller] != key || !isAttachedToWindow || !scroller.isAttachedToWindow) return
980
- if (key in pendingPageRestores) {
981
- // A false native pending flag before our posted request executes is not cancellation.
982
- val request = ownedScrollerRestoreRequests[key]
983
- if (request == null || request.first.get() !== scroller || request.second != attachmentGeneration ||
984
- scroller.width <= 0 || scroller.height <= 0 || scroller.host.hasPendingScrollRestore()) return
985
- pendingPageRestores.remove(key)
986
- ownedScrollerRestoreRequests.remove(key)
987
- }
988
- if (observedOwnedScroller !== scroller || observedOwnedScrollerKey != key ||
989
- key != currentPageKey() || transitionHeaderOffsets != null || syntheticPageRestore) return
990
- val contentOffset = scroller.scrollY
991
- if (pageOffsets[key] != contentOffset) offsetDiag("owned-cache-before-new=$contentOffset", scroller, key)
992
- pageOffsets[key] = contentOffset
993
- if (contentOffset > 0 && headerOffsetPx < headerHeightPx) {
994
- offsetDiag("owned-positive-collapse-before", scroller, key)
995
- setHeaderOffset(headerHeightPx)
996
- offsetDiag("owned-positive-collapse-after", scroller, key)
997
- }
998
- }
999
-
1000
838
  private fun detachOwnedScrollerObserver(recordOffset: Boolean) {
1001
839
  val scroller = observedOwnedScroller ?: return
1002
840
  scroller.stopMotion()
1003
841
  publishConsumedOffset(scroller, remove = true)
1004
- offsetDiag("owned-detach-save-before-record=$recordOffset", scroller)
1005
- if (recordOffset && transitionPageOffsets == null) observedOwnedScrollerKey?.let {
1006
- if (it !in pendingPageRestores) pageOffsets[it] = scroller.scrollY
1007
- }
1008
- offsetDiag("owned-detach-save-after", scroller)
842
+ if (recordOffset) observedOwnedScrollerKey?.let { pageOffsets[it] = scroller.scrollY }
1009
843
  observedOwnedScrollerListener?.let {
1010
844
  removeScrollChangedListener(observedOwnedScrollerTreeObserver, scroller, it)
1011
845
  }
@@ -1059,7 +893,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
1059
893
  val key = pageKey(pageIndex)
1060
894
  if (restoredRecyclerKeys[recycler] != key) {
1061
895
  restoredRecyclerKeys[recycler] = key
1062
- if (nativeSmoothHeaderScrollEnabled) pendingPageRestores.add(key)
1063
896
  val generation = attachmentGeneration
1064
897
  recycler.post {
1065
898
  if (!isAttachedToWindow || generation != attachmentGeneration ||
@@ -1068,16 +901,9 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
1068
901
  if (restoredRecyclerKeys[recycler] == key) restoredRecyclerKeys.remove(recycler)
1069
902
  return@post
1070
903
  }
1071
- val saved = transitionPageOffsets?.get(key) ?: pageOffsets[key] ?: 0
904
+ val saved = pageOffsets[key] ?: 0
1072
905
  val current = recycler.computeVerticalScrollOffset()
1073
- offsetDiag("recycler-restore-before-saved=$saved", recycler, key)
1074
- if (nativeSmoothHeaderScrollEnabled && (recycler.height <= 0 || recycler.childCount == 0)) {
1075
- restoredRecyclerKeys.remove(recycler)
1076
- return@post
1077
- }
1078
906
  if (saved != current) recycler.scrollBy(0, saved - current)
1079
- pendingPageRestores.remove(key)
1080
- offsetDiag("recycler-restore-after", recycler, key)
1081
907
  }
1082
908
  }
1083
909
  }
@@ -1123,7 +949,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
1123
949
  val key = pageKey(pageIndex)
1124
950
  if (restoredScrollerKeys[scroller] != key) {
1125
951
  restoredScrollerKeys[scroller] = key
1126
- if (nativeSmoothHeaderScrollEnabled) pendingPageRestores.add(key)
1127
952
  val generation = attachmentGeneration
1128
953
  scroller.post {
1129
954
  if (!isAttachedToWindow || generation != attachmentGeneration ||
@@ -1132,16 +957,8 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
1132
957
  if (restoredScrollerKeys[scroller] == key) restoredScrollerKeys.remove(scroller)
1133
958
  return@post
1134
959
  }
1135
- val saved = transitionPageOffsets?.get(key) ?: pageOffsets[key] ?: 0
1136
- offsetDiag("scrollview-restore-before-saved=$saved", scroller, key)
1137
- if (nativeSmoothHeaderScrollEnabled &&
1138
- (scroller.height <= 0 || (scroller.getChildAt(0)?.height ?: 0) <= 0)) {
1139
- restoredScrollerKeys.remove(scroller)
1140
- return@post
1141
- }
960
+ val saved = pageOffsets[key] ?: 0
1142
961
  if (scroller.scrollY != saved) scroller.scrollTo(scroller.scrollX, saved)
1143
- pendingPageRestores.remove(key)
1144
- offsetDiag("scrollview-restore-after", scroller, key)
1145
962
  }
1146
963
  }
1147
964
  }
@@ -1582,10 +1399,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
1582
1399
  override fun onNestedScrollAccepted(child: View, target: View, axes: Int, type: Int) {
1583
1400
  if (type == ViewCompat.TYPE_TOUCH) pendingRefreshPull = null
1584
1401
  nestedScrollingParentHelper.onNestedScrollAccepted(child, target, axes, type)
1585
- if (type == ViewCompat.TYPE_TOUCH) {
1586
- pendingPageRestores.remove(currentPageKey())
1587
- ownedScrollerRestoreRequests.remove(currentPageKey())
1588
- }
1589
1402
  }
1590
1403
 
1591
1404
  override fun onStopNestedScroll(target: View, type: Int) {
@@ -1679,13 +1492,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
1679
1492
  restoredRecyclerKeys.clear()
1680
1493
  restoredScrollerKeys.clear()
1681
1494
  viewTreeObserver.addOnPreDrawListener(pageContentLayoutListener)
1682
- resumeTransitionSource?.let { source ->
1683
- resumeTransitionSource = null
1684
- if (source in 0 until adapter.itemCount) {
1685
- pager.setCurrentItem(source, false)
1686
- selectPage(source)
1687
- }
1688
- }
1689
1495
  log(
1690
1496
  "host-attach generation=$attachmentGeneration inner=$selectedPage " +
1691
1497
  "outer=${outerPagerIndex()} nativePages=${adapter.itemCount}",
@@ -1705,15 +1511,6 @@ class CollapsiblePagerHost(context: Context) : NestedScrollableHost(context), Ne
1705
1511
  nestedNativeGestureStarted = false
1706
1512
  viewTreeObserver.removeOnPreDrawListener(pageContentLayoutListener)
1707
1513
  detachPrimaryScrollObserver()
1708
- transitionHeaderOffsets?.let { headers ->
1709
- resumeTransitionSource = transitionSourcePage
1710
- syntheticPageRestore = true
1711
- setHeaderOffset(headers[pageKey(transitionSourcePage)] ?: 0)
1712
- syntheticPageRestore = false
1713
- }
1714
- transitionHeaderOffsets = null
1715
- transitionPageOffsets = null
1716
- transitionTargetPage = null
1717
1514
  // Snapshot live keys before restoration removes their padding records.
1718
1515
  for (recycler in originalRecyclerPadding.keys.toTypedArray()) {
1719
1516
  restoreRecyclerInsets(recycler)
@@ -43,7 +43,6 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
43
43
  )
44
44
  host.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
45
45
  override fun onPageScrolled(position: Int, offset: Float, offsetPixels: Int) {
46
- host.onPagerScrolled(position, offset)
47
46
  dispatch(reactContext, host, PageScrollEvent(host.id, position, offset))
48
47
  }
49
48
 
@@ -56,7 +55,6 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
56
55
  }
57
56
 
58
57
  override fun onPageScrollStateChanged(state: Int) {
59
- host.onPagerScrollStateChanged(state)
60
58
  val value = when (state) {
61
59
  ViewPager2.SCROLL_STATE_IDLE -> "idle"
62
60
  ViewPager2.SCROLL_STATE_DRAGGING -> "dragging"
@@ -326,19 +324,13 @@ class CollapsiblePagerViewManager : ViewGroupManager<CollapsiblePagerHost>(),
326
324
 
327
325
  override fun setPage(view: CollapsiblePagerHost?, selectedPage: Int) {
328
326
  if (view != null && selectedPage in 0 until view.adapter.itemCount) {
329
- view.beginPageTransition(selectedPage)
330
327
  view.pager.setCurrentItem(selectedPage, true)
331
- if (view.pager.scrollState == ViewPager2.SCROLL_STATE_IDLE) {
332
- view.onPagerScrollStateChanged(ViewPager2.SCROLL_STATE_IDLE)
333
- }
334
328
  }
335
329
  }
336
330
 
337
331
  override fun setPageWithoutAnimation(view: CollapsiblePagerHost?, selectedPage: Int) {
338
332
  if (view != null && selectedPage in 0 until view.adapter.itemCount) {
339
- view.beginPageTransition(selectedPage)
340
333
  view.pager.setCurrentItem(selectedPage, false)
341
- view.onPagerScrollStateChanged(ViewPager2.SCROLL_STATE_IDLE)
342
334
  }
343
335
  }
344
336
 
@@ -255,8 +255,6 @@ class NativeScrollerView(context: Context) : FrameLayout(context) {
255
255
 
256
256
  internal fun cancelPendingScrollRestore() = pendingRestore.cancel()
257
257
 
258
- internal fun hasPendingScrollRestore(): Boolean = pendingRestore.target != null
259
-
260
258
  internal fun setRefreshOffset(value: Int) {
261
259
  refreshOffset = value
262
260
  applyRefreshOffset()
@@ -27,16 +27,5 @@ fun main() {
27
27
  restore.cancel()
28
28
  check(restore.target == null)
29
29
  check(restore.anchor(120) == 120)
30
- // Cancellation clears the old target without requiring the reached offset to change.
31
- restore.request(target = 640, reached = 120)
32
- check(restore.anchor(120) == 640)
33
- restore.cancel()
34
- check(restore.target == null)
35
- check(restore.anchor(120) == 120)
36
- // A fresh request owns its target after cancellation.
37
- restore.request(target = 320, reached = 120)
38
- check(restore.target == 320)
39
- restore.settle(reached = 320)
40
- check(restore.target == null)
41
30
  println("Native scroller pending restore: clamp retry, partial growth, settle and cancel passed")
42
31
  }
package/docs/SPEC.md CHANGED
@@ -3,42 +3,6 @@
3
3
  Existing PagerView and CollapsiblePagerView APIs are documented in README.md.
4
4
  The sections below define native coordination boundaries without new React props.
5
5
 
6
- ## Smooth per-page screen restoration
7
-
8
- When nativeSmoothHeaderScrollEnabled is enabled with a collapsible header, each
9
- page owns its header collapse and body offset. For a settled position outside
10
- pull overscroll, Q = H + L, H = min(Q, C), and L = max(Q - C, 0), where C is
11
- the header-collapse distance. These are semantic coordinates, not new props.
12
- An unvisited page starts expanded
13
- at body start. Leaving a page preserves its expanded, partial or collapsed header
14
- and its body position; tap, swipe and programmatic selection restore the same
15
- complete screen state. Legacy mode and pagers without a collapsible header retain
16
- their existing behavior.
17
-
18
- Horizontal progress interpolates the shared header between immutable source and
19
- target snapshots. Native body geometry follows that header without obscured rows
20
- or blank gaps. Temporary transition offsets MUST NOT overwrite page snapshots.
21
- Non-adjacent commands use the original source and destination, not intermediate
22
- pages, as the header interpolation endpoints. Cancellation restores the source
23
- and all temporarily compensated pages before observation resumes.
24
-
25
- A scroll observer owns one page identity and generation independently of mutable
26
- selection. Detach, replacement and late notifications MUST NOT save another
27
- page's offset. Zero layout and unresolved replacement identity preserve pending
28
- restoration until an actual usable layout; settled content shrink still clamps to
29
- the legal range. This state is native UI-thread state, not persistent wallet data
30
- or per-frame JavaScript synchronization. Vertical scrolling, refresh, snapping
31
- and repeated-current-page selection retain their existing contracts.
32
-
33
- Android owned NativeScroller restoration keeps its intent while a posted request
34
- has not executed or native content is still too short to reach the saved offset.
35
- Only the same viewport, page key and attachment generation may reconcile an
36
- issued request. Once native restoration settles or is cancelled by touch, an
37
- imperative command or content replacement, the host resumes recording its live
38
- body offset. Existing pre-draw observation also reconciles a same-position command
39
- that emits no scroll notification. Zero-sized geometry retains the pending intent;
40
- no timeout infers that delayed content is permanently short.
41
-
42
6
  ## Independent NativeScroller migration
43
7
 
44
8
  Status: implemented in source. Both native builds pass. Android focused emulator
@@ -490,8 +454,7 @@ When restoring a retained page's cached vertical offset, the pager limits only
490
454
  the upper bound to the current content height, viewport and applied insets. A
491
455
  cache captured before rows were removed MUST NOT restore an offset below the new
492
456
  content bottom, including when removal occurs during a page transition. Valid
493
- deep offsets and pull-overscroll behavior are preserved; smooth-header selection
494
- follows the per-page reading-position contract above.
457
+ deep offsets and the existing lower-bound/header alignment rules are unchanged.
495
458
  This check runs only on explicit restoration, not on scroll frames; UIKit pull
496
459
  overscroll and refresh settlement retain their existing ownership. An unmeasured
497
460
  zero-height viewport defers this upper-bound limit to a later restoration.