@onekeyfe/react-native-pager-view 6.7.0-22 → 6.7.0-24
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/README.md +16 -0
- package/android/build.gradle +1 -0
- package/android/src/androidTest/java/com/reactnativepagerview/CollapsiblePagerTabReservationTest.kt +170 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +88 -12
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +447 -15
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerViewManager.kt +11 -1
- package/android/src/main/java/com/reactnativepagerview/event/NativeHeaderPressEvent.kt +8 -0
- package/docs/SPEC.md +333 -3
- package/ios/RNCCollapsiblePagerViewComponentView.mm +847 -21
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +116 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +22 -0
- package/lib/module/CollapsiblePagerView.js +37 -5
- package/lib/module/CollapsiblePagerView.web.js +520 -6
- package/lib/module/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/lib/module/NativeScrollerNativeComponent.ts +0 -1
- package/lib/typescript/src/CollapsiblePagerView.d.ts +31 -2
- package/lib/typescript/src/CollapsiblePagerView.web.d.ts +44 -1
- package/lib/typescript/src/CollapsiblePagerViewNativeComponent.d.ts +11 -0
- package/lib/typescript/src/NativeScroller.d.ts +2 -2
- package/package.json +3 -2
- package/src/CollapsiblePagerView.tsx +68 -5
- package/src/CollapsiblePagerView.web.tsx +465 -5
- package/src/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/src/NativeScroller.tsx +2 -3
- package/src/NativeScrollerNativeComponent.ts +0 -1
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
#import "React/RCTSurfaceTouchHandler.h"
|
|
15
15
|
#import "React/RCTTouchHandler.h"
|
|
16
16
|
#import <objc/runtime.h>
|
|
17
|
+
#import <React/RCTConvert.h>
|
|
18
|
+
#import <React/RCTFont.h>
|
|
17
19
|
#import <UIKit/UIGestureRecognizerSubclass.h>
|
|
18
20
|
|
|
19
21
|
// OneKey patch: deduplicate structural mutation parents before scanning the page.
|
|
@@ -398,8 +400,65 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
398
400
|
(long)pagerState]);
|
|
399
401
|
}
|
|
400
402
|
|
|
403
|
+
static CGFloat RNCPagerToolbarDimension(NSDictionary *entry, NSString *key, CGFloat fallback, CGFloat minimum, CGFloat maximum)
|
|
404
|
+
{
|
|
405
|
+
id value = entry[key];
|
|
406
|
+
if (![value isKindOfClass:NSNumber.class]) return fallback;
|
|
407
|
+
double dimension = [value doubleValue];
|
|
408
|
+
return std::isfinite(dimension) ? MIN(maximum, MAX(minimum, dimension)) : fallback;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
@protocol RNCPagerNamedSVGBridge <NSObject>
|
|
412
|
+
+ (void)cancelWithOwner:(id)owner;
|
|
413
|
+
+ (void)requestWithOwner:(id)owner name:(NSString *)name color:(NSString *)color
|
|
414
|
+
width:(NSInteger)width height:(NSInteger)height scale:(double)scale
|
|
415
|
+
complete:(void (^)(UIImage *))complete;
|
|
416
|
+
@end
|
|
417
|
+
|
|
418
|
+
@interface RNCPagerToolbarButton : UIButton
|
|
419
|
+
@property (nonatomic, strong) UIColor *baseColor;
|
|
420
|
+
@property (nonatomic, strong) UIColor *hoverColor;
|
|
421
|
+
@property (nonatomic, strong) UIColor *pressedColor;
|
|
422
|
+
@property (nonatomic) BOOL hovering;
|
|
423
|
+
@property (nonatomic) CGFloat iconSize;
|
|
424
|
+
- (void)refreshBackground;
|
|
425
|
+
@end
|
|
426
|
+
@implementation RNCPagerToolbarButton
|
|
427
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
428
|
+
if ((self = [super initWithFrame:frame])) {
|
|
429
|
+
self.adjustsImageWhenHighlighted = NO;
|
|
430
|
+
[self addGestureRecognizer:[[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hover:)]];
|
|
431
|
+
}
|
|
432
|
+
return self;
|
|
433
|
+
}
|
|
434
|
+
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
|
|
435
|
+
CGFloat side = MIN(self.iconSize, MIN(CGRectGetWidth(contentRect), CGRectGetHeight(contentRect)));
|
|
436
|
+
return CGRectMake(CGRectGetMidX(contentRect) - side / 2, CGRectGetMidY(contentRect) - side / 2, side, side);
|
|
437
|
+
}
|
|
438
|
+
- (void)hover:(UIHoverGestureRecognizer *)gesture {
|
|
439
|
+
self.hovering = gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged;
|
|
440
|
+
[self refreshBackground];
|
|
441
|
+
}
|
|
442
|
+
- (void)setEnabled:(BOOL)enabled {
|
|
443
|
+
[super setEnabled:enabled];
|
|
444
|
+
if (!enabled) self.hovering = NO;
|
|
445
|
+
[self refreshBackground];
|
|
446
|
+
}
|
|
447
|
+
- (void)setHighlighted:(BOOL)highlighted {
|
|
448
|
+
[super setHighlighted:highlighted];
|
|
449
|
+
[self refreshBackground];
|
|
450
|
+
}
|
|
451
|
+
- (void)refreshBackground {
|
|
452
|
+
self.backgroundColor = self.enabled && self.highlighted ? self.pressedColor : (self.enabled && self.hovering ? self.hoverColor : self.baseColor);
|
|
453
|
+
}
|
|
454
|
+
@end
|
|
455
|
+
|
|
401
456
|
@interface RNCCollapsiblePagerNativeTabBarView : UIView <UIScrollViewDelegate>
|
|
402
457
|
@property (nonatomic, copy) RNCCollapsiblePagerNativeTabPressHandler onTabPress;
|
|
458
|
+
@property (nonatomic, copy) void (^onToolbarPress)(NSString *, CGRect);
|
|
459
|
+
@property (nonatomic, copy) void (^onToolbarNeedsSettlement)(void);
|
|
460
|
+
- (void)updateToolbarPagerIndex:(NSInteger)index busy:(BOOL)busy;
|
|
461
|
+
- (void)updateConfigJSON:(NSString *)json;
|
|
403
462
|
- (void)updateItemsJSON:(NSString *)itemsJSON;
|
|
404
463
|
- (void)updateStyleWithHeight:(CGFloat)height
|
|
405
464
|
variant:(NSString *)variant
|
|
@@ -415,6 +474,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
415
474
|
indicatorHeight:(CGFloat)indicatorHeight
|
|
416
475
|
indicatorBottom:(CGFloat)indicatorBottom;
|
|
417
476
|
- (void)setProgress:(CGFloat)progress;
|
|
477
|
+
- (BOOL)usesControlledTextProgress;
|
|
478
|
+
- (BOOL)ownsSettledToolbar;
|
|
418
479
|
- (void)setLeftToRight:(BOOL)leftToRight;
|
|
419
480
|
- (void)updateAncestorGesturePrecedence;
|
|
420
481
|
@end
|
|
@@ -430,6 +491,40 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
430
491
|
CGFloat _contentPaddingHorizontal;
|
|
431
492
|
CGFloat _contentPaddingVertical;
|
|
432
493
|
BOOL _segmented;
|
|
494
|
+
BOOL _textVariant;
|
|
495
|
+
NSDictionary *_config;
|
|
496
|
+
RNCPagerToolbarButton *_toolbarButton;
|
|
497
|
+
NSString *_toolbarKey;
|
|
498
|
+
BOOL _toolbarPagerBusy;
|
|
499
|
+
NSInteger _toolbarPageIndex;
|
|
500
|
+
NSDictionary *_committedToolbarEntry;
|
|
501
|
+
NSString *_desiredToolbarKey;
|
|
502
|
+
NSDictionary *_desiredToolbarEntry;
|
|
503
|
+
UIImage *_loadedToolbarImage;
|
|
504
|
+
BOOL _toolbarPresenceAnimating;
|
|
505
|
+
BOOL _toolbarPresenceInterrupted;
|
|
506
|
+
NSString *_toolbarTouchKey;
|
|
507
|
+
BOOL _toolbarTouchStarted;
|
|
508
|
+
NSString *_iconIdentity;
|
|
509
|
+
NSObject *_iconOwner;
|
|
510
|
+
NSInteger _progressSource;
|
|
511
|
+
NSInteger _progressDestination;
|
|
512
|
+
CGFloat _progressFraction;
|
|
513
|
+
CGFloat _nativeProgress;
|
|
514
|
+
BOOL _hasNativeProgress;
|
|
515
|
+
BOOL _hasMappedPageProgress;
|
|
516
|
+
CGFloat _toolbarReservedWidth;
|
|
517
|
+
BOOL _toolbarVisible;
|
|
518
|
+
NSUInteger _toolbarAnimationGeneration;
|
|
519
|
+
NSString *_loadedIconIdentity;
|
|
520
|
+
BOOL _iconRequestPending;
|
|
521
|
+
UIImage *_readyToolbarImage;
|
|
522
|
+
NSString *_readyToolbarIdentity;
|
|
523
|
+
NSString *_failedIconIdentity;
|
|
524
|
+
CGFloat _toolbarSize;
|
|
525
|
+
CGFloat _toolbarIconSize;
|
|
526
|
+
CGFloat _toolbarContentWidth;
|
|
527
|
+
CGFloat _toolbarTrailingInset;
|
|
433
528
|
CGFloat _itemSpacing;
|
|
434
529
|
CGFloat _fontSize;
|
|
435
530
|
NSString *_fontFamily;
|
|
@@ -448,6 +543,21 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
448
543
|
{
|
|
449
544
|
if (self = [super initWithFrame:frame]) {
|
|
450
545
|
_items = @[];
|
|
546
|
+
_config = @{};
|
|
547
|
+
_toolbarPagerBusy = YES;
|
|
548
|
+
_toolbarPageIndex = -1;
|
|
549
|
+
_toolbarButton = [RNCPagerToolbarButton new];
|
|
550
|
+
_toolbarButton.layer.cornerRadius = 19;
|
|
551
|
+
_toolbarButton.hidden = YES;
|
|
552
|
+
_toolbarButton.alpha = 0;
|
|
553
|
+
_toolbarButton.userInteractionEnabled = NO;
|
|
554
|
+
_toolbarButton.accessibilityElementsHidden = YES;
|
|
555
|
+
_toolbarButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
|
556
|
+
_toolbarButton.iconSize = 24;
|
|
557
|
+
[_toolbarButton addTarget:self action:@selector(toolbarTouchDown) forControlEvents:UIControlEventTouchDown];
|
|
558
|
+
[_toolbarButton addTarget:self action:@selector(toolbarPress:forEvent:) forControlEvents:UIControlEventTouchUpInside];
|
|
559
|
+
[_toolbarButton addTarget:self action:@selector(toolbarCancel) forControlEvents:UIControlEventTouchCancel | UIControlEventTouchUpOutside];
|
|
560
|
+
[self addSubview:_toolbarButton];
|
|
451
561
|
_buttons = [NSMutableArray new];
|
|
452
562
|
_indicatorFrames = [NSMutableArray new];
|
|
453
563
|
_barHeight = 44;
|
|
@@ -495,6 +605,18 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
495
605
|
return [UIFont systemFontOfSize:_fontSize weight:UIFontWeightMedium];
|
|
496
606
|
}
|
|
497
607
|
|
|
608
|
+
- (UIFont *)activeTabFont
|
|
609
|
+
{
|
|
610
|
+
NSString *activeFamily = _config[@"activeFontFamily"];
|
|
611
|
+
if (activeFamily.length > 0) {
|
|
612
|
+
return [UIFont fontWithName:activeFamily size:_fontSize]
|
|
613
|
+
?: [UIFont systemFontOfSize:_fontSize weight:UIFontWeightSemibold];
|
|
614
|
+
}
|
|
615
|
+
UIFont *normalFont = _fontFamily.length > 0 ? [UIFont fontWithName:_fontFamily size:_fontSize] : nil;
|
|
616
|
+
return normalFont != nil ? [RCTFont updateFont:normalFont withWeight:@"600"]
|
|
617
|
+
: [UIFont systemFontOfSize:_fontSize weight:UIFontWeightSemibold];
|
|
618
|
+
}
|
|
619
|
+
|
|
498
620
|
- (void)updateItemsJSON:(NSString *)itemsJSON
|
|
499
621
|
{
|
|
500
622
|
NSData *data = [itemsJSON dataUsingEncoding:NSUTF8StringEncoding];
|
|
@@ -529,6 +651,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
529
651
|
for (UIButton *button in _buttons) [_scrollView bringSubviewToFront:button];
|
|
530
652
|
_shouldCenterSelectedItem = YES;
|
|
531
653
|
self.hidden = _items.count == 0;
|
|
654
|
+
if (_hasNativeProgress) [self setProgress:_nativeProgress];
|
|
655
|
+
else if ([_config[@"controlled"] boolValue]) {
|
|
656
|
+
NSUInteger selected = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:self->_config[@"selectedKey"]]; }];
|
|
657
|
+
if (selected != NSNotFound) { _progress = selected; _progressSource = selected; _progressDestination = selected; _progressFraction = 0; }
|
|
658
|
+
}
|
|
532
659
|
[self setNeedsLayout];
|
|
533
660
|
}
|
|
534
661
|
|
|
@@ -549,13 +676,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
549
676
|
_barHeight = MAX(1, height);
|
|
550
677
|
BOOL wasSegmented = _segmented;
|
|
551
678
|
_segmented = [variant isEqualToString:@"segmented"];
|
|
679
|
+
_textVariant = [variant isEqualToString:@"text"];
|
|
552
680
|
_contentPaddingHorizontal = MAX(0, contentPaddingHorizontal);
|
|
553
681
|
_contentPaddingVertical = MAX(0, contentPaddingVertical);
|
|
554
682
|
_itemSpacing = MAX(0, itemSpacing);
|
|
555
683
|
_fontSize = MAX(1, fontSize);
|
|
556
684
|
_fontFamily = [fontFamily copy];
|
|
557
685
|
self.backgroundColor = _segmented ? UIColor.clearColor : (backgroundColor ?: UIColor.clearColor);
|
|
558
|
-
_scrollView.backgroundColor = self.backgroundColor;
|
|
686
|
+
_scrollView.backgroundColor = _textVariant ? UIColor.clearColor : self.backgroundColor;
|
|
559
687
|
_scrollView.scrollEnabled = !_segmented;
|
|
560
688
|
if (_segmented && !wasSegmented) [_scrollView setContentOffset:CGPointZero animated:NO];
|
|
561
689
|
_trackView.hidden = !_segmented;
|
|
@@ -624,9 +752,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
624
752
|
- (void)layoutSubviews
|
|
625
753
|
{
|
|
626
754
|
[super layoutSubviews];
|
|
627
|
-
|
|
755
|
+
CGFloat reserved = _toolbarReservedWidth;
|
|
756
|
+
[self bringSubviewToFront:_toolbarButton];
|
|
757
|
+
_scrollView.frame = CGRectMake(_leftToRight ? 0 : reserved, 0,
|
|
758
|
+
MAX(0, CGRectGetWidth(self.bounds) - reserved), CGRectGetHeight(self.bounds));
|
|
759
|
+
CGFloat inset = _toolbarTrailingInset - (_toolbarSize - _toolbarContentWidth) / 2;
|
|
760
|
+
_toolbarButton.frame = CGRectMake(_leftToRight ? CGRectGetWidth(self.bounds) - inset - _toolbarSize : inset,
|
|
761
|
+
(CGRectGetHeight(self.bounds) - _toolbarSize) / 2, _toolbarSize, _toolbarSize);
|
|
628
762
|
if (_segmented) {
|
|
629
|
-
CGFloat width = CGRectGetWidth(
|
|
763
|
+
CGFloat width = CGRectGetWidth(_scrollView.bounds);
|
|
630
764
|
CGFloat height = CGRectGetHeight(self.bounds);
|
|
631
765
|
CGFloat insetX = MIN(_contentPaddingHorizontal, width / 2);
|
|
632
766
|
CGFloat insetY = MIN(_contentPaddingVertical, height / 2);
|
|
@@ -634,7 +768,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
634
768
|
MAX(0, width - 2 * insetX), MAX(0, height - 2 * insetY));
|
|
635
769
|
_trackView.frame = trackFrame;
|
|
636
770
|
_trackView.layer.cornerRadius = CGRectGetHeight(trackFrame) / 2;
|
|
637
|
-
_scrollView.contentSize =
|
|
771
|
+
_scrollView.contentSize = _scrollView.bounds.size;
|
|
638
772
|
[_indicatorFrames removeAllObjects];
|
|
639
773
|
NSUInteger count = _buttons.count;
|
|
640
774
|
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
@@ -658,12 +792,16 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
658
792
|
for (UIButton *button in _buttons) {
|
|
659
793
|
NSString *title = [button titleForState:UIControlStateNormal] ?: @"";
|
|
660
794
|
CGFloat textWidth = ceil([title sizeWithAttributes:@{NSFontAttributeName: font}].width);
|
|
661
|
-
|
|
795
|
+
if (_textVariant) {
|
|
796
|
+
UIFont *activeFont = self.activeTabFont;
|
|
797
|
+
textWidth = MAX(textWidth, ceil([title sizeWithAttributes:@{NSFontAttributeName: activeFont}].width));
|
|
798
|
+
}
|
|
799
|
+
CGFloat buttonWidth = MAX(44, textWidth + (_textVariant ? 20 : 16));
|
|
662
800
|
[widths addObject:@(buttonWidth)];
|
|
663
801
|
itemsWidth += buttonWidth;
|
|
664
802
|
}
|
|
665
803
|
if (_buttons.count > 1) itemsWidth += (_buttons.count - 1) * _itemSpacing;
|
|
666
|
-
CGFloat contentWidth = MAX(CGRectGetWidth(
|
|
804
|
+
CGFloat contentWidth = MAX(CGRectGetWidth(_scrollView.bounds),
|
|
667
805
|
itemsWidth + _contentPaddingHorizontal * 2);
|
|
668
806
|
_scrollView.contentSize = CGSizeMake(contentWidth, MAX(_barHeight, CGRectGetHeight(self.bounds)));
|
|
669
807
|
[_indicatorFrames removeAllObjects];
|
|
@@ -693,14 +831,493 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
693
831
|
[self updatePresentation];
|
|
694
832
|
}
|
|
695
833
|
|
|
834
|
+
- (Class<RNCPagerNamedSVGBridge>)iconBridge {
|
|
835
|
+
Class cls = NSClassFromString(@"OneKeyNamedSVGBridge");
|
|
836
|
+
if (![cls respondsToSelector:@selector(cancelWithOwner:)] ||
|
|
837
|
+
![cls respondsToSelector:@selector(requestWithOwner:name:color:width:height:scale:complete:)]) return Nil;
|
|
838
|
+
return (Class<RNCPagerNamedSVGBridge>)cls;
|
|
839
|
+
}
|
|
840
|
+
- (void)updateConfigJSON:(NSString *)json {
|
|
841
|
+
BOOL wasOwned = [self ownsSettledToolbar];
|
|
842
|
+
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
|
|
843
|
+
id parsed = data ? [NSJSONSerialization JSONObjectWithData:data options:0 error:nil] : nil;
|
|
844
|
+
_config = [parsed isKindOfClass:NSDictionary.class] ? parsed : @{};
|
|
845
|
+
BOOL owned = [self ownsSettledToolbar];
|
|
846
|
+
if (owned != wasOwned) {
|
|
847
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
848
|
+
_iconOwner = [NSObject new];
|
|
849
|
+
_iconRequestPending = NO;
|
|
850
|
+
_readyToolbarImage = nil;
|
|
851
|
+
_readyToolbarIdentity = nil;
|
|
852
|
+
_desiredToolbarKey = nil;
|
|
853
|
+
_desiredToolbarEntry = nil;
|
|
854
|
+
[self suspendToolbar];
|
|
855
|
+
if (!owned) {
|
|
856
|
+
++_toolbarAnimationGeneration;
|
|
857
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
858
|
+
_toolbarPresenceAnimating = NO;
|
|
859
|
+
_toolbarPresenceInterrupted = NO;
|
|
860
|
+
_toolbarButton.alpha = _toolbarVisible ? 1 : 0;
|
|
861
|
+
_toolbarButton.hidden = !_toolbarVisible;
|
|
862
|
+
[self setToolbarInteractive:_toolbarVisible];
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
if (!owned) {
|
|
866
|
+
_toolbarTouchKey = nil;
|
|
867
|
+
_toolbarButton.highlighted = NO;
|
|
868
|
+
_toolbarButton.hovering = NO;
|
|
869
|
+
}
|
|
870
|
+
_toolbarButton.adjustsImageWhenDisabled = !owned;
|
|
871
|
+
NSString *key = _config[@"selectedKey"];
|
|
872
|
+
if (![_config[@"controlled"] boolValue]) {
|
|
873
|
+
_progress = RNCClamp(_nativeProgress, 0, MAX(0, (NSInteger)_items.count - 1));
|
|
874
|
+
key = _items.count ? _items[(NSInteger)round(_progress)][@"key"] : nil;
|
|
875
|
+
}
|
|
876
|
+
if (_hasNativeProgress) {
|
|
877
|
+
[self setProgress:_nativeProgress];
|
|
878
|
+
} else {
|
|
879
|
+
NSUInteger index = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:key]; }];
|
|
880
|
+
if (index != NSNotFound) {
|
|
881
|
+
_progress = index;
|
|
882
|
+
_progressSource = index;
|
|
883
|
+
_progressDestination = index;
|
|
884
|
+
_progressFraction = 0;
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
[self updateToolbarForKey:key];
|
|
888
|
+
[self setNeedsLayout];
|
|
889
|
+
}
|
|
890
|
+
- (BOOL)usesControlledTextProgress { return _textVariant && [_config[@"controlled"] boolValue]; }
|
|
891
|
+
- (void)setLegacyToolbarVisible:(BOOL)visible {
|
|
892
|
+
_toolbarButton.enabled = visible;
|
|
893
|
+
_toolbarButton.userInteractionEnabled = visible;
|
|
894
|
+
_toolbarButton.accessibilityElementsHidden = !visible;
|
|
895
|
+
_toolbarButton.isAccessibilityElement = visible;
|
|
896
|
+
if (_toolbarVisible == visible) return;
|
|
897
|
+
_toolbarVisible = visible;
|
|
898
|
+
NSUInteger generation = ++_toolbarAnimationGeneration;
|
|
899
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
900
|
+
CGFloat alpha = presentation ? presentation.opacity : _toolbarButton.alpha;
|
|
901
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
902
|
+
_toolbarButton.alpha = alpha;
|
|
903
|
+
_toolbarButton.enabled = visible;
|
|
904
|
+
_toolbarButton.userInteractionEnabled = visible;
|
|
905
|
+
_toolbarButton.accessibilityElementsHidden = !visible;
|
|
906
|
+
if (visible) _toolbarButton.hidden = NO;
|
|
907
|
+
if (self.window == nil) {
|
|
908
|
+
_toolbarButton.alpha = visible ? 1 : 0;
|
|
909
|
+
_toolbarButton.hidden = !visible;
|
|
910
|
+
return;
|
|
911
|
+
}
|
|
912
|
+
[UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
|
|
913
|
+
self->_toolbarButton.alpha = visible ? 1 : 0;
|
|
914
|
+
} completion:^(BOOL finished) {
|
|
915
|
+
if (self->_toolbarAnimationGeneration == generation && !visible) {
|
|
916
|
+
self->_toolbarButton.hidden = YES;
|
|
917
|
+
[self applyReadyLegacyToolbarImage];
|
|
918
|
+
}
|
|
919
|
+
}];
|
|
920
|
+
}
|
|
921
|
+
- (void)applyReadyLegacyToolbarImage {
|
|
922
|
+
if (_readyToolbarImage == nil || ![_readyToolbarIdentity isEqualToString:_iconIdentity]) return;
|
|
923
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
924
|
+
if (_toolbarButton.alpha > 0.001 || (presentation && presentation.opacity > 0.001)) return;
|
|
925
|
+
[_toolbarButton setImage:_readyToolbarImage forState:UIControlStateNormal];
|
|
926
|
+
_loadedIconIdentity = _readyToolbarIdentity;
|
|
927
|
+
_loadedToolbarImage = _readyToolbarImage;
|
|
928
|
+
_readyToolbarImage = nil;
|
|
929
|
+
_readyToolbarIdentity = nil;
|
|
930
|
+
_iconRequestPending = NO;
|
|
931
|
+
[self setLegacyToolbarVisible:YES];
|
|
932
|
+
}
|
|
933
|
+
- (void)updateLegacyToolbarForKey:(NSString *)key {
|
|
934
|
+
NSDictionary *toolbar = [_config[@"toolbar"] isKindOfClass:NSDictionary.class] ? _config[@"toolbar"] : nil;
|
|
935
|
+
_toolbarReservedWidth = toolbar == nil ? 0 : 44;
|
|
936
|
+
if (toolbar.count > 0) {
|
|
937
|
+
_toolbarReservedWidth = 0;
|
|
938
|
+
for (id value in toolbar.allValues) {
|
|
939
|
+
NSDictionary *entry = [value isKindOfClass:NSDictionary.class] ? value : @{};
|
|
940
|
+
CGFloat size = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
941
|
+
CGFloat content = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, size), 0, size);
|
|
942
|
+
CGFloat trailing = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
943
|
+
_toolbarReservedWidth = MAX(_toolbarReservedWidth, content + trailing);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
NSDictionary *entry = [toolbar[key ?: @""] isKindOfClass:NSDictionary.class] ? toolbar[key] : nil;
|
|
947
|
+
NSString *name = [entry[@"iconName"] isKindOfClass:NSString.class] ? entry[@"iconName"] : nil;
|
|
948
|
+
if (![_toolbarKey isEqualToString:key]) {
|
|
949
|
+
_toolbarTouchKey = nil;
|
|
950
|
+
_toolbarButton.highlighted = NO;
|
|
951
|
+
_toolbarButton.hovering = NO;
|
|
952
|
+
}
|
|
953
|
+
_toolbarKey = [key copy];
|
|
954
|
+
if (name.length == 0) {
|
|
955
|
+
_toolbarTouchKey = nil;
|
|
956
|
+
_toolbarButton.accessibilityLabel = nil;
|
|
957
|
+
_toolbarButton.accessibilityIdentifier = nil;
|
|
958
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
959
|
+
_iconOwner = [NSObject new];
|
|
960
|
+
_iconRequestPending = NO;
|
|
961
|
+
_readyToolbarImage = nil;
|
|
962
|
+
_readyToolbarIdentity = nil;
|
|
963
|
+
[self setLegacyToolbarVisible:NO];
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
_toolbarSize = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
967
|
+
_toolbarIconSize = RNCPagerToolbarDimension(entry, @"iconSize", MIN(24, _toolbarSize), 1, _toolbarSize);
|
|
968
|
+
_toolbarContentWidth = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, _toolbarSize), 0, _toolbarSize);
|
|
969
|
+
_toolbarTrailingInset = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
970
|
+
_toolbarButton.layer.cornerRadius = _toolbarSize / 2;
|
|
971
|
+
_toolbarButton.iconSize = _toolbarIconSize;
|
|
972
|
+
[_toolbarButton setNeedsLayout];
|
|
973
|
+
_toolbarButton.baseColor = [entry[@"selected"] boolValue] ? ([RCTConvert UIColor:entry[@"selectedBackgroundColor"]] ?: UIColor.clearColor) : UIColor.clearColor;
|
|
974
|
+
_toolbarButton.hoverColor = [RCTConvert UIColor:entry[@"hoverBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
975
|
+
_toolbarButton.pressedColor = [RCTConvert UIColor:entry[@"pressedBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
976
|
+
[_toolbarButton refreshBackground];
|
|
977
|
+
_toolbarButton.accessibilityLabel = entry[@"accessibilityLabel"];
|
|
978
|
+
_toolbarButton.accessibilityIdentifier = entry[@"testID"];
|
|
979
|
+
NSString *color = [entry[@"iconColor"] isKindOfClass:NSString.class] ? entry[@"iconColor"] : @"#000000";
|
|
980
|
+
CGFloat scale = self.window.screen.scale ?: UIScreen.mainScreen.scale;
|
|
981
|
+
NSString *identity = [NSString stringWithFormat:@"%@|%@|%g|%g", name, color, scale, _toolbarIconSize];
|
|
982
|
+
if ([_loadedIconIdentity isEqualToString:identity] && [_toolbarButton imageForState:UIControlStateNormal] != nil) {
|
|
983
|
+
if (_iconRequestPending) {
|
|
984
|
+
[[self iconBridge] cancelWithOwner:_iconOwner];
|
|
985
|
+
_iconOwner = [NSObject new];
|
|
986
|
+
}
|
|
987
|
+
_iconIdentity = identity;
|
|
988
|
+
_iconRequestPending = NO;
|
|
989
|
+
_readyToolbarImage = nil;
|
|
990
|
+
_readyToolbarIdentity = nil;
|
|
991
|
+
[self setLegacyToolbarVisible:YES];
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
if ((_iconRequestPending || [_failedIconIdentity isEqualToString:identity]) && [_iconIdentity isEqualToString:identity]) return;
|
|
995
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
996
|
+
NSObject *owner = [NSObject new];
|
|
997
|
+
_iconOwner = owner;
|
|
998
|
+
_iconIdentity = identity;
|
|
999
|
+
_iconRequestPending = NO;
|
|
1000
|
+
_readyToolbarImage = nil;
|
|
1001
|
+
_readyToolbarIdentity = nil;
|
|
1002
|
+
[self setLegacyToolbarVisible:NO];
|
|
1003
|
+
if ([_failedIconIdentity isEqualToString:identity] || self.window == nil) return;
|
|
1004
|
+
_iconRequestPending = YES;
|
|
1005
|
+
__weak __typeof__(self) weakSelf = self;
|
|
1006
|
+
[[self iconBridge] requestWithOwner:owner name:name color:color width:(NSInteger)ceil(_toolbarIconSize * scale) height:(NSInteger)ceil(_toolbarIconSize * scale) scale:scale complete:^(UIImage *image) {
|
|
1007
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
1008
|
+
if (!self || self->_iconOwner != owner || ![self->_iconIdentity isEqualToString:identity] || !self.window) return;
|
|
1009
|
+
if (image == nil) {
|
|
1010
|
+
self->_iconRequestPending = NO;
|
|
1011
|
+
self->_failedIconIdentity = identity;
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
UIImage *scaled = image.CGImage ? [UIImage imageWithCGImage:image.CGImage scale:scale orientation:UIImageOrientationUp] : image;
|
|
1015
|
+
self->_readyToolbarImage = [scaled imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
|
1016
|
+
self->_readyToolbarIdentity = identity;
|
|
1017
|
+
[self applyReadyLegacyToolbarImage];
|
|
1018
|
+
}];
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
- (BOOL)ownsSettledToolbar {
|
|
1022
|
+
return [_config[@"controlled"] boolValue] && [_config[@"toolbar"] isKindOfClass:NSDictionary.class];
|
|
1023
|
+
}
|
|
1024
|
+
- (BOOL)toolbarCanCommit {
|
|
1025
|
+
if (!self.window) return NO;
|
|
1026
|
+
if (![self ownsSettledToolbar]) return YES;
|
|
1027
|
+
if (_toolbarPagerBusy || _toolbarPageIndex < 0) return NO;
|
|
1028
|
+
NSArray *pages = [_config[@"pageKeys"] isKindOfClass:NSArray.class] ? _config[@"pageKeys"] : @[];
|
|
1029
|
+
NSArray *items = [_items valueForKey:@"key"];
|
|
1030
|
+
BOOL valid = pages.count > 0 && [pages containsObject:_config[@"selectedKey"] ?: @""] &&
|
|
1031
|
+
[NSSet setWithArray:pages].count == pages.count && [NSSet setWithArray:items].count == items.count;
|
|
1032
|
+
for (id key in pages) if (![items containsObject:key]) { valid = NO; break; }
|
|
1033
|
+
return !valid || (_toolbarPageIndex < pages.count && [pages[_toolbarPageIndex] isEqual:_config[@"selectedKey"]]);
|
|
1034
|
+
}
|
|
1035
|
+
- (void)setToolbarInteractive:(BOOL)interactive {
|
|
1036
|
+
_toolbarButton.enabled = interactive;
|
|
1037
|
+
_toolbarButton.userInteractionEnabled = interactive;
|
|
1038
|
+
_toolbarButton.accessibilityElementsHidden = !interactive;
|
|
1039
|
+
_toolbarButton.isAccessibilityElement = interactive;
|
|
1040
|
+
if (!interactive) {
|
|
1041
|
+
_toolbarTouchKey = nil;
|
|
1042
|
+
_toolbarButton.highlighted = NO;
|
|
1043
|
+
_toolbarButton.hovering = NO;
|
|
1044
|
+
[_toolbarButton refreshBackground];
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
- (void)cancelToolbarRequest {
|
|
1048
|
+
if (_iconRequestPending || _readyToolbarImage) {
|
|
1049
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
1050
|
+
_iconOwner = [NSObject new];
|
|
1051
|
+
_iconRequestPending = NO;
|
|
1052
|
+
_readyToolbarImage = nil;
|
|
1053
|
+
_readyToolbarIdentity = nil;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
- (void)suspendToolbar {
|
|
1057
|
+
[self setToolbarInteractive:NO];
|
|
1058
|
+
[self cancelToolbarRequest];
|
|
1059
|
+
_desiredToolbarKey = nil;
|
|
1060
|
+
_desiredToolbarEntry = nil;
|
|
1061
|
+
if (_toolbarPresenceAnimating) {
|
|
1062
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
1063
|
+
CGFloat alpha = presentation ? presentation.opacity : _toolbarButton.alpha;
|
|
1064
|
+
++_toolbarAnimationGeneration;
|
|
1065
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
1066
|
+
_toolbarButton.alpha = alpha;
|
|
1067
|
+
_toolbarPresenceAnimating = NO;
|
|
1068
|
+
_toolbarPresenceInterrupted = YES;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
- (void)updateToolbarPagerIndex:(NSInteger)index busy:(BOOL)busy {
|
|
1072
|
+
if (_toolbarPagerBusy == busy && (busy || _toolbarPageIndex == index)) return;
|
|
1073
|
+
_toolbarPagerBusy = busy;
|
|
1074
|
+
if (!busy) _toolbarPageIndex = index;
|
|
1075
|
+
NSString *key = [_config[@"controlled"] boolValue] ? _config[@"selectedKey"] : _toolbarKey;
|
|
1076
|
+
[self updateToolbarForKey:key];
|
|
1077
|
+
}
|
|
1078
|
+
- (void)setToolbarVisible:(BOOL)visible {
|
|
1079
|
+
if (_toolbarVisible == visible && !_toolbarPresenceInterrupted) return;
|
|
1080
|
+
_toolbarVisible = visible;
|
|
1081
|
+
_toolbarPresenceInterrupted = NO;
|
|
1082
|
+
NSUInteger generation = ++_toolbarAnimationGeneration;
|
|
1083
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
1084
|
+
CGFloat alpha = presentation ? presentation.opacity : _toolbarButton.alpha;
|
|
1085
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
1086
|
+
_toolbarButton.alpha = alpha;
|
|
1087
|
+
if (visible) _toolbarButton.hidden = NO;
|
|
1088
|
+
if (!self.window) {
|
|
1089
|
+
_toolbarPresenceAnimating = NO;
|
|
1090
|
+
_toolbarButton.alpha = visible ? 1 : 0;
|
|
1091
|
+
_toolbarButton.hidden = !visible;
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
_toolbarPresenceAnimating = YES;
|
|
1095
|
+
[UIView animateWithDuration:0.10 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
|
|
1096
|
+
self->_toolbarButton.alpha = visible ? 1 : 0;
|
|
1097
|
+
} completion:^(BOOL finished) {
|
|
1098
|
+
if (self->_toolbarAnimationGeneration != generation) return;
|
|
1099
|
+
self->_toolbarPresenceAnimating = NO;
|
|
1100
|
+
if (!visible) self->_toolbarButton.hidden = YES;
|
|
1101
|
+
}];
|
|
1102
|
+
}
|
|
1103
|
+
- (void)commitToolbarImage:(UIImage *)image identity:(NSString *)identity {
|
|
1104
|
+
if (![self toolbarCanCommit] || !_desiredToolbarKey || !_desiredToolbarEntry) return;
|
|
1105
|
+
NSDictionary *entry = _desiredToolbarEntry;
|
|
1106
|
+
BOOL changed = ![_toolbarKey isEqualToString:_desiredToolbarKey] || ![_committedToolbarEntry isEqual:entry] || _loadedToolbarImage != image;
|
|
1107
|
+
if (![_toolbarKey isEqualToString:_desiredToolbarKey]) [self setToolbarInteractive:NO];
|
|
1108
|
+
_toolbarKey = [_desiredToolbarKey copy];
|
|
1109
|
+
_committedToolbarEntry = entry;
|
|
1110
|
+
_toolbarSize = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
1111
|
+
_toolbarIconSize = RNCPagerToolbarDimension(entry, @"iconSize", MIN(24, _toolbarSize), 1, _toolbarSize);
|
|
1112
|
+
_toolbarContentWidth = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, _toolbarSize), 0, _toolbarSize);
|
|
1113
|
+
_toolbarTrailingInset = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
1114
|
+
_toolbarButton.layer.cornerRadius = _toolbarSize / 2;
|
|
1115
|
+
_toolbarButton.iconSize = _toolbarIconSize;
|
|
1116
|
+
[_toolbarButton setNeedsLayout];
|
|
1117
|
+
_toolbarButton.baseColor = [entry[@"selected"] boolValue] ? ([RCTConvert UIColor:entry[@"selectedBackgroundColor"]] ?: UIColor.clearColor) : UIColor.clearColor;
|
|
1118
|
+
_toolbarButton.hoverColor = [RCTConvert UIColor:entry[@"hoverBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
1119
|
+
_toolbarButton.pressedColor = [RCTConvert UIColor:entry[@"pressedBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
1120
|
+
[_toolbarButton refreshBackground];
|
|
1121
|
+
_toolbarButton.accessibilityLabel = entry[@"accessibilityLabel"];
|
|
1122
|
+
_toolbarButton.accessibilityIdentifier = entry[@"testID"];
|
|
1123
|
+
[_toolbarButton setImage:image forState:UIControlStateNormal];
|
|
1124
|
+
_loadedIconIdentity = identity;
|
|
1125
|
+
_loadedToolbarImage = image;
|
|
1126
|
+
_readyToolbarImage = nil;
|
|
1127
|
+
_readyToolbarIdentity = nil;
|
|
1128
|
+
_iconRequestPending = NO;
|
|
1129
|
+
[self setToolbarVisible:YES];
|
|
1130
|
+
[self setToolbarInteractive:YES];
|
|
1131
|
+
if (changed) [self setNeedsLayout];
|
|
1132
|
+
}
|
|
1133
|
+
- (void)applyReadyToolbarImage {
|
|
1134
|
+
if (!_readyToolbarImage || ![_readyToolbarIdentity isEqualToString:_iconIdentity] || ![self toolbarCanCommit]) return;
|
|
1135
|
+
[self commitToolbarImage:_readyToolbarImage identity:_readyToolbarIdentity];
|
|
1136
|
+
}
|
|
1137
|
+
- (void)hideFailedToolbar {
|
|
1138
|
+
[self setToolbarInteractive:NO];
|
|
1139
|
+
_toolbarKey = nil;
|
|
1140
|
+
_committedToolbarEntry = nil;
|
|
1141
|
+
[_toolbarButton setImage:nil forState:UIControlStateNormal];
|
|
1142
|
+
_toolbarButton.accessibilityLabel = nil;
|
|
1143
|
+
_toolbarButton.accessibilityIdentifier = nil;
|
|
1144
|
+
[self setToolbarVisible:NO];
|
|
1145
|
+
}
|
|
1146
|
+
- (void)updateToolbarForKey:(NSString *)key {
|
|
1147
|
+
if (![self ownsSettledToolbar]) { [self updateLegacyToolbarForKey:key]; return; }
|
|
1148
|
+
NSDictionary *toolbar = [_config[@"toolbar"] isKindOfClass:NSDictionary.class] ? _config[@"toolbar"] : nil;
|
|
1149
|
+
_toolbarReservedWidth = toolbar == nil ? 0 : 44;
|
|
1150
|
+
if (toolbar.count > 0) {
|
|
1151
|
+
_toolbarReservedWidth = 0;
|
|
1152
|
+
for (id value in toolbar.allValues) {
|
|
1153
|
+
NSDictionary *entry = [value isKindOfClass:NSDictionary.class] ? value : @{};
|
|
1154
|
+
CGFloat size = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
1155
|
+
CGFloat content = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, size), 0, size);
|
|
1156
|
+
CGFloat trailing = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
1157
|
+
_toolbarReservedWidth = MAX(_toolbarReservedWidth, content + trailing);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
if (![self toolbarCanCommit]) { [self suspendToolbar]; return; }
|
|
1161
|
+
NSDictionary *entry = [toolbar[key ?: @""] isKindOfClass:NSDictionary.class] ? toolbar[key] : nil;
|
|
1162
|
+
NSString *name = [entry[@"iconName"] isKindOfClass:NSString.class] ? entry[@"iconName"] : nil;
|
|
1163
|
+
if (!name.length) {
|
|
1164
|
+
[self cancelToolbarRequest];
|
|
1165
|
+
_desiredToolbarKey = nil;
|
|
1166
|
+
_desiredToolbarEntry = nil;
|
|
1167
|
+
_toolbarKey = nil;
|
|
1168
|
+
_committedToolbarEntry = nil;
|
|
1169
|
+
[self setToolbarInteractive:NO];
|
|
1170
|
+
_toolbarButton.accessibilityLabel = nil;
|
|
1171
|
+
_toolbarButton.accessibilityIdentifier = nil;
|
|
1172
|
+
[self setToolbarVisible:NO];
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
_desiredToolbarKey = [key copy];
|
|
1176
|
+
_desiredToolbarEntry = [entry copy];
|
|
1177
|
+
CGFloat size = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
1178
|
+
CGFloat iconSize = RNCPagerToolbarDimension(entry, @"iconSize", MIN(24, size), 1, size);
|
|
1179
|
+
NSString *color = [entry[@"iconColor"] isKindOfClass:NSString.class] ? entry[@"iconColor"] : @"#000000";
|
|
1180
|
+
CGFloat scale = self.window.screen.scale ?: UIScreen.mainScreen.scale;
|
|
1181
|
+
NSString *identity = [NSString stringWithFormat:@"%@|%@|%g|%g", name, color, scale, iconSize];
|
|
1182
|
+
if ([_loadedIconIdentity isEqualToString:identity] && _loadedToolbarImage) {
|
|
1183
|
+
[self cancelToolbarRequest];
|
|
1184
|
+
_iconIdentity = identity;
|
|
1185
|
+
[self commitToolbarImage:_loadedToolbarImage identity:identity];
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
if ([_iconIdentity isEqualToString:identity] && (_iconRequestPending || _readyToolbarImage)) {
|
|
1189
|
+
[self setToolbarInteractive:NO];
|
|
1190
|
+
[self applyReadyToolbarImage];
|
|
1191
|
+
return;
|
|
1192
|
+
}
|
|
1193
|
+
[self cancelToolbarRequest];
|
|
1194
|
+
_iconIdentity = identity;
|
|
1195
|
+
[self setToolbarInteractive:NO];
|
|
1196
|
+
if ([_failedIconIdentity isEqualToString:identity]) { [self hideFailedToolbar]; return; }
|
|
1197
|
+
NSObject *owner = [NSObject new];
|
|
1198
|
+
_iconOwner = owner;
|
|
1199
|
+
_iconRequestPending = YES;
|
|
1200
|
+
__weak __typeof__(self) weakSelf = self;
|
|
1201
|
+
[[self iconBridge] requestWithOwner:owner name:name color:color width:(NSInteger)ceil(iconSize * scale) height:(NSInteger)ceil(iconSize * scale) scale:scale complete:^(UIImage *image) {
|
|
1202
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
1203
|
+
if (!self || self->_iconOwner != owner || ![self->_iconIdentity isEqualToString:identity] || !self.window) return;
|
|
1204
|
+
self->_iconRequestPending = NO;
|
|
1205
|
+
if (!image) {
|
|
1206
|
+
self->_failedIconIdentity = identity;
|
|
1207
|
+
if ([self toolbarCanCommit]) [self hideFailedToolbar];
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
UIImage *scaled = image.CGImage ? [UIImage imageWithCGImage:image.CGImage scale:scale orientation:UIImageOrientationUp] : image;
|
|
1211
|
+
self->_readyToolbarImage = [scaled imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
|
1212
|
+
self->_readyToolbarIdentity = identity;
|
|
1213
|
+
[self applyReadyToolbarImage];
|
|
1214
|
+
}];
|
|
1215
|
+
}
|
|
1216
|
+
- (void)didMoveToWindow {
|
|
1217
|
+
[super didMoveToWindow];
|
|
1218
|
+
if (![self ownsSettledToolbar]) {
|
|
1219
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
1220
|
+
_iconIdentity = nil;
|
|
1221
|
+
_loadedIconIdentity = nil;
|
|
1222
|
+
_iconRequestPending = NO;
|
|
1223
|
+
_readyToolbarImage = nil;
|
|
1224
|
+
_readyToolbarIdentity = nil;
|
|
1225
|
+
_failedIconIdentity = nil;
|
|
1226
|
+
_iconOwner = [NSObject new];
|
|
1227
|
+
_toolbarAnimationGeneration++;
|
|
1228
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
1229
|
+
_toolbarVisible = NO;
|
|
1230
|
+
_toolbarButton.alpha = 0;
|
|
1231
|
+
_toolbarButton.hidden = YES;
|
|
1232
|
+
_toolbarButton.enabled = NO;
|
|
1233
|
+
_toolbarButton.userInteractionEnabled = NO;
|
|
1234
|
+
_toolbarButton.accessibilityElementsHidden = YES;
|
|
1235
|
+
_toolbarTouchKey = nil;
|
|
1236
|
+
_toolbarTouchStarted = NO;
|
|
1237
|
+
_toolbarButton.highlighted = NO;
|
|
1238
|
+
_toolbarButton.hovering = NO;
|
|
1239
|
+
[self updateToolbarForKey:_toolbarKey];
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
[self suspendToolbar];
|
|
1243
|
+
_toolbarPagerBusy = YES;
|
|
1244
|
+
_toolbarPageIndex = -1;
|
|
1245
|
+
_toolbarTouchStarted = NO;
|
|
1246
|
+
if (self.window && self.onToolbarNeedsSettlement) self.onToolbarNeedsSettlement();
|
|
1247
|
+
}
|
|
1248
|
+
- (void)dealloc { if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner]; }
|
|
1249
|
+
- (void)toolbarTouchDown {
|
|
1250
|
+
_toolbarTouchStarted = YES;
|
|
1251
|
+
_toolbarTouchKey = _toolbarButton.enabled && [self toolbarCanCommit] ? [_toolbarKey copy] : nil;
|
|
1252
|
+
}
|
|
1253
|
+
- (void)toolbarCancel {
|
|
1254
|
+
_toolbarTouchKey = nil;
|
|
1255
|
+
if (![self ownsSettledToolbar]) _toolbarTouchStarted = NO;
|
|
1256
|
+
}
|
|
1257
|
+
- (void)toolbarPress:(UIControl *)sender forEvent:(UIEvent *)event {
|
|
1258
|
+
if (![self ownsSettledToolbar]) {
|
|
1259
|
+
NSString *key = _toolbarTouchStarted ? _toolbarTouchKey : _toolbarKey;
|
|
1260
|
+
_toolbarTouchStarted = NO; _toolbarTouchKey = nil;
|
|
1261
|
+
if (key && [key isEqualToString:_toolbarKey] && !_toolbarButton.hidden && _toolbarButton.enabled && self.window && self.onToolbarPress)
|
|
1262
|
+
self.onToolbarPress(key, [_toolbarButton convertRect:_toolbarButton.bounds toView:self.window]);
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
BOOL pointer = event && event.type == UIEventTypeTouches;
|
|
1266
|
+
NSString *key = pointer ? (_toolbarTouchStarted ? _toolbarTouchKey : nil) : _toolbarKey;
|
|
1267
|
+
if (pointer) { _toolbarTouchStarted = NO; _toolbarTouchKey = nil; }
|
|
1268
|
+
BOOL selectedMatches = ![_config[@"controlled"] boolValue] || [key isEqual:_config[@"selectedKey"]];
|
|
1269
|
+
if (key && selectedMatches && [key isEqualToString:_toolbarKey] && [self toolbarCanCommit] &&
|
|
1270
|
+
!_toolbarButton.hidden && _toolbarButton.enabled && self.onToolbarPress) {
|
|
1271
|
+
self.onToolbarPress(key, [_toolbarButton convertRect:_toolbarButton.bounds toView:self.window]);
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
|
|
696
1275
|
- (void)setProgress:(CGFloat)progress
|
|
697
1276
|
{
|
|
1277
|
+
_hasMappedPageProgress = NO;
|
|
1278
|
+
_nativeProgress = progress;
|
|
1279
|
+
_hasNativeProgress = YES;
|
|
698
1280
|
if (_items.count == 0) return;
|
|
699
1281
|
if (fabs(_progress - progress) >= 0.001) _shouldCenterSelectedItem = YES;
|
|
1282
|
+
if ([_config[@"controlled"] boolValue]) {
|
|
1283
|
+
NSArray *pageKeys = [_config[@"pageKeys"] isKindOfClass:NSArray.class] ? _config[@"pageKeys"] : @[];
|
|
1284
|
+
NSMutableArray<NSNumber *> *indices = [NSMutableArray new];
|
|
1285
|
+
NSSet *unique = [NSSet setWithArray:pageKeys];
|
|
1286
|
+
NSArray *itemKeys = [_items valueForKey:@"key"];
|
|
1287
|
+
BOOL valid = pageKeys.count > 0 && [pageKeys containsObject:_config[@"selectedKey"] ?: @""] && unique.count == pageKeys.count && [NSSet setWithArray:itemKeys].count == itemKeys.count;
|
|
1288
|
+
for (NSString *key in pageKeys) {
|
|
1289
|
+
NSUInteger index = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:key]; }];
|
|
1290
|
+
if (index == NSNotFound) { valid = NO; break; }
|
|
1291
|
+
[indices addObject:@(index)];
|
|
1292
|
+
}
|
|
1293
|
+
if (!valid) {
|
|
1294
|
+
NSUInteger selected = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:self->_config[@"selectedKey"]]; }];
|
|
1295
|
+
if (selected == NSNotFound) return;
|
|
1296
|
+
_progressSource = selected; _progressDestination = selected; _progressFraction = 0;
|
|
1297
|
+
_progress = selected;
|
|
1298
|
+
if (_indicatorFrames.count == _items.count) [self updatePresentation];
|
|
1299
|
+
return;
|
|
1300
|
+
}
|
|
1301
|
+
_hasMappedPageProgress = YES;
|
|
1302
|
+
CGFloat bounded = RNCClamp(progress, 0, pageKeys.count - 1);
|
|
1303
|
+
NSInteger lower = (NSInteger)floor(bounded);
|
|
1304
|
+
NSInteger upper = MIN(lower + 1, pageKeys.count - 1);
|
|
1305
|
+
_progressSource = indices[lower].integerValue;
|
|
1306
|
+
_progressDestination = indices[upper].integerValue;
|
|
1307
|
+
_progressFraction = bounded - lower;
|
|
1308
|
+
progress = _progressSource + (_progressDestination - _progressSource) * _progressFraction;
|
|
1309
|
+
}
|
|
700
1310
|
_progress = RNCClamp(progress, 0, _items.count - 1);
|
|
701
1311
|
if (_indicatorFrames.count == _items.count) [self updatePresentation];
|
|
702
1312
|
}
|
|
703
1313
|
|
|
1314
|
+
- (NSInteger)visualFontOwnerIndex:(NSInteger)semanticIndex {
|
|
1315
|
+
if (_textVariant && [_config[@"controlled"] boolValue] && _hasMappedPageProgress) {
|
|
1316
|
+
return _progressFraction < 0.5 ? _progressSource : _progressDestination;
|
|
1317
|
+
}
|
|
1318
|
+
return semanticIndex;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
704
1321
|
- (void)updatePresentation
|
|
705
1322
|
{
|
|
706
1323
|
NSInteger count = _buttons.count;
|
|
@@ -718,11 +1335,22 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
718
1335
|
_segmented ? CGRectGetHeight(fromFrame) : _indicatorHeight
|
|
719
1336
|
);
|
|
720
1337
|
_indicatorView.frame = indicatorFrame;
|
|
721
|
-
_indicatorView.hidden = CGRectGetHeight(indicatorFrame) <= 0;
|
|
1338
|
+
_indicatorView.hidden = _textVariant || CGRectGetHeight(indicatorFrame) <= 0;
|
|
722
1339
|
|
|
723
1340
|
NSInteger selectedIndex = (NSInteger)round(progress);
|
|
1341
|
+
if ([_config[@"controlled"] boolValue]) {
|
|
1342
|
+
NSUInteger selected = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:self->_config[@"selectedKey"]]; }];
|
|
1343
|
+
selectedIndex = selected == NSNotFound ? -1 : (NSInteger)selected;
|
|
1344
|
+
}
|
|
1345
|
+
NSInteger fontOwnerIndex = [self visualFontOwnerIndex:selectedIndex];
|
|
1346
|
+
NSString *key = selectedIndex >= 0 && selectedIndex < count ? _items[selectedIndex][@"key"] : nil;
|
|
1347
|
+
[self updateToolbarForKey:key];
|
|
724
1348
|
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
725
1349
|
CGFloat emphasis = 1 - MIN(1, fabs(progress - index));
|
|
1350
|
+
if ([self->_config[@"controlled"] boolValue]) {
|
|
1351
|
+
emphasis = index == self->_progressSource ? 1 - self->_progressFraction : 0;
|
|
1352
|
+
if (index == self->_progressDestination) emphasis += self->_progressFraction;
|
|
1353
|
+
}
|
|
726
1354
|
UIColor *color = RNCInterpolateColor(
|
|
727
1355
|
self->_inactiveTextColor,
|
|
728
1356
|
self->_activeTextColor,
|
|
@@ -730,6 +1358,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
730
1358
|
self.traitCollection
|
|
731
1359
|
);
|
|
732
1360
|
[button setTitleColor:color forState:UIControlStateNormal];
|
|
1361
|
+
if (self->_textVariant) {
|
|
1362
|
+
button.titleLabel.font = index == fontOwnerIndex
|
|
1363
|
+
? self.activeTabFont
|
|
1364
|
+
: self.tabFont;
|
|
1365
|
+
}
|
|
733
1366
|
button.accessibilityTraits = index == selectedIndex
|
|
734
1367
|
? UIAccessibilityTraitButton | UIAccessibilityTraitSelected
|
|
735
1368
|
: UIAccessibilityTraitButton;
|
|
@@ -1534,6 +2167,24 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1534
2167
|
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)other { return YES; }
|
|
1535
2168
|
@end
|
|
1536
2169
|
|
|
2170
|
+
@protocol RNCPagerPresentationProgressTarget <NSObject>
|
|
2171
|
+
- (void)sampleProgrammaticTabProgress:(CADisplayLink *)link;
|
|
2172
|
+
- (void)sampleToolbarSettlement:(CADisplayLink *)link;
|
|
2173
|
+
@end
|
|
2174
|
+
|
|
2175
|
+
@interface RNCPagerPresentationProgressProxy : NSObject
|
|
2176
|
+
@property (nonatomic, weak) id<RNCPagerPresentationProgressTarget> target;
|
|
2177
|
+
@property (nonatomic, assign) BOOL toolbarSettlement;
|
|
2178
|
+
@end
|
|
2179
|
+
@implementation RNCPagerPresentationProgressProxy
|
|
2180
|
+
- (void)tick:(CADisplayLink *)link {
|
|
2181
|
+
id<RNCPagerPresentationProgressTarget> target = self.target;
|
|
2182
|
+
if (target && self.toolbarSettlement) [target sampleToolbarSettlement:link];
|
|
2183
|
+
else if (target) [target sampleProgrammaticTabProgress:link];
|
|
2184
|
+
else [link invalidate];
|
|
2185
|
+
}
|
|
2186
|
+
@end
|
|
2187
|
+
|
|
1537
2188
|
@interface RNCCollapsiblePagerViewComponentView () <
|
|
1538
2189
|
RCTRNCCollapsiblePagerViewViewProtocol,
|
|
1539
2190
|
UIPageViewControllerDataSource,
|
|
@@ -1542,8 +2193,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1542
2193
|
UIGestureRecognizerDelegate,
|
|
1543
2194
|
RCTMountingTransactionObserving,
|
|
1544
2195
|
RNCNativeScrollerInsetCoordinator,
|
|
1545
|
-
RNCPagerViewNestedOverlayObserver
|
|
2196
|
+
RNCPagerViewNestedOverlayObserver,
|
|
2197
|
+
RNCPagerPresentationProgressTarget
|
|
1546
2198
|
>
|
|
2199
|
+
- (void)syncNativeToolbarState;
|
|
2200
|
+
- (void)cancelNativeToolbarSettlement;
|
|
2201
|
+
- (void)scheduleNativeToolbarSettlement;
|
|
1547
2202
|
- (void)finishPagerScrollEmittingSelection:(BOOL)emitSelection;
|
|
1548
2203
|
- (void)cancelPendingNativePagerScroll;
|
|
1549
2204
|
- (void)completeTransitionOnNextRunLoopForGeneration:(NSUInteger)generation
|
|
@@ -1662,6 +2317,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1662
2317
|
BOOL _nativeSmoothHeaderScrollEnabled;
|
|
1663
2318
|
UIPanGestureRecognizer *_blockerGesture;
|
|
1664
2319
|
BOOL _transitioning;
|
|
2320
|
+
BOOL _toolbarTransitionPending;
|
|
2321
|
+
CADisplayLink *_toolbarSettleDisplayLink;
|
|
2322
|
+
NSUInteger _toolbarSettleGeneration;
|
|
2323
|
+
NSUInteger _toolbarSettleTransition;
|
|
2324
|
+
NSInteger _toolbarSettlePage;
|
|
2325
|
+
CGFloat _toolbarSettleWidth;
|
|
2326
|
+
NSUInteger _toolbarSettleTicks;
|
|
2327
|
+
BOOL _toolbarSettleAttempted;
|
|
1665
2328
|
BOOL _isPagerDragging;
|
|
1666
2329
|
BOOL _directNativePagerEnabled;
|
|
1667
2330
|
BOOL _pendingDirectNativePagerEnabled;
|
|
@@ -1672,6 +2335,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1672
2335
|
NSUInteger _pendingNativePagerScrollGeneration;
|
|
1673
2336
|
NSUInteger _pendingNativePagerScrollTransition;
|
|
1674
2337
|
CGFloat _pendingNativePagerScrollTarget;
|
|
2338
|
+
CADisplayLink *_tabProgressDisplayLink;
|
|
2339
|
+
NSUInteger _tabProgressGeneration;
|
|
2340
|
+
NSUInteger _tabProgressTransition;
|
|
2341
|
+
CGFloat _tabProgressWidth;
|
|
1675
2342
|
NSIndexSet *_programmaticPageMaskIndexes;
|
|
1676
2343
|
NSMapTable<UIScrollView *, RNCCollapsiblePagerTransitionEdgeOwner *> *_programmaticScrollEdges;
|
|
1677
2344
|
NSMutableArray<RNCCollapsiblePagerTransitionEdgeOwner *> *_programmaticScrollEdgeOwners;
|
|
@@ -1851,6 +2518,23 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1851
2518
|
});
|
|
1852
2519
|
}
|
|
1853
2520
|
};
|
|
2521
|
+
_nativeTabBarView.onToolbarNeedsSettlement = ^{
|
|
2522
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
2523
|
+
if (!self) return;
|
|
2524
|
+
NSUInteger generation = self->_generation;
|
|
2525
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
2526
|
+
if (self->_generation == generation) [self syncNativeToolbarState];
|
|
2527
|
+
});
|
|
2528
|
+
};
|
|
2529
|
+
_nativeTabBarView.onToolbarPress = ^(NSString *key, CGRect rect) {
|
|
2530
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
2531
|
+
if (!self || self->_isBeingRecycled) return;
|
|
2532
|
+
const auto emitter = [self eventEmitter];
|
|
2533
|
+
if (emitter) emitter->onNativeToolbarPress({
|
|
2534
|
+
.key = std::string(key.UTF8String ?: ""),
|
|
2535
|
+
.anchorRect = {.x = rect.origin.x, .y = rect.origin.y, .width = rect.size.width, .height = rect.size.height}
|
|
2536
|
+
});
|
|
2537
|
+
};
|
|
1854
2538
|
[_sharedHeaderHostView addSubview:_nativeTabBarView];
|
|
1855
2539
|
|
|
1856
2540
|
_nativeSubHeaderView = [RNCCollapsiblePagerNativeSubHeaderView new];
|
|
@@ -1935,11 +2619,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1935
2619
|
- (void)didMoveToWindow
|
|
1936
2620
|
{
|
|
1937
2621
|
[super didMoveToWindow];
|
|
2622
|
+
[self cancelNativeToolbarSettlement];
|
|
2623
|
+
if (self.window == nil) [self stopProgrammaticTabProgress];
|
|
1938
2624
|
if (self.window == nil && _pendingNativePagerScrollCompletion != nil) {
|
|
1939
2625
|
[self cancelPendingNativePagerScroll];
|
|
1940
2626
|
_pagerScrollView.scrollEnabled = _scrollEnabled;
|
|
1941
2627
|
[self finishPagerScrollEmittingSelection:NO];
|
|
1942
2628
|
}
|
|
2629
|
+
[self syncNativeToolbarState];
|
|
1943
2630
|
[self registerExternalOuterPager];
|
|
1944
2631
|
BOOL externalHeightChanged = [self resolveExternalTopOverlay];
|
|
1945
2632
|
if (self.window == nil) [self restoreScrollEdgeContentMasks];
|
|
@@ -2054,6 +2741,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2054
2741
|
BOOL wasTransitioning = _externalOuterTransitioning;
|
|
2055
2742
|
_externalOuterPageActive = active;
|
|
2056
2743
|
_externalOuterTransitioning = transitioning;
|
|
2744
|
+
[self syncNativeToolbarState];
|
|
2057
2745
|
if (transitioning && !wasTransitioning) {
|
|
2058
2746
|
[self updateTopScrollEdgeForScrollView:nil];
|
|
2059
2747
|
[self liftSharedHeadersForPagerTransition:@"outer"];
|
|
@@ -2418,8 +3106,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2418
3106
|
[host addSubview:marker];
|
|
2419
3107
|
return marker;
|
|
2420
3108
|
};
|
|
2421
|
-
owner.ownsTransitionHosts =
|
|
2422
|
-
_externalTopOverlayTag > 0 && pageIndex != _currentIndex;
|
|
3109
|
+
owner.ownsTransitionHosts = pageIndex != _currentIndex;
|
|
2423
3110
|
UIView *(^transitionHost)(UIView *) = ^UIView *(UIView *referenceHost) {
|
|
2424
3111
|
CGRect frame = [referenceHost convertRect:referenceHost.bounds toView:owner.page];
|
|
2425
3112
|
CGRect viewport = [scrollView convertRect:scrollView.bounds toView:owner.page];
|
|
@@ -2680,6 +3367,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2680
3367
|
|
|
2681
3368
|
- (void)dealloc
|
|
2682
3369
|
{
|
|
3370
|
+
[self cancelNativeToolbarSettlement];
|
|
3371
|
+
[self stopProgrammaticTabProgress];
|
|
2683
3372
|
[self clearProgrammaticScrollEdges];
|
|
2684
3373
|
[self updateTopScrollEdgeForScrollView:nil];
|
|
2685
3374
|
[self cancelHeaderSettledCheck];
|
|
@@ -2734,6 +3423,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2734
3423
|
}
|
|
2735
3424
|
_hasPendingDirectNativePagerChange = NO;
|
|
2736
3425
|
if (_directNativePagerEnabled == enabled) return;
|
|
3426
|
+
[self cancelNativeToolbarSettlement];
|
|
2737
3427
|
[self cancelPendingNativePagerScroll];
|
|
2738
3428
|
|
|
2739
3429
|
[self restoreScrollEdgeContentMasks];
|
|
@@ -2796,12 +3486,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2796
3486
|
return width * MAX(0, physicalIndex);
|
|
2797
3487
|
}
|
|
2798
3488
|
|
|
2799
|
-
- (CGFloat)
|
|
3489
|
+
- (CGFloat)directPagerProgressForOffset:(CGFloat)offset
|
|
2800
3490
|
{
|
|
2801
3491
|
CGFloat width = CGRectGetWidth(_pagerScrollView.bounds);
|
|
2802
3492
|
if (width <= 0 || _pageControllers.count == 0) return _currentIndex;
|
|
2803
3493
|
CGFloat physicalProgress = RNCClamp(
|
|
2804
|
-
|
|
3494
|
+
offset / width,
|
|
2805
3495
|
0,
|
|
2806
3496
|
_pageControllers.count - 1
|
|
2807
3497
|
);
|
|
@@ -2810,6 +3500,50 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2810
3500
|
: _pageControllers.count - 1 - physicalProgress;
|
|
2811
3501
|
}
|
|
2812
3502
|
|
|
3503
|
+
- (CGFloat)directPagerProgress {
|
|
3504
|
+
return [self directPagerProgressForOffset:_pagerScrollView.contentOffset.x];
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
- (void)stopProgrammaticTabProgress {
|
|
3508
|
+
if (_tabProgressDisplayLink != nil && _tabProgressWidth > 0 && fabs(CGRectGetWidth(_pagerScrollView.bounds) - _tabProgressWidth) < 0.5) {
|
|
3509
|
+
CALayer *presentation = _pagerScrollView.layer.presentationLayer;
|
|
3510
|
+
if (presentation && std::isfinite(presentation.bounds.origin.x)) {
|
|
3511
|
+
[_nativeTabBarView setProgress:[self directPagerProgressForOffset:presentation.bounds.origin.x]];
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3514
|
+
[_tabProgressDisplayLink invalidate];
|
|
3515
|
+
_tabProgressDisplayLink = nil;
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
- (void)updateNativeTabProgress:(CGFloat)progress {
|
|
3519
|
+
if (_tabProgressDisplayLink == nil) [_nativeTabBarView setProgress:progress];
|
|
3520
|
+
}
|
|
3521
|
+
|
|
3522
|
+
- (void)sampleProgrammaticTabProgress:(CADisplayLink *)link {
|
|
3523
|
+
if (link != _tabProgressDisplayLink) { [link invalidate]; return; }
|
|
3524
|
+
if (self.window == nil || !_transitioning || ![_nativeTabBarView usesControlledTextProgress] ||
|
|
3525
|
+
_tabProgressGeneration != _generation || _tabProgressTransition != _transitionId ||
|
|
3526
|
+
!std::isfinite(CGRectGetWidth(_pagerScrollView.bounds)) || _tabProgressWidth <= 0 ||
|
|
3527
|
+
fabs(CGRectGetWidth(_pagerScrollView.bounds) - _tabProgressWidth) >= 0.5) {
|
|
3528
|
+
[self stopProgrammaticTabProgress];
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
CALayer *presentation = _pagerScrollView.layer.presentationLayer;
|
|
3532
|
+
if (presentation == nil || !std::isfinite(presentation.bounds.origin.x)) return;
|
|
3533
|
+
[_nativeTabBarView setProgress:[self directPagerProgressForOffset:presentation.bounds.origin.x]];
|
|
3534
|
+
}
|
|
3535
|
+
|
|
3536
|
+
- (void)startProgrammaticTabProgress {
|
|
3537
|
+
[self stopProgrammaticTabProgress];
|
|
3538
|
+
RNCPagerPresentationProgressProxy *proxy = [RNCPagerPresentationProgressProxy new];
|
|
3539
|
+
proxy.target = self;
|
|
3540
|
+
_tabProgressWidth = CGRectGetWidth(_pagerScrollView.bounds);
|
|
3541
|
+
_tabProgressGeneration = _generation;
|
|
3542
|
+
_tabProgressTransition = _transitionId;
|
|
3543
|
+
_tabProgressDisplayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(tick:)];
|
|
3544
|
+
[_tabProgressDisplayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
3545
|
+
}
|
|
3546
|
+
|
|
2813
3547
|
#pragma mark - Smooth shared header ownership
|
|
2814
3548
|
|
|
2815
3549
|
- (void)restorePinnedStickyHeader
|
|
@@ -3068,8 +3802,81 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3068
3802
|
}
|
|
3069
3803
|
}
|
|
3070
3804
|
|
|
3805
|
+
- (void)cancelNativeToolbarSettlement {
|
|
3806
|
+
[_toolbarSettleDisplayLink invalidate];
|
|
3807
|
+
_toolbarSettleDisplayLink = nil;
|
|
3808
|
+
_toolbarSettleAttempted = NO;
|
|
3809
|
+
_toolbarSettleTicks = 0;
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
- (void)scheduleNativeToolbarSettlement {
|
|
3813
|
+
CGFloat width = CGRectGetWidth(_pagerScrollView.bounds);
|
|
3814
|
+
BOOL sameAttempt = _toolbarSettleAttempted && _toolbarSettleGeneration == _generation &&
|
|
3815
|
+
_toolbarSettleTransition == _transitionId && _toolbarSettlePage == _currentIndex &&
|
|
3816
|
+
_toolbarSettleWidth == width;
|
|
3817
|
+
if (sameAttempt) return;
|
|
3818
|
+
[self cancelNativeToolbarSettlement];
|
|
3819
|
+
_toolbarSettleAttempted = YES;
|
|
3820
|
+
_toolbarSettleGeneration = _generation;
|
|
3821
|
+
_toolbarSettleTransition = _transitionId;
|
|
3822
|
+
_toolbarSettlePage = _currentIndex;
|
|
3823
|
+
_toolbarSettleWidth = width;
|
|
3824
|
+
RNCPagerPresentationProgressProxy *proxy = [RNCPagerPresentationProgressProxy new];
|
|
3825
|
+
proxy.target = self;
|
|
3826
|
+
proxy.toolbarSettlement = YES;
|
|
3827
|
+
_toolbarSettleDisplayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(tick:)];
|
|
3828
|
+
[_toolbarSettleDisplayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
- (void)sampleToolbarSettlement:(CADisplayLink *)link {
|
|
3832
|
+
if (link != _toolbarSettleDisplayLink) { [link invalidate]; return; }
|
|
3833
|
+
if (_toolbarSettleGeneration != _generation || _toolbarSettleTransition != _transitionId ||
|
|
3834
|
+
_toolbarSettlePage != _currentIndex ||
|
|
3835
|
+
_toolbarSettleWidth != CGRectGetWidth(_pagerScrollView.bounds) ||
|
|
3836
|
+
!self.window || _nativeTabBarView.window != self.window || ![_nativeTabBarView ownsSettledToolbar]) {
|
|
3837
|
+
[self cancelNativeToolbarSettlement];
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
++_toolbarSettleTicks;
|
|
3841
|
+
[self syncNativeToolbarState];
|
|
3842
|
+
if (link == _toolbarSettleDisplayLink && _toolbarSettleTicks >= 2) {
|
|
3843
|
+
[link invalidate];
|
|
3844
|
+
_toolbarSettleDisplayLink = nil;
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3848
|
+
- (void)syncNativeToolbarState {
|
|
3849
|
+
BOOL logicalBusy = !self.window || _isBeingRecycled || !_hasAppliedInitialPage || _toolbarTransitionPending ||
|
|
3850
|
+
_transitioning || _isPagerDragging || _externalOuterTransitioning ||
|
|
3851
|
+
(_externalOuterPager != nil && !_externalOuterPageActive) || _needsSlotRebuild ||
|
|
3852
|
+
_pendingGoToIndex >= 0 || _transitionPageOffsets != nil || _pendingNativePagerScrollCompletion != nil ||
|
|
3853
|
+
_tabProgressDisplayLink != nil || _currentIndex < 0 || _currentIndex >= _pageControllers.count ||
|
|
3854
|
+
CGRectGetWidth(self.bounds) <= 0 || CGRectGetHeight(self.bounds) <= 0 ||
|
|
3855
|
+
_pagerScrollView.dragging || _pagerScrollView.decelerating;
|
|
3856
|
+
BOOL busy = logicalBusy || _pagerScrollView.tracking;
|
|
3857
|
+
if (!busy && _directNativePagerEnabled) {
|
|
3858
|
+
CALayer *presentation = _pagerScrollView.layer.presentationLayer;
|
|
3859
|
+
CGFloat offset = presentation ? presentation.bounds.origin.x : _pagerScrollView.contentOffset.x;
|
|
3860
|
+
busy = CGRectGetWidth(_pagerScrollView.bounds) <= 0 || !std::isfinite(offset) ||
|
|
3861
|
+
fabs(offset - [self directPagerOffsetForIndex:_currentIndex]) >= 0.5;
|
|
3862
|
+
} else if (!busy) {
|
|
3863
|
+
busy = _pageViewController.viewControllers.firstObject != _pageControllers[_currentIndex];
|
|
3864
|
+
}
|
|
3865
|
+
[_nativeTabBarView updateToolbarPagerIndex:_currentIndex busy:busy];
|
|
3866
|
+
if (busy && !logicalBusy && [_nativeTabBarView ownsSettledToolbar] &&
|
|
3867
|
+
_nativeTabBarView.window == self.window && !_nativeTabBarView.hidden &&
|
|
3868
|
+
CGRectGetWidth(_pagerScrollView.bounds) > 0) {
|
|
3869
|
+
[self scheduleNativeToolbarSettlement];
|
|
3870
|
+
} else {
|
|
3871
|
+
[self cancelNativeToolbarSettlement];
|
|
3872
|
+
}
|
|
3873
|
+
}
|
|
3874
|
+
|
|
3071
3875
|
- (void)beginPageStateTransition
|
|
3072
3876
|
{
|
|
3877
|
+
[self cancelNativeToolbarSettlement];
|
|
3878
|
+
_toolbarTransitionPending = YES;
|
|
3879
|
+
[_nativeTabBarView updateToolbarPagerIndex:-1 busy:YES];
|
|
3073
3880
|
[self cancelPagerTop];
|
|
3074
3881
|
if (!_nativeSmoothHeaderScrollEnabled || _headerHeight <= 0 || _transitionPageOffsets != nil) return;
|
|
3075
3882
|
_transitionSourceDisplayBody = MAX(_observedScrollView.contentOffset.y + _observedScrollView.contentInset.top - _headerHeight, 0);
|
|
@@ -3444,7 +4251,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3444
4251
|
animated:NO
|
|
3445
4252
|
completion:nil];
|
|
3446
4253
|
}
|
|
3447
|
-
[
|
|
4254
|
+
[self updateNativeTabProgress:_currentIndex];
|
|
3448
4255
|
}
|
|
3449
4256
|
if (_stickyHeaderView != nil) [_sharedHeaderHostView bringSubviewToFront:_stickyHeaderView];
|
|
3450
4257
|
if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
@@ -3462,6 +4269,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3462
4269
|
- (void)layoutSubviews
|
|
3463
4270
|
{
|
|
3464
4271
|
[super layoutSubviews];
|
|
4272
|
+
if (_tabProgressDisplayLink != nil && fabs(CGRectGetWidth(self.bounds) - _tabProgressWidth) >= 0.5) {
|
|
4273
|
+
[self stopProgrammaticTabProgress];
|
|
4274
|
+
}
|
|
3465
4275
|
void (^resizedPagerCompletion)(BOOL) = nil;
|
|
3466
4276
|
NSInteger resizedPagerDestination = -1;
|
|
3467
4277
|
NSUInteger resizedPagerGeneration = 0;
|
|
@@ -3504,7 +4314,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3504
4314
|
animated:NO
|
|
3505
4315
|
completion:nil];
|
|
3506
4316
|
}
|
|
3507
|
-
[
|
|
4317
|
+
[self updateNativeTabProgress:_currentIndex];
|
|
3508
4318
|
}
|
|
3509
4319
|
// UIKit frame assignment is undefined while a view has a non-identity transform.
|
|
3510
4320
|
// Restore transforms before settling page and shared-header frames.
|
|
@@ -3556,10 +4366,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3556
4366
|
) animated:NO];
|
|
3557
4367
|
resizedPagerCompletion(YES);
|
|
3558
4368
|
}
|
|
4369
|
+
[self syncNativeToolbarState];
|
|
3559
4370
|
}
|
|
3560
4371
|
|
|
3561
4372
|
- (void)prepareForRecycle
|
|
3562
4373
|
{
|
|
4374
|
+
[self cancelNativeToolbarSettlement];
|
|
3563
4375
|
[self cancelPendingNativePagerScroll];
|
|
3564
4376
|
[_externalOuterPager removeNestedOverlayObserver:self];
|
|
3565
4377
|
_externalOuterPager = nil;
|
|
@@ -3569,6 +4381,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3569
4381
|
[self cancelHeaderSettledCheck];
|
|
3570
4382
|
[self publishHeaderSettledAtTop:NO identityChanged:NO];
|
|
3571
4383
|
_isBeingRecycled = YES;
|
|
4384
|
+
_toolbarTransitionPending = NO;
|
|
4385
|
+
[_nativeTabBarView updateToolbarPagerIndex:-1 busy:YES];
|
|
3572
4386
|
_pendingTopOverlayAppearanceCommit = NO;
|
|
3573
4387
|
[self syncScrollEdgeAppearance];
|
|
3574
4388
|
_generation++;
|
|
@@ -3706,7 +4520,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3706
4520
|
_needsPropsReapply = YES;
|
|
3707
4521
|
_layoutDirection = @"ltr";
|
|
3708
4522
|
[_nativeTabBarView updateItemsJSON:@"[]"];
|
|
3709
|
-
[_nativeTabBarView
|
|
4523
|
+
[_nativeTabBarView updateConfigJSON:@"{}"];
|
|
4524
|
+
[self updateNativeTabProgress:0];
|
|
3710
4525
|
[_nativeSubHeaderView updateConfigJSON:@"{}"];
|
|
3711
4526
|
_isBeingRecycled = NO;
|
|
3712
4527
|
}
|
|
@@ -3835,6 +4650,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3835
4650
|
_retainedPages = RCTNSStringFromString(newViewProps.retainedPages);
|
|
3836
4651
|
[self setNeedsLayout];
|
|
3837
4652
|
}
|
|
4653
|
+
if (_needsPropsReapply || oldViewProps.nativeTabBarConfig != newViewProps.nativeTabBarConfig) {
|
|
4654
|
+
[_nativeTabBarView updateConfigJSON:RCTNSStringFromString(newViewProps.nativeTabBarConfig)];
|
|
4655
|
+
}
|
|
3838
4656
|
if (_needsPropsReapply || oldViewProps.nativeTabBarItems != newViewProps.nativeTabBarItems) {
|
|
3839
4657
|
NSString *itemsJSON = RCTNSStringFromString(newViewProps.nativeTabBarItems);
|
|
3840
4658
|
[_nativeTabBarView updateItemsJSON:itemsJSON];
|
|
@@ -3899,6 +4717,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3899
4717
|
[self logHomeVisualGeometry:@"props-inset" scrollView:_observedScrollView];
|
|
3900
4718
|
}
|
|
3901
4719
|
_applyingTopOverlayInsetChange = NO;
|
|
4720
|
+
[self syncNativeToolbarState];
|
|
3902
4721
|
if (!_nativeTabBarView.hidden) {
|
|
3903
4722
|
[_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
|
|
3904
4723
|
}
|
|
@@ -5324,6 +6143,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5324
6143
|
|
|
5325
6144
|
- (void)cancelPendingNativePagerScroll
|
|
5326
6145
|
{
|
|
6146
|
+
[self stopProgrammaticTabProgress];
|
|
5327
6147
|
if (_pendingNativePagerScrollCompletion == nil) return;
|
|
5328
6148
|
_pendingNativePagerScrollCompletion = nil;
|
|
5329
6149
|
_pendingNativePagerScrollGeneration = 0;
|
|
@@ -5342,6 +6162,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5342
6162
|
{
|
|
5343
6163
|
[self cancelTopInsetReboundCheck];
|
|
5344
6164
|
if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
|
|
6165
|
+
[self cancelNativeToolbarSettlement];
|
|
5345
6166
|
_hasReceivedPageCommand = YES;
|
|
5346
6167
|
_hasAppliedInitialPage = YES;
|
|
5347
6168
|
if (_transitioning || _isPagerDragging) {
|
|
@@ -5363,6 +6184,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5363
6184
|
[self attachScrollObserverForCurrentPage];
|
|
5364
6185
|
[self emitPageSelected:index];
|
|
5365
6186
|
[self emitDiagnostics:@"page-selected"];
|
|
6187
|
+
[self syncNativeToolbarState];
|
|
5366
6188
|
return;
|
|
5367
6189
|
}
|
|
5368
6190
|
|
|
@@ -5391,13 +6213,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5391
6213
|
CGFloat targetOffset = [self directPagerOffsetForIndex:index];
|
|
5392
6214
|
void (^animations)(void) = ^{
|
|
5393
6215
|
[self->_pagerScrollView setContentOffset:CGPointMake(targetOffset, 0)];
|
|
5394
|
-
[self->_nativeTabBarView setProgress:index];
|
|
6216
|
+
if (self->_tabProgressDisplayLink == nil) [self->_nativeTabBarView setProgress:index];
|
|
5395
6217
|
[self updatePageStateTransitionProgress:index];
|
|
5396
6218
|
};
|
|
5397
6219
|
void (^completion)(BOOL) = ^(BOOL finished) {
|
|
5398
6220
|
__strong __typeof__(weakSelf) self = weakSelf;
|
|
5399
6221
|
if (self == nil || self->_generation != capturedGeneration ||
|
|
5400
6222
|
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
6223
|
+
[self stopProgrammaticTabProgress];
|
|
5401
6224
|
self->_pagerScrollView.scrollEnabled = self->_scrollEnabled;
|
|
5402
6225
|
if (finished) {
|
|
5403
6226
|
[self detachScrollObserver];
|
|
@@ -5433,7 +6256,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5433
6256
|
_pagerScrollView.scrollEnabled = NO;
|
|
5434
6257
|
if (useNativeAnimatedScroll) {
|
|
5435
6258
|
if (fabs(_pagerScrollView.contentOffset.x - targetOffset) <= 0.5) {
|
|
5436
|
-
[
|
|
6259
|
+
[self updateNativeTabProgress:index];
|
|
5437
6260
|
[self updatePageStateTransitionProgress:index];
|
|
5438
6261
|
completion(YES);
|
|
5439
6262
|
} else {
|
|
@@ -5444,6 +6267,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5444
6267
|
[_pagerScrollView setContentOffset:CGPointMake(targetOffset, 0) animated:YES];
|
|
5445
6268
|
}
|
|
5446
6269
|
} else {
|
|
6270
|
+
if ([_nativeTabBarView usesControlledTextProgress]) [self startProgrammaticTabProgress];
|
|
5447
6271
|
[UIView animateWithDuration:0.28
|
|
5448
6272
|
delay:0
|
|
5449
6273
|
options:UIViewAnimationOptionCurveEaseInOut |
|
|
@@ -5508,10 +6332,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5508
6332
|
[self attachScrollObserverForCurrentPage];
|
|
5509
6333
|
}
|
|
5510
6334
|
[self drainPendingGoTo];
|
|
6335
|
+
if (!self->_transitioning && !self->_isPagerDragging && self->_pendingGoToIndex < 0) self->_toolbarTransitionPending = NO;
|
|
5511
6336
|
if (!hadProgrammaticScrollEdges && !self->_transitioning && !self->_needsSlotRebuild) {
|
|
5512
6337
|
[self attachScrollObserverForCurrentPage];
|
|
5513
6338
|
}
|
|
5514
6339
|
[self updateScrollEdgeContentMasks];
|
|
6340
|
+
[self syncNativeToolbarState];
|
|
5515
6341
|
});
|
|
5516
6342
|
}
|
|
5517
6343
|
|
|
@@ -5631,7 +6457,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5631
6457
|
[self directPagerOffsetForIndex:index],
|
|
5632
6458
|
0
|
|
5633
6459
|
) animated:NO];
|
|
5634
|
-
[
|
|
6460
|
+
[self updateNativeTabProgress:index];
|
|
5635
6461
|
[self attachScrollObserverForCurrentPageAfterTransition:YES];
|
|
5636
6462
|
if (emitSelection &&
|
|
5637
6463
|
(_pendingGoToIndex < 0 || _pendingGoToIndex == index)) {
|
|
@@ -5682,7 +6508,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5682
6508
|
[self detachScrollObserver];
|
|
5683
6509
|
_currentIndex = index;
|
|
5684
6510
|
_destinationIndex = index;
|
|
5685
|
-
[
|
|
6511
|
+
[self updateNativeTabProgress:index];
|
|
5686
6512
|
[self attachScrollObserverForCurrentPageAfterTransition:YES];
|
|
5687
6513
|
// A newer tab tap remains authoritative while its queued command runs.
|
|
5688
6514
|
if (emitSelection &&
|
|
@@ -5743,7 +6569,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5743
6569
|
position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
|
|
5744
6570
|
[self updatePageStateTransitionProgress:progress];
|
|
5745
6571
|
[self updateScrollEdgeContentMasks];
|
|
5746
|
-
[
|
|
6572
|
+
if (_tabProgressDisplayLink == nil) [self updateNativeTabProgress:progress];
|
|
5747
6573
|
const auto emitter = [self eventEmitter];
|
|
5748
6574
|
if (emitter) {
|
|
5749
6575
|
emitter->onPageScroll({.position = (double)position, .offset = (double)offset});
|
|
@@ -5762,7 +6588,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5762
6588
|
}
|
|
5763
6589
|
[self updatePageStateTransitionProgress:tabProgress];
|
|
5764
6590
|
[self updateScrollEdgeContentMasks];
|
|
5765
|
-
[
|
|
6591
|
+
[self updateNativeTabProgress:tabProgress];
|
|
5766
6592
|
const auto emitter = [self eventEmitter];
|
|
5767
6593
|
if (emitter) {
|
|
5768
6594
|
emitter->onPageScroll({.position = (double)position, .offset = (double)offset});
|