@onekeyfe/react-native-pager-view 3.0.133 → 3.0.134

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.
@@ -184,10 +184,12 @@ private class CollapsiblePagerHorizontalItemsView(
184
184
  }
185
185
  }
186
186
 
187
- private fun selectedBackground(selected: Boolean) = GradientDrawable().apply {
187
+ private fun selectedBackground(selected: Boolean, height: Int) = GradientDrawable().apply {
188
188
  shape = GradientDrawable.RECTANGLE
189
189
  setColor(if (selected) selectedBackgroundColor else Color.TRANSPARENT)
190
- cornerRadius = dp(10.0).toFloat()
190
+ // OneKey patch: match the pill shape of the React category filters.
191
+ // cornerRadius = dp(10.0).toFloat()
192
+ cornerRadius = height / 2f
191
193
  }
192
194
 
193
195
  override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
@@ -278,7 +280,7 @@ private class CollapsiblePagerHorizontalItemsView(
278
280
  buttons.forEachIndexed { index, button ->
279
281
  val selected = index == selectedIndex
280
282
  button.setTextColor(if (selected) activeTextColor else inactiveTextColor)
281
- button.background = selectedBackground(selected)
283
+ button.background = selectedBackground(selected, button.height)
282
284
  button.isSelected = selected
283
285
  }
284
286
  }
@@ -11,12 +11,39 @@
11
11
  #import "React/RCTSurfaceTouchHandler.h"
12
12
  #import "React/RCTTouchHandler.h"
13
13
 
14
+ // OneKey patch: deduplicate structural mutation parents before scanning the page.
15
+ #include <unordered_set>
16
+
14
17
  using namespace facebook::react;
15
18
 
16
19
  static void *RNCCollapsiblePagerContentOffsetContext = &RNCCollapsiblePagerContentOffsetContext;
17
20
  static NSString *const RNCCollapsiblePagerNativeScrollerIDPrefix =
18
21
  @"rnc-collapsible-pager-native-scroller:";
19
22
 
23
+ static const CFTimeInterval RNCCollapsiblePagerFallbackDebounceInterval = 0.5;
24
+
25
+ // OneKey patch: native scroll views can survive a move or be reused for a new
26
+ // Fabric component. Keep weak identity until their page is attached again.
27
+ @interface RNCCollapsiblePagerReleasedScrollState : NSObject
28
+ @property (nonatomic, weak) UIScrollView *scrollView;
29
+ @property (nonatomic, weak) UIView *componentView;
30
+ @property (nonatomic, assign) NSInteger componentTag;
31
+ @property (nonatomic, weak) UIScrollView *pendingFallbackScrollView;
32
+ @property (nonatomic, assign) CFTimeInterval pendingFallbackDetectedAt;
33
+ @end
34
+
35
+ @implementation RNCCollapsiblePagerReleasedScrollState
36
+ @end
37
+
38
+ static UIView *RNCScrollComponentView(UIScrollView *scrollView)
39
+ {
40
+ for (UIView *view = scrollView; view != nil; view = view.superview) {
41
+ // UIView's Fabric category adopts the protocol even for unregistered views.
42
+ if ([view isKindOfClass:RCTViewComponentView.class] && view.tag > 0) return view;
43
+ }
44
+ return nil;
45
+ }
46
+
20
47
  typedef void (^RNCCollapsiblePagerNativeTabPressHandler)(NSInteger index, NSString *key);
21
48
 
22
49
  static CGFloat RNCClamp(CGFloat value, CGFloat minimum, CGFloat maximum)
@@ -716,7 +743,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
716
743
  NSString *key = [item[@"key"] isKindOfClass:NSString.class] ? item[@"key"] : @"";
717
744
  BOOL selected = [key isEqualToString:self->_selectedKey];
718
745
  button.backgroundColor = selected ? self->_selectedBackgroundColor : UIColor.clearColor;
719
- button.layer.cornerRadius = 10;
746
+ // OneKey patch: match the pill shape of the React category filters.
747
+ // button.layer.cornerRadius = 10;
748
+ button.layer.cornerRadius = CGRectGetHeight(button.bounds) / 2;
720
749
  [button setTitleColor:selected ? self->_activeTextColor : self->_inactiveTextColor
721
750
  forState:UIControlStateNormal];
722
751
  button.accessibilityTraits = selected
@@ -989,6 +1018,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
989
1018
  NSMapTable<UIScrollView *, NSValue *> *_originalIndicatorInsets;
990
1019
  NSMapTable<UIScrollView *, NSNumber *> *_originalAlwaysBounceVertical;
991
1020
  NSMapTable<UIScrollView *, NSNumber *> *_originalInsetAdjustmentBehavior;
1021
+ // OneKey patch: pair pre-mount release with recovery, including deferred mounts.
1022
+ NSMutableDictionary<NSString *, RNCCollapsiblePagerReleasedScrollState *> *_releasedPageScrollStates;
1023
+ BOOL _needsScrollObserverReattachAfterMount;
992
1024
  __weak UIScrollView *_observedScrollView;
993
1025
  __weak UIScrollView *_sharedHeaderScrollView;
994
1026
  __weak UIScrollView *_pendingFallbackScrollView;
@@ -1023,6 +1055,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1023
1055
  _logicalChildren = [NSMutableArray new];
1024
1056
  _pageControllers = [NSMutableArray new];
1025
1057
  _pageOffsets = [NSMutableDictionary new];
1058
+ _releasedPageScrollStates = [NSMutableDictionary new];
1026
1059
  _originalInsets = [NSMapTable weakToStrongObjectsMapTable];
1027
1060
  _appliedTopInsets = [NSMapTable weakToStrongObjectsMapTable];
1028
1061
  _originalIndicatorInsets = [NSMapTable weakToStrongObjectsMapTable];
@@ -1132,6 +1165,19 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1132
1165
  !_pagerScrollView.dragging && !_pagerScrollView.decelerating) {
1133
1166
  [self finishPagerScrollEmittingSelection:NO];
1134
1167
  }
1168
+ // OneKey patch: a detached pager can return without a new Fabric transaction.
1169
+ // Wait until the current mount has finished before inspecting its new lists.
1170
+ if (self.window != nil &&
1171
+ (_needsScrollObserverReattachAfterMount || _releasedPageScrollStates.count > 0)) {
1172
+ NSUInteger generation = _generation;
1173
+ dispatch_async(dispatch_get_main_queue(), ^{
1174
+ if (self->_generation == generation &&
1175
+ (self->_needsScrollObserverReattachAfterMount || self->_releasedPageScrollStates.count > 0)) {
1176
+ self->_needsScrollObserverReattachAfterMount = YES;
1177
+ [self attachScrollObserverForCurrentPage];
1178
+ }
1179
+ });
1180
+ }
1135
1181
  }
1136
1182
 
1137
1183
  - (void)dealloc
@@ -1375,6 +1421,63 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1375
1421
 
1376
1422
  #pragma mark - React mounting
1377
1423
 
1424
+ // OneKey patch: Fabric can recycle a removed list as a descendant of our header
1425
+ // within the same transaction. Release native ownership before that reuse can
1426
+ // form a cycle; did-mount recovery runs too late to prevent it.
1427
+ - (void)mountingTransactionWillMount:(const MountingTransaction &)transaction
1428
+ withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry
1429
+ {
1430
+ if (_isBeingRecycled || (_originalInsets.count == 0 && _releasedPageScrollStates.count == 0)) return;
1431
+ std::unordered_set<NSInteger> removedTags;
1432
+ std::unordered_set<NSInteger> deletedTags;
1433
+ for (const auto &mutation : transaction.getMutations()) {
1434
+ if ((mutation.type == ShadowViewMutation::Remove ||
1435
+ mutation.type == ShadowViewMutation::Delete) && mutation.oldChildShadowView.tag > 0) {
1436
+ removedTags.insert(mutation.oldChildShadowView.tag);
1437
+ if (mutation.type == ShadowViewMutation::Delete) {
1438
+ deletedTags.insert(mutation.oldChildShadowView.tag);
1439
+ }
1440
+ }
1441
+ }
1442
+ // A transition can defer reattachment across several mounting transactions.
1443
+ for (RNCCollapsiblePagerReleasedScrollState *state in _releasedPageScrollStates.allValues) {
1444
+ if (deletedTags.find(state.componentTag) != deletedTags.end()) state.componentView = nil;
1445
+ }
1446
+ if (removedTags.empty()) return;
1447
+
1448
+ // Inspect only the managed scroll views' ancestor chains, including wrappers
1449
+ // above the pager. Row removals and unrelated commits keep ownership intact.
1450
+ for (UIScrollView *scrollView in _originalInsets.keyEnumerator.allObjects) {
1451
+ for (UIView *ancestor = scrollView; ancestor != nil; ancestor = ancestor.superview) {
1452
+ if (removedTags.find(ancestor.tag) == removedTags.end()) continue;
1453
+ BOOL belongsToPage = NO;
1454
+ for (NSInteger index = 0; index < _pageControllers.count; index++) {
1455
+ if (![scrollView isDescendantOfView:_pageControllers[index].view]) continue;
1456
+ belongsToPage = YES;
1457
+ NSString *pageKey = [self pageKeyForIndex:index];
1458
+ UIView *componentView = RNCScrollComponentView(scrollView);
1459
+ RNCCollapsiblePagerReleasedScrollState *state = [RNCCollapsiblePagerReleasedScrollState new];
1460
+ state.scrollView = scrollView;
1461
+ state.componentTag = componentView.tag;
1462
+ // A deleted component can reuse both the native object and its tag.
1463
+ if (deletedTags.find(componentView.tag) == deletedTags.end()) {
1464
+ state.componentView = componentView;
1465
+ }
1466
+ _releasedPageScrollStates[pageKey] = state;
1467
+ // Retained pages may be visually aligned to the expanded header while
1468
+ // their saved offsets still hold the user's independent list position.
1469
+ if (scrollView == _observedScrollView || _pageOffsets[pageKey] == nil) {
1470
+ _pageOffsets[pageKey] = @(scrollView.contentOffset.y + scrollView.contentInset.top);
1471
+ }
1472
+ break;
1473
+ }
1474
+ _needsScrollObserverReattachAfterMount = YES;
1475
+ [self restoreScrollViewState:scrollView resetSavedOffset:!belongsToPage];
1476
+ break;
1477
+ }
1478
+ }
1479
+ }
1480
+
1378
1481
  - (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
1379
1482
  withSurfaceTelemetry:(const SurfaceTelemetry &)surfaceTelemetry
1380
1483
  {
@@ -1382,16 +1485,55 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1382
1485
  _isPagerDragging || _isBeingRecycled || self.window == nil ||
1383
1486
  _currentIndex < 0 || _currentIndex >= _pageControllers.count) return;
1384
1487
 
1385
- UIView *pageView = _pageControllers[_currentIndex].view;
1386
- BOOL shouldResolve = _observedScrollView == nil ||
1387
- ![_observedScrollView isDescendantOfView:pageView];
1388
- if (!shouldResolve &&
1389
- [_observedScrollView isKindOfClass:UICollectionView.class]) {
1390
- UIScrollView *preferred = [self findExplicitNativeScrollerInView:pageView];
1391
- if (preferred == nil) preferred = [self findFabricScrollViewInView:pageView];
1392
- shouldResolve = preferred != nil && preferred != _observedScrollView;
1488
+ // OneKey patch: a release must recover even when the changed parent is above
1489
+ // the page subtree. Missing lists keep their identity for a later insertion.
1490
+ if (_needsScrollObserverReattachAfterMount) {
1491
+ [self attachScrollObserverForCurrentPage];
1492
+ return;
1493
+ }
1494
+ UIView *page = _pageControllers[_currentIndex].view;
1495
+ BOOL headerIsAttached = [_sharedHeaderHostView isDescendantOfView:self];
1496
+ BOOL currentListIsAttached = _observedScrollView != nil &&
1497
+ [_observedScrollView isDescendantOfView:page] && headerIsAttached;
1498
+ BOOL currentListIsFallback = currentListIsAttached &&
1499
+ [_observedScrollView isKindOfClass:UICollectionView.class];
1500
+ if (currentListIsAttached && !currentListIsFallback &&
1501
+ _releasedPageScrollStates.count == 0) return;
1502
+ if (!headerIsAttached) {
1503
+ [self attachScrollObserverForCurrentPage];
1504
+ return;
1505
+ }
1506
+ // Property-only updates cannot replace a list. Collect changed parents first
1507
+ // so a burst of unrelated mutations scans the current page at most once.
1508
+ std::unordered_set<NSInteger> changedParentTags;
1509
+ for (const auto &mutation : transaction.getMutations()) {
1510
+ if ((mutation.type == ShadowViewMutation::Insert ||
1511
+ mutation.type == ShadowViewMutation::Remove) && mutation.parentTag > 0) {
1512
+ changedParentTags.insert(mutation.parentTag);
1513
+ }
1514
+ }
1515
+ if (changedParentTags.empty()) return;
1516
+
1517
+ // A missing list can return in a later transaction. Inspect only the current
1518
+ // page needing an observer and pages whose released identities are retained.
1519
+ for (NSInteger index = 0; index < _pageControllers.count; index++) {
1520
+ BOOL hasReleasedList = _releasedPageScrollStates[[self pageKeyForIndex:index]] != nil;
1521
+ BOOL currentPageNeedsResolve = index == _currentIndex &&
1522
+ (!currentListIsAttached || currentListIsFallback);
1523
+ if (!hasReleasedList && !currentPageNeedsResolve) continue;
1524
+ NSMutableArray<UIView *> *pendingViews =
1525
+ [NSMutableArray arrayWithObject:_pageControllers[index].view];
1526
+ while (pendingViews.count > 0) {
1527
+ UIView *view = pendingViews.lastObject;
1528
+ [pendingViews removeLastObject];
1529
+ if (changedParentTags.find(view.tag) != changedParentTags.end()) {
1530
+ if (hasReleasedList) _needsScrollObserverReattachAfterMount = YES;
1531
+ [self attachScrollObserverForCurrentPage];
1532
+ return;
1533
+ }
1534
+ [pendingViews addObjectsFromArray:view.subviews];
1535
+ }
1393
1536
  }
1394
- if (shouldResolve) [self attachScrollObserverForCurrentPage];
1395
1537
  }
1396
1538
 
1397
1539
  - (void)mountChildComponentView:(UIView<RCTComponentViewProtocol> *)childComponentView
@@ -1573,6 +1715,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1573
1715
  {
1574
1716
  _isBeingRecycled = YES;
1575
1717
  _generation++;
1718
+ _needsScrollObserverReattachAfterMount = NO;
1719
+ [_releasedPageScrollStates removeAllObjects];
1576
1720
  [self detachSharedHeaderPressCancellationGesture];
1577
1721
  [self detachScrollObserver];
1578
1722
  [self restoreSharedHeadersToContainer];
@@ -1943,6 +2087,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1943
2087
 
1944
2088
  - (void)attachScrollObserverForCurrentPage
1945
2089
  {
2090
+ [self attachScrollObserverForCurrentPageAfterTransition:NO];
2091
+ }
2092
+
2093
+ // OneKey patch: selection completion restores list state synchronously, before
2094
+ // the transition flags are cleared on the next run loop. Other callers wait.
2095
+ - (void)attachScrollObserverForCurrentPageAfterTransition:(BOOL)selectionSettled
2096
+ {
2097
+ if (_isBeingRecycled) return;
2098
+ if (_needsScrollObserverReattachAfterMount) {
2099
+ if (self.window == nil || ((_transitioning || _isPagerDragging) && !selectionSettled)) return;
2100
+ [self prepareAdjacentPageInsets];
2101
+ }
1946
2102
  [self restoreDetachedScrollInsets];
1947
2103
  if (_scrollResolveRetryPageIndex != _currentIndex) {
1948
2104
  _scrollResolveRetryToken += 1;
@@ -1956,7 +2112,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1956
2112
  if (candidate == nil) {
1957
2113
  [self detachScrollObserver];
1958
2114
  [self restoreSharedHeadersToContainer];
1959
- [self scheduleScrollObserverRetryForCurrentPage];
2115
+ // Stop retrying released lists on unrelated commits without discarding a
2116
+ // surviving identity or offset. A relevant insertion resumes recovery.
2117
+ if (_needsScrollObserverReattachAfterMount) {
2118
+ _needsScrollObserverReattachAfterMount = NO;
2119
+ _scrollResolveRetryToken += 1;
2120
+ _scrollResolveRetryScheduled = NO;
2121
+ _scrollResolveRetryAttempt = 0;
2122
+ _pendingFallbackScrollView = nil;
2123
+ _pendingFallbackScrollViewDetectedAt = 0;
2124
+ } else {
2125
+ [self scheduleScrollObserverRetryForCurrentPage];
2126
+ }
1960
2127
  return;
1961
2128
  }
1962
2129
  if ([candidate isKindOfClass:UICollectionView.class] &&
@@ -1966,7 +2133,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1966
2133
  _pendingFallbackScrollViewDetectedAt = CACurrentMediaTime();
1967
2134
  _scrollResolveRetryAttempt = 0;
1968
2135
  }
1969
- if (CACurrentMediaTime() - _pendingFallbackScrollViewDetectedAt < 0.5) {
2136
+ if (CACurrentMediaTime() - _pendingFallbackScrollViewDetectedAt <
2137
+ RNCCollapsiblePagerFallbackDebounceInterval) {
1970
2138
  [self scheduleScrollObserverRetryForCurrentPage];
1971
2139
  return;
1972
2140
  }
@@ -1983,6 +2151,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
1983
2151
  [candidate.panGestureRecognizer requireGestureRecognizerToFail:_pagerScrollView.panGestureRecognizer];
1984
2152
  }
1985
2153
  if (candidate == _observedScrollView) {
2154
+ _needsScrollObserverReattachAfterMount = NO;
1986
2155
  [self attachSharedHeadersToScrollView:candidate];
1987
2156
  [self updateSharedHeaderPressCancellationGesture];
1988
2157
  return;
@@ -2006,6 +2175,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2006
2175
  context:RNCCollapsiblePagerContentOffsetContext];
2007
2176
  [candidate.panGestureRecognizer addTarget:self action:@selector(observedListPanChanged:)];
2008
2177
  _observingContentOffset = YES;
2178
+ _needsScrollObserverReattachAfterMount = NO;
2009
2179
  _currentLogicalOffset = candidate.contentOffset.y + candidate.contentInset.top;
2010
2180
  [self attachSharedHeadersToScrollView:candidate];
2011
2181
  [self updateSharedHeaderPressCancellationGesture];
@@ -2235,6 +2405,39 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2235
2405
  }
2236
2406
  }
2237
2407
 
2408
+ // OneKey patch: release observers, headers and saved state while the scroll view
2409
+ // still represents the old list, before Fabric applies its next owner's props.
2410
+ - (void)restoreScrollViewState:(UIScrollView *)scrollView resetSavedOffset:(BOOL)resetSavedOffset
2411
+ {
2412
+ if (scrollView == _observedScrollView) {
2413
+ [self detachScrollObserver];
2414
+ if (resetSavedOffset) _pageOffsets[self.currentPageKey] = @(_headerOffset);
2415
+ }
2416
+ if (scrollView == _sharedHeaderScrollView) {
2417
+ [self restoreSharedHeadersToContainer];
2418
+ }
2419
+ CGFloat logicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
2420
+ UIEdgeInsets original = [_originalInsets objectForKey:scrollView].UIEdgeInsetsValue;
2421
+ NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
2422
+ if (appliedTop != nil && scrollView.refreshControl != nil) {
2423
+ original.top += MAX(0, scrollView.contentInset.top - appliedTop.doubleValue);
2424
+ }
2425
+ scrollView.contentInset = original;
2426
+ scrollView.verticalScrollIndicatorInsets =
2427
+ [_originalIndicatorInsets objectForKey:scrollView].UIEdgeInsetsValue;
2428
+ scrollView.alwaysBounceVertical =
2429
+ [_originalAlwaysBounceVertical objectForKey:scrollView].boolValue;
2430
+ scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)
2431
+ [_originalInsetAdjustmentBehavior objectForKey:scrollView].integerValue;
2432
+ [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, logicalOffset - original.top)
2433
+ animated:NO];
2434
+ [_originalInsets removeObjectForKey:scrollView];
2435
+ [_appliedTopInsets removeObjectForKey:scrollView];
2436
+ [_originalIndicatorInsets removeObjectForKey:scrollView];
2437
+ [_originalAlwaysBounceVertical removeObjectForKey:scrollView];
2438
+ [_originalInsetAdjustmentBehavior removeObjectForKey:scrollView];
2439
+ }
2440
+
2238
2441
  - (void)restoreDetachedScrollInsets
2239
2442
  {
2240
2443
  for (UIScrollView *scrollView in _originalInsets.keyEnumerator.allObjects) {
@@ -2246,32 +2449,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2246
2449
  }
2247
2450
  }
2248
2451
  if (belongsToPage) continue;
2249
- if (scrollView == _observedScrollView) {
2250
- [self detachScrollObserver];
2251
- _pageOffsets[self.currentPageKey] = @(_headerOffset);
2252
- }
2253
- // Fabric can reuse an empty RN ScrollView in another screen. Release all
2254
- // pager-owned insets before that view is used as a search or dialog list.
2255
- CGFloat logicalOffset = scrollView.contentOffset.y + scrollView.contentInset.top;
2256
- UIEdgeInsets original = [_originalInsets objectForKey:scrollView].UIEdgeInsetsValue;
2257
- NSNumber *appliedTop = [_appliedTopInsets objectForKey:scrollView];
2258
- if (appliedTop != nil && scrollView.refreshControl != nil) {
2259
- original.top += MAX(0, scrollView.contentInset.top - appliedTop.doubleValue);
2260
- }
2261
- scrollView.contentInset = original;
2262
- scrollView.verticalScrollIndicatorInsets =
2263
- [_originalIndicatorInsets objectForKey:scrollView].UIEdgeInsetsValue;
2264
- scrollView.alwaysBounceVertical =
2265
- [_originalAlwaysBounceVertical objectForKey:scrollView].boolValue;
2266
- scrollView.contentInsetAdjustmentBehavior = (UIScrollViewContentInsetAdjustmentBehavior)
2267
- [_originalInsetAdjustmentBehavior objectForKey:scrollView].integerValue;
2268
- [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, logicalOffset - original.top)
2269
- animated:NO];
2270
- [_originalInsets removeObjectForKey:scrollView];
2271
- [_appliedTopInsets removeObjectForKey:scrollView];
2272
- [_originalIndicatorInsets removeObjectForKey:scrollView];
2273
- [_originalAlwaysBounceVertical removeObjectForKey:scrollView];
2274
- [_originalInsetAdjustmentBehavior removeObjectForKey:scrollView];
2452
+ // OneKey patch: share the full release path with pre-mount recycling.
2453
+ [self restoreScrollViewState:scrollView resetSavedOffset:YES];
2275
2454
  }
2276
2455
  }
2277
2456
 
@@ -2279,6 +2458,57 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2279
2458
  pageIndex:(NSInteger)pageIndex
2280
2459
  restore:(BOOL)restore
2281
2460
  {
2461
+ // OneKey patch: a surviving list keeps its full offset. A replacement keeps
2462
+ // only header collapse, even if Fabric reused the same native scroll object.
2463
+ NSString *pageKey = [self pageKeyForIndex:pageIndex];
2464
+ RNCCollapsiblePagerReleasedScrollState *released = _releasedPageScrollStates[pageKey];
2465
+ BOOL deferredReleasedIdentity = NO;
2466
+ if (released != nil) {
2467
+ UIView *componentView = RNCScrollComponentView(scrollView);
2468
+ BOOL sameList = released.scrollView == scrollView && componentView != nil &&
2469
+ released.componentView == componentView && released.componentTag == componentView.tag;
2470
+ // Adjacent Nitro lists are visible only as generic UICollectionViews, so
2471
+ // debounce each replacement candidate independently before rejecting the
2472
+ // released identity. A surviving native list is safe to accept immediately.
2473
+ BOOL needsFallbackDebounce = !sameList &&
2474
+ [scrollView isKindOfClass:UICollectionView.class] && scrollView != _observedScrollView;
2475
+ if (needsFallbackDebounce) {
2476
+ CFTimeInterval now = CACurrentMediaTime();
2477
+ if (released.pendingFallbackScrollView != scrollView) {
2478
+ released.pendingFallbackScrollView = scrollView;
2479
+ released.pendingFallbackDetectedAt = now;
2480
+ __weak __typeof__(self) weakSelf = self;
2481
+ __weak RNCCollapsiblePagerReleasedScrollState *weakReleased = released;
2482
+ __weak UIScrollView *weakScrollView = scrollView;
2483
+ NSString *capturedPageKey = [pageKey copy];
2484
+ NSUInteger capturedGeneration = _generation;
2485
+ dispatch_after(
2486
+ dispatch_time(
2487
+ DISPATCH_TIME_NOW,
2488
+ (int64_t)(RNCCollapsiblePagerFallbackDebounceInterval * NSEC_PER_SEC)
2489
+ ),
2490
+ dispatch_get_main_queue(),
2491
+ ^{
2492
+ RNCCollapsiblePagerViewComponentView *strongSelf = weakSelf;
2493
+ RNCCollapsiblePagerReleasedScrollState *strongReleased = weakReleased;
2494
+ UIScrollView *strongScrollView = weakScrollView;
2495
+ if (strongSelf == nil || strongReleased == nil || strongScrollView == nil ||
2496
+ strongSelf->_isBeingRecycled || strongSelf.window == nil ||
2497
+ strongSelf->_generation != capturedGeneration ||
2498
+ strongSelf->_releasedPageScrollStates[capturedPageKey] != strongReleased ||
2499
+ strongReleased.pendingFallbackScrollView != strongScrollView) return;
2500
+ [strongSelf prepareAdjacentPageInsets];
2501
+ }
2502
+ );
2503
+ }
2504
+ deferredReleasedIdentity = now - released.pendingFallbackDetectedAt <
2505
+ RNCCollapsiblePagerFallbackDebounceInterval;
2506
+ }
2507
+ if (!deferredReleasedIdentity) {
2508
+ if (!sameList) _pageOffsets[pageKey] = @(_headerOffset);
2509
+ [_releasedPageScrollStates removeObjectForKey:pageKey];
2510
+ }
2511
+ }
2282
2512
  NSValue *storedOriginal = [_originalInsets objectForKey:scrollView];
2283
2513
  UIEdgeInsets original = storedOriginal == nil
2284
2514
  ? scrollView.contentInset
@@ -2327,7 +2557,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2327
2557
  NSNumber *saved = _pageOffsets[[self pageKeyForIndex:pageIndex]];
2328
2558
  // The sticky slot can change height between pages. Cache offsets relative
2329
2559
  // to content start so a round trip does not add that height difference.
2330
- CGFloat restoredLogicalOffset = saved == nil ? _headerOffset : saved.doubleValue;
2560
+ CGFloat restoredLogicalOffset = deferredReleasedIdentity || saved == nil
2561
+ ? _headerOffset
2562
+ : saved.doubleValue;
2331
2563
  // While the shared header is visible, retained pages must agree on its
2332
2564
  // collapse position. Deep offsets remain independent after full collapse.
2333
2565
  if (_nativeSmoothHeaderScrollEnabled && _headerOffset < _headerHeight) {
@@ -2483,7 +2715,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2483
2715
  self->_currentIndex = index;
2484
2716
  self->_destinationIndex = index;
2485
2717
  [self->_nativeTabBarView setProgress:index];
2486
- [self attachScrollObserverForCurrentPage];
2718
+ [self attachScrollObserverForCurrentPageAfterTransition:YES];
2487
2719
  [self emitPageSelected:index];
2488
2720
  [self emitDiagnostics:@"page-selected"];
2489
2721
  } else if (self->_pendingGoToIndex < 0) {
@@ -2520,7 +2752,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2520
2752
  self->_currentIndex = index;
2521
2753
  self->_destinationIndex = index;
2522
2754
  [self->_nativeTabBarView setProgress:index];
2523
- [self attachScrollObserverForCurrentPage];
2755
+ [self attachScrollObserverForCurrentPageAfterTransition:YES];
2524
2756
  [self emitPageSelected:index];
2525
2757
  [self emitDiagnostics:@"page-selected"];
2526
2758
  } else {
@@ -2660,7 +2892,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2660
2892
  0
2661
2893
  ) animated:NO];
2662
2894
  [_nativeTabBarView setProgress:index];
2663
- [self attachScrollObserverForCurrentPage];
2895
+ [self attachScrollObserverForCurrentPageAfterTransition:YES];
2664
2896
  if (emitSelection &&
2665
2897
  (_pendingGoToIndex < 0 || _pendingGoToIndex == index)) {
2666
2898
  [self emitPageSelected:index];
@@ -2711,7 +2943,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
2711
2943
  _currentIndex = index;
2712
2944
  _destinationIndex = index;
2713
2945
  [_nativeTabBarView setProgress:index];
2714
- [self attachScrollObserverForCurrentPage];
2946
+ [self attachScrollObserverForCurrentPageAfterTransition:YES];
2715
2947
  // A newer tab tap remains authoritative while its queued command runs.
2716
2948
  if (emitSelection &&
2717
2949
  (_pendingGoToIndex < 0 || _pendingGoToIndex == index)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onekeyfe/react-native-pager-view",
3
- "version": "3.0.133",
3
+ "version": "3.0.134",
4
4
  "description": "React Native wrapper for Android and iOS ViewPager",
5
5
  "source": "./src/index.tsx",
6
6
  "main": "./lib/module/index.js",
@@ -66,7 +66,7 @@
66
66
  "typescript": "^5.9.2"
67
67
  },
68
68
  "peerDependencies": {
69
- "@onekeyfe/react-native-native-logger": "3.0.133",
69
+ "@onekeyfe/react-native-native-logger": "3.0.134",
70
70
  "react": "*",
71
71
  "react-native": "*"
72
72
  },