@onekeyfe/react-native-pager-view 6.7.0-alpha.436 → 6.7.0-alpha.449
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 +135 -0
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerHost.kt +88 -12
- package/android/src/main/java/com/reactnativepagerview/CollapsiblePagerNativeHeaders.kt +442 -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 +298 -3
- package/ios/RNCCollapsiblePagerViewComponentView.mm +834 -20
- package/ios/tests/RNCCollapsiblePagerFirstFrameTests.mm +61 -0
- package/ios/tests/RNCCollapsiblePagerHitTestTests.mm +22 -0
- package/lib/module/CollapsiblePagerView.js +37 -5
- package/lib/module/CollapsiblePagerView.web.js +519 -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 +464 -5
- package/src/CollapsiblePagerViewNativeComponent.ts +6 -0
- package/src/NativeScroller.tsx +2 -3
- package/src/NativeScrollerNativeComponent.ts +0 -1
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
#import "React/RCTSurfaceTouchHandler.h"
|
|
15
15
|
#import "React/RCTTouchHandler.h"
|
|
16
16
|
#import <objc/runtime.h>
|
|
17
|
+
#import <React/RCTConvert.h>
|
|
17
18
|
#import <UIKit/UIGestureRecognizerSubclass.h>
|
|
18
19
|
|
|
19
20
|
// OneKey patch: deduplicate structural mutation parents before scanning the page.
|
|
@@ -398,8 +399,65 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
398
399
|
(long)pagerState]);
|
|
399
400
|
}
|
|
400
401
|
|
|
402
|
+
static CGFloat RNCPagerToolbarDimension(NSDictionary *entry, NSString *key, CGFloat fallback, CGFloat minimum, CGFloat maximum)
|
|
403
|
+
{
|
|
404
|
+
id value = entry[key];
|
|
405
|
+
if (![value isKindOfClass:NSNumber.class]) return fallback;
|
|
406
|
+
double dimension = [value doubleValue];
|
|
407
|
+
return std::isfinite(dimension) ? MIN(maximum, MAX(minimum, dimension)) : fallback;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
@protocol RNCPagerNamedSVGBridge <NSObject>
|
|
411
|
+
+ (void)cancelWithOwner:(id)owner;
|
|
412
|
+
+ (void)requestWithOwner:(id)owner name:(NSString *)name color:(NSString *)color
|
|
413
|
+
width:(NSInteger)width height:(NSInteger)height scale:(double)scale
|
|
414
|
+
complete:(void (^)(UIImage *))complete;
|
|
415
|
+
@end
|
|
416
|
+
|
|
417
|
+
@interface RNCPagerToolbarButton : UIButton
|
|
418
|
+
@property (nonatomic, strong) UIColor *baseColor;
|
|
419
|
+
@property (nonatomic, strong) UIColor *hoverColor;
|
|
420
|
+
@property (nonatomic, strong) UIColor *pressedColor;
|
|
421
|
+
@property (nonatomic) BOOL hovering;
|
|
422
|
+
@property (nonatomic) CGFloat iconSize;
|
|
423
|
+
- (void)refreshBackground;
|
|
424
|
+
@end
|
|
425
|
+
@implementation RNCPagerToolbarButton
|
|
426
|
+
- (instancetype)initWithFrame:(CGRect)frame {
|
|
427
|
+
if ((self = [super initWithFrame:frame])) {
|
|
428
|
+
self.adjustsImageWhenHighlighted = NO;
|
|
429
|
+
[self addGestureRecognizer:[[UIHoverGestureRecognizer alloc] initWithTarget:self action:@selector(hover:)]];
|
|
430
|
+
}
|
|
431
|
+
return self;
|
|
432
|
+
}
|
|
433
|
+
- (CGRect)imageRectForContentRect:(CGRect)contentRect {
|
|
434
|
+
CGFloat side = MIN(self.iconSize, MIN(CGRectGetWidth(contentRect), CGRectGetHeight(contentRect)));
|
|
435
|
+
return CGRectMake(CGRectGetMidX(contentRect) - side / 2, CGRectGetMidY(contentRect) - side / 2, side, side);
|
|
436
|
+
}
|
|
437
|
+
- (void)hover:(UIHoverGestureRecognizer *)gesture {
|
|
438
|
+
self.hovering = gesture.state == UIGestureRecognizerStateBegan || gesture.state == UIGestureRecognizerStateChanged;
|
|
439
|
+
[self refreshBackground];
|
|
440
|
+
}
|
|
441
|
+
- (void)setEnabled:(BOOL)enabled {
|
|
442
|
+
[super setEnabled:enabled];
|
|
443
|
+
if (!enabled) self.hovering = NO;
|
|
444
|
+
[self refreshBackground];
|
|
445
|
+
}
|
|
446
|
+
- (void)setHighlighted:(BOOL)highlighted {
|
|
447
|
+
[super setHighlighted:highlighted];
|
|
448
|
+
[self refreshBackground];
|
|
449
|
+
}
|
|
450
|
+
- (void)refreshBackground {
|
|
451
|
+
self.backgroundColor = self.enabled && self.highlighted ? self.pressedColor : (self.enabled && self.hovering ? self.hoverColor : self.baseColor);
|
|
452
|
+
}
|
|
453
|
+
@end
|
|
454
|
+
|
|
401
455
|
@interface RNCCollapsiblePagerNativeTabBarView : UIView <UIScrollViewDelegate>
|
|
402
456
|
@property (nonatomic, copy) RNCCollapsiblePagerNativeTabPressHandler onTabPress;
|
|
457
|
+
@property (nonatomic, copy) void (^onToolbarPress)(NSString *, CGRect);
|
|
458
|
+
@property (nonatomic, copy) void (^onToolbarNeedsSettlement)(void);
|
|
459
|
+
- (void)updateToolbarPagerIndex:(NSInteger)index busy:(BOOL)busy;
|
|
460
|
+
- (void)updateConfigJSON:(NSString *)json;
|
|
403
461
|
- (void)updateItemsJSON:(NSString *)itemsJSON;
|
|
404
462
|
- (void)updateStyleWithHeight:(CGFloat)height
|
|
405
463
|
variant:(NSString *)variant
|
|
@@ -415,6 +473,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
415
473
|
indicatorHeight:(CGFloat)indicatorHeight
|
|
416
474
|
indicatorBottom:(CGFloat)indicatorBottom;
|
|
417
475
|
- (void)setProgress:(CGFloat)progress;
|
|
476
|
+
- (BOOL)usesControlledTextProgress;
|
|
477
|
+
- (BOOL)ownsSettledToolbar;
|
|
418
478
|
- (void)setLeftToRight:(BOOL)leftToRight;
|
|
419
479
|
- (void)updateAncestorGesturePrecedence;
|
|
420
480
|
@end
|
|
@@ -430,6 +490,40 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
430
490
|
CGFloat _contentPaddingHorizontal;
|
|
431
491
|
CGFloat _contentPaddingVertical;
|
|
432
492
|
BOOL _segmented;
|
|
493
|
+
BOOL _textVariant;
|
|
494
|
+
NSDictionary *_config;
|
|
495
|
+
RNCPagerToolbarButton *_toolbarButton;
|
|
496
|
+
NSString *_toolbarKey;
|
|
497
|
+
BOOL _toolbarPagerBusy;
|
|
498
|
+
NSInteger _toolbarPageIndex;
|
|
499
|
+
NSDictionary *_committedToolbarEntry;
|
|
500
|
+
NSString *_desiredToolbarKey;
|
|
501
|
+
NSDictionary *_desiredToolbarEntry;
|
|
502
|
+
UIImage *_loadedToolbarImage;
|
|
503
|
+
BOOL _toolbarPresenceAnimating;
|
|
504
|
+
BOOL _toolbarPresenceInterrupted;
|
|
505
|
+
NSString *_toolbarTouchKey;
|
|
506
|
+
BOOL _toolbarTouchStarted;
|
|
507
|
+
NSString *_iconIdentity;
|
|
508
|
+
NSObject *_iconOwner;
|
|
509
|
+
NSInteger _progressSource;
|
|
510
|
+
NSInteger _progressDestination;
|
|
511
|
+
CGFloat _progressFraction;
|
|
512
|
+
CGFloat _nativeProgress;
|
|
513
|
+
BOOL _hasNativeProgress;
|
|
514
|
+
BOOL _hasMappedPageProgress;
|
|
515
|
+
CGFloat _toolbarReservedWidth;
|
|
516
|
+
BOOL _toolbarVisible;
|
|
517
|
+
NSUInteger _toolbarAnimationGeneration;
|
|
518
|
+
NSString *_loadedIconIdentity;
|
|
519
|
+
BOOL _iconRequestPending;
|
|
520
|
+
UIImage *_readyToolbarImage;
|
|
521
|
+
NSString *_readyToolbarIdentity;
|
|
522
|
+
NSString *_failedIconIdentity;
|
|
523
|
+
CGFloat _toolbarSize;
|
|
524
|
+
CGFloat _toolbarIconSize;
|
|
525
|
+
CGFloat _toolbarContentWidth;
|
|
526
|
+
CGFloat _toolbarTrailingInset;
|
|
433
527
|
CGFloat _itemSpacing;
|
|
434
528
|
CGFloat _fontSize;
|
|
435
529
|
NSString *_fontFamily;
|
|
@@ -448,6 +542,21 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
448
542
|
{
|
|
449
543
|
if (self = [super initWithFrame:frame]) {
|
|
450
544
|
_items = @[];
|
|
545
|
+
_config = @{};
|
|
546
|
+
_toolbarPagerBusy = YES;
|
|
547
|
+
_toolbarPageIndex = -1;
|
|
548
|
+
_toolbarButton = [RNCPagerToolbarButton new];
|
|
549
|
+
_toolbarButton.layer.cornerRadius = 19;
|
|
550
|
+
_toolbarButton.hidden = YES;
|
|
551
|
+
_toolbarButton.alpha = 0;
|
|
552
|
+
_toolbarButton.userInteractionEnabled = NO;
|
|
553
|
+
_toolbarButton.accessibilityElementsHidden = YES;
|
|
554
|
+
_toolbarButton.imageView.contentMode = UIViewContentModeScaleAspectFit;
|
|
555
|
+
_toolbarButton.iconSize = 24;
|
|
556
|
+
[_toolbarButton addTarget:self action:@selector(toolbarTouchDown) forControlEvents:UIControlEventTouchDown];
|
|
557
|
+
[_toolbarButton addTarget:self action:@selector(toolbarPress:forEvent:) forControlEvents:UIControlEventTouchUpInside];
|
|
558
|
+
[_toolbarButton addTarget:self action:@selector(toolbarCancel) forControlEvents:UIControlEventTouchCancel | UIControlEventTouchUpOutside];
|
|
559
|
+
[self addSubview:_toolbarButton];
|
|
451
560
|
_buttons = [NSMutableArray new];
|
|
452
561
|
_indicatorFrames = [NSMutableArray new];
|
|
453
562
|
_barHeight = 44;
|
|
@@ -529,6 +638,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
529
638
|
for (UIButton *button in _buttons) [_scrollView bringSubviewToFront:button];
|
|
530
639
|
_shouldCenterSelectedItem = YES;
|
|
531
640
|
self.hidden = _items.count == 0;
|
|
641
|
+
if (_hasNativeProgress) [self setProgress:_nativeProgress];
|
|
642
|
+
else if ([_config[@"controlled"] boolValue]) {
|
|
643
|
+
NSUInteger selected = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:self->_config[@"selectedKey"]]; }];
|
|
644
|
+
if (selected != NSNotFound) { _progress = selected; _progressSource = selected; _progressDestination = selected; _progressFraction = 0; }
|
|
645
|
+
}
|
|
532
646
|
[self setNeedsLayout];
|
|
533
647
|
}
|
|
534
648
|
|
|
@@ -549,6 +663,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
549
663
|
_barHeight = MAX(1, height);
|
|
550
664
|
BOOL wasSegmented = _segmented;
|
|
551
665
|
_segmented = [variant isEqualToString:@"segmented"];
|
|
666
|
+
_textVariant = [variant isEqualToString:@"text"];
|
|
552
667
|
_contentPaddingHorizontal = MAX(0, contentPaddingHorizontal);
|
|
553
668
|
_contentPaddingVertical = MAX(0, contentPaddingVertical);
|
|
554
669
|
_itemSpacing = MAX(0, itemSpacing);
|
|
@@ -624,9 +739,15 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
624
739
|
- (void)layoutSubviews
|
|
625
740
|
{
|
|
626
741
|
[super layoutSubviews];
|
|
627
|
-
|
|
742
|
+
CGFloat reserved = _toolbarReservedWidth;
|
|
743
|
+
[self bringSubviewToFront:_toolbarButton];
|
|
744
|
+
_scrollView.frame = CGRectMake(_leftToRight ? 0 : reserved, 0,
|
|
745
|
+
MAX(0, CGRectGetWidth(self.bounds) - reserved), CGRectGetHeight(self.bounds));
|
|
746
|
+
CGFloat inset = _toolbarTrailingInset - (_toolbarSize - _toolbarContentWidth) / 2;
|
|
747
|
+
_toolbarButton.frame = CGRectMake(_leftToRight ? CGRectGetWidth(self.bounds) - inset - _toolbarSize : inset,
|
|
748
|
+
(CGRectGetHeight(self.bounds) - _toolbarSize) / 2, _toolbarSize, _toolbarSize);
|
|
628
749
|
if (_segmented) {
|
|
629
|
-
CGFloat width = CGRectGetWidth(
|
|
750
|
+
CGFloat width = CGRectGetWidth(_scrollView.bounds);
|
|
630
751
|
CGFloat height = CGRectGetHeight(self.bounds);
|
|
631
752
|
CGFloat insetX = MIN(_contentPaddingHorizontal, width / 2);
|
|
632
753
|
CGFloat insetY = MIN(_contentPaddingVertical, height / 2);
|
|
@@ -634,7 +755,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
634
755
|
MAX(0, width - 2 * insetX), MAX(0, height - 2 * insetY));
|
|
635
756
|
_trackView.frame = trackFrame;
|
|
636
757
|
_trackView.layer.cornerRadius = CGRectGetHeight(trackFrame) / 2;
|
|
637
|
-
_scrollView.contentSize =
|
|
758
|
+
_scrollView.contentSize = _scrollView.bounds.size;
|
|
638
759
|
[_indicatorFrames removeAllObjects];
|
|
639
760
|
NSUInteger count = _buttons.count;
|
|
640
761
|
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
@@ -658,12 +779,17 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
658
779
|
for (UIButton *button in _buttons) {
|
|
659
780
|
NSString *title = [button titleForState:UIControlStateNormal] ?: @"";
|
|
660
781
|
CGFloat textWidth = ceil([title sizeWithAttributes:@{NSFontAttributeName: font}].width);
|
|
661
|
-
|
|
782
|
+
if (_textVariant) {
|
|
783
|
+
UIFont *activeFont = [UIFont fontWithName:_config[@"activeFontFamily"] ?: @"" size:_fontSize]
|
|
784
|
+
?: [UIFont systemFontOfSize:_fontSize weight:UIFontWeightSemibold];
|
|
785
|
+
textWidth = MAX(textWidth, ceil([title sizeWithAttributes:@{NSFontAttributeName: activeFont}].width));
|
|
786
|
+
}
|
|
787
|
+
CGFloat buttonWidth = MAX(44, textWidth + (_textVariant ? 20 : 16));
|
|
662
788
|
[widths addObject:@(buttonWidth)];
|
|
663
789
|
itemsWidth += buttonWidth;
|
|
664
790
|
}
|
|
665
791
|
if (_buttons.count > 1) itemsWidth += (_buttons.count - 1) * _itemSpacing;
|
|
666
|
-
CGFloat contentWidth = MAX(CGRectGetWidth(
|
|
792
|
+
CGFloat contentWidth = MAX(CGRectGetWidth(_scrollView.bounds),
|
|
667
793
|
itemsWidth + _contentPaddingHorizontal * 2);
|
|
668
794
|
_scrollView.contentSize = CGSizeMake(contentWidth, MAX(_barHeight, CGRectGetHeight(self.bounds)));
|
|
669
795
|
[_indicatorFrames removeAllObjects];
|
|
@@ -693,14 +819,493 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
693
819
|
[self updatePresentation];
|
|
694
820
|
}
|
|
695
821
|
|
|
822
|
+
- (Class<RNCPagerNamedSVGBridge>)iconBridge {
|
|
823
|
+
Class cls = NSClassFromString(@"OneKeyNamedSVGBridge");
|
|
824
|
+
if (![cls respondsToSelector:@selector(cancelWithOwner:)] ||
|
|
825
|
+
![cls respondsToSelector:@selector(requestWithOwner:name:color:width:height:scale:complete:)]) return Nil;
|
|
826
|
+
return (Class<RNCPagerNamedSVGBridge>)cls;
|
|
827
|
+
}
|
|
828
|
+
- (void)updateConfigJSON:(NSString *)json {
|
|
829
|
+
BOOL wasOwned = [self ownsSettledToolbar];
|
|
830
|
+
NSData *data = [json dataUsingEncoding:NSUTF8StringEncoding];
|
|
831
|
+
id parsed = data ? [NSJSONSerialization JSONObjectWithData:data options:0 error:nil] : nil;
|
|
832
|
+
_config = [parsed isKindOfClass:NSDictionary.class] ? parsed : @{};
|
|
833
|
+
BOOL owned = [self ownsSettledToolbar];
|
|
834
|
+
if (owned != wasOwned) {
|
|
835
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
836
|
+
_iconOwner = [NSObject new];
|
|
837
|
+
_iconRequestPending = NO;
|
|
838
|
+
_readyToolbarImage = nil;
|
|
839
|
+
_readyToolbarIdentity = nil;
|
|
840
|
+
_desiredToolbarKey = nil;
|
|
841
|
+
_desiredToolbarEntry = nil;
|
|
842
|
+
[self suspendToolbar];
|
|
843
|
+
if (!owned) {
|
|
844
|
+
++_toolbarAnimationGeneration;
|
|
845
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
846
|
+
_toolbarPresenceAnimating = NO;
|
|
847
|
+
_toolbarPresenceInterrupted = NO;
|
|
848
|
+
_toolbarButton.alpha = _toolbarVisible ? 1 : 0;
|
|
849
|
+
_toolbarButton.hidden = !_toolbarVisible;
|
|
850
|
+
[self setToolbarInteractive:_toolbarVisible];
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (!owned) {
|
|
854
|
+
_toolbarTouchKey = nil;
|
|
855
|
+
_toolbarButton.highlighted = NO;
|
|
856
|
+
_toolbarButton.hovering = NO;
|
|
857
|
+
}
|
|
858
|
+
_toolbarButton.adjustsImageWhenDisabled = !owned;
|
|
859
|
+
NSString *key = _config[@"selectedKey"];
|
|
860
|
+
if (![_config[@"controlled"] boolValue]) {
|
|
861
|
+
_progress = RNCClamp(_nativeProgress, 0, MAX(0, (NSInteger)_items.count - 1));
|
|
862
|
+
key = _items.count ? _items[(NSInteger)round(_progress)][@"key"] : nil;
|
|
863
|
+
}
|
|
864
|
+
if (_hasNativeProgress) {
|
|
865
|
+
[self setProgress:_nativeProgress];
|
|
866
|
+
} else {
|
|
867
|
+
NSUInteger index = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:key]; }];
|
|
868
|
+
if (index != NSNotFound) {
|
|
869
|
+
_progress = index;
|
|
870
|
+
_progressSource = index;
|
|
871
|
+
_progressDestination = index;
|
|
872
|
+
_progressFraction = 0;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
[self updateToolbarForKey:key];
|
|
876
|
+
[self setNeedsLayout];
|
|
877
|
+
}
|
|
878
|
+
- (BOOL)usesControlledTextProgress { return _textVariant && [_config[@"controlled"] boolValue]; }
|
|
879
|
+
- (void)setLegacyToolbarVisible:(BOOL)visible {
|
|
880
|
+
_toolbarButton.enabled = visible;
|
|
881
|
+
_toolbarButton.userInteractionEnabled = visible;
|
|
882
|
+
_toolbarButton.accessibilityElementsHidden = !visible;
|
|
883
|
+
_toolbarButton.isAccessibilityElement = visible;
|
|
884
|
+
if (_toolbarVisible == visible) return;
|
|
885
|
+
_toolbarVisible = visible;
|
|
886
|
+
NSUInteger generation = ++_toolbarAnimationGeneration;
|
|
887
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
888
|
+
CGFloat alpha = presentation ? presentation.opacity : _toolbarButton.alpha;
|
|
889
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
890
|
+
_toolbarButton.alpha = alpha;
|
|
891
|
+
_toolbarButton.enabled = visible;
|
|
892
|
+
_toolbarButton.userInteractionEnabled = visible;
|
|
893
|
+
_toolbarButton.accessibilityElementsHidden = !visible;
|
|
894
|
+
if (visible) _toolbarButton.hidden = NO;
|
|
895
|
+
if (self.window == nil) {
|
|
896
|
+
_toolbarButton.alpha = visible ? 1 : 0;
|
|
897
|
+
_toolbarButton.hidden = !visible;
|
|
898
|
+
return;
|
|
899
|
+
}
|
|
900
|
+
[UIView animateWithDuration:0.15 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
|
|
901
|
+
self->_toolbarButton.alpha = visible ? 1 : 0;
|
|
902
|
+
} completion:^(BOOL finished) {
|
|
903
|
+
if (self->_toolbarAnimationGeneration == generation && !visible) {
|
|
904
|
+
self->_toolbarButton.hidden = YES;
|
|
905
|
+
[self applyReadyLegacyToolbarImage];
|
|
906
|
+
}
|
|
907
|
+
}];
|
|
908
|
+
}
|
|
909
|
+
- (void)applyReadyLegacyToolbarImage {
|
|
910
|
+
if (_readyToolbarImage == nil || ![_readyToolbarIdentity isEqualToString:_iconIdentity]) return;
|
|
911
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
912
|
+
if (_toolbarButton.alpha > 0.001 || (presentation && presentation.opacity > 0.001)) return;
|
|
913
|
+
[_toolbarButton setImage:_readyToolbarImage forState:UIControlStateNormal];
|
|
914
|
+
_loadedIconIdentity = _readyToolbarIdentity;
|
|
915
|
+
_loadedToolbarImage = _readyToolbarImage;
|
|
916
|
+
_readyToolbarImage = nil;
|
|
917
|
+
_readyToolbarIdentity = nil;
|
|
918
|
+
_iconRequestPending = NO;
|
|
919
|
+
[self setLegacyToolbarVisible:YES];
|
|
920
|
+
}
|
|
921
|
+
- (void)updateLegacyToolbarForKey:(NSString *)key {
|
|
922
|
+
NSDictionary *toolbar = [_config[@"toolbar"] isKindOfClass:NSDictionary.class] ? _config[@"toolbar"] : nil;
|
|
923
|
+
_toolbarReservedWidth = toolbar == nil ? 0 : 44;
|
|
924
|
+
if (toolbar.count > 0) {
|
|
925
|
+
_toolbarReservedWidth = 0;
|
|
926
|
+
for (id value in toolbar.allValues) {
|
|
927
|
+
NSDictionary *entry = [value isKindOfClass:NSDictionary.class] ? value : @{};
|
|
928
|
+
CGFloat size = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
929
|
+
CGFloat content = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, size), 0, size);
|
|
930
|
+
CGFloat trailing = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
931
|
+
_toolbarReservedWidth = MAX(_toolbarReservedWidth, content + trailing);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
NSDictionary *entry = [toolbar[key ?: @""] isKindOfClass:NSDictionary.class] ? toolbar[key] : nil;
|
|
935
|
+
NSString *name = [entry[@"iconName"] isKindOfClass:NSString.class] ? entry[@"iconName"] : nil;
|
|
936
|
+
if (![_toolbarKey isEqualToString:key]) {
|
|
937
|
+
_toolbarTouchKey = nil;
|
|
938
|
+
_toolbarButton.highlighted = NO;
|
|
939
|
+
_toolbarButton.hovering = NO;
|
|
940
|
+
}
|
|
941
|
+
_toolbarKey = [key copy];
|
|
942
|
+
if (name.length == 0) {
|
|
943
|
+
_toolbarTouchKey = nil;
|
|
944
|
+
_toolbarButton.accessibilityLabel = nil;
|
|
945
|
+
_toolbarButton.accessibilityIdentifier = nil;
|
|
946
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
947
|
+
_iconOwner = [NSObject new];
|
|
948
|
+
_iconRequestPending = NO;
|
|
949
|
+
_readyToolbarImage = nil;
|
|
950
|
+
_readyToolbarIdentity = nil;
|
|
951
|
+
[self setLegacyToolbarVisible:NO];
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
954
|
+
_toolbarSize = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
955
|
+
_toolbarIconSize = RNCPagerToolbarDimension(entry, @"iconSize", MIN(24, _toolbarSize), 1, _toolbarSize);
|
|
956
|
+
_toolbarContentWidth = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, _toolbarSize), 0, _toolbarSize);
|
|
957
|
+
_toolbarTrailingInset = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
958
|
+
_toolbarButton.layer.cornerRadius = _toolbarSize / 2;
|
|
959
|
+
_toolbarButton.iconSize = _toolbarIconSize;
|
|
960
|
+
[_toolbarButton setNeedsLayout];
|
|
961
|
+
_toolbarButton.baseColor = [entry[@"selected"] boolValue] ? ([RCTConvert UIColor:entry[@"selectedBackgroundColor"]] ?: UIColor.clearColor) : UIColor.clearColor;
|
|
962
|
+
_toolbarButton.hoverColor = [RCTConvert UIColor:entry[@"hoverBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
963
|
+
_toolbarButton.pressedColor = [RCTConvert UIColor:entry[@"pressedBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
964
|
+
[_toolbarButton refreshBackground];
|
|
965
|
+
_toolbarButton.accessibilityLabel = entry[@"accessibilityLabel"];
|
|
966
|
+
_toolbarButton.accessibilityIdentifier = entry[@"testID"];
|
|
967
|
+
NSString *color = [entry[@"iconColor"] isKindOfClass:NSString.class] ? entry[@"iconColor"] : @"#000000";
|
|
968
|
+
CGFloat scale = self.window.screen.scale ?: UIScreen.mainScreen.scale;
|
|
969
|
+
NSString *identity = [NSString stringWithFormat:@"%@|%@|%g|%g", name, color, scale, _toolbarIconSize];
|
|
970
|
+
if ([_loadedIconIdentity isEqualToString:identity] && [_toolbarButton imageForState:UIControlStateNormal] != nil) {
|
|
971
|
+
if (_iconRequestPending) {
|
|
972
|
+
[[self iconBridge] cancelWithOwner:_iconOwner];
|
|
973
|
+
_iconOwner = [NSObject new];
|
|
974
|
+
}
|
|
975
|
+
_iconIdentity = identity;
|
|
976
|
+
_iconRequestPending = NO;
|
|
977
|
+
_readyToolbarImage = nil;
|
|
978
|
+
_readyToolbarIdentity = nil;
|
|
979
|
+
[self setLegacyToolbarVisible:YES];
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
if ((_iconRequestPending || [_failedIconIdentity isEqualToString:identity]) && [_iconIdentity isEqualToString:identity]) return;
|
|
983
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
984
|
+
NSObject *owner = [NSObject new];
|
|
985
|
+
_iconOwner = owner;
|
|
986
|
+
_iconIdentity = identity;
|
|
987
|
+
_iconRequestPending = NO;
|
|
988
|
+
_readyToolbarImage = nil;
|
|
989
|
+
_readyToolbarIdentity = nil;
|
|
990
|
+
[self setLegacyToolbarVisible:NO];
|
|
991
|
+
if ([_failedIconIdentity isEqualToString:identity] || self.window == nil) return;
|
|
992
|
+
_iconRequestPending = YES;
|
|
993
|
+
__weak __typeof__(self) weakSelf = self;
|
|
994
|
+
[[self iconBridge] requestWithOwner:owner name:name color:color width:(NSInteger)ceil(_toolbarIconSize * scale) height:(NSInteger)ceil(_toolbarIconSize * scale) scale:scale complete:^(UIImage *image) {
|
|
995
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
996
|
+
if (!self || self->_iconOwner != owner || ![self->_iconIdentity isEqualToString:identity] || !self.window) return;
|
|
997
|
+
if (image == nil) {
|
|
998
|
+
self->_iconRequestPending = NO;
|
|
999
|
+
self->_failedIconIdentity = identity;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
UIImage *scaled = image.CGImage ? [UIImage imageWithCGImage:image.CGImage scale:scale orientation:UIImageOrientationUp] : image;
|
|
1003
|
+
self->_readyToolbarImage = [scaled imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
|
1004
|
+
self->_readyToolbarIdentity = identity;
|
|
1005
|
+
[self applyReadyLegacyToolbarImage];
|
|
1006
|
+
}];
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
- (BOOL)ownsSettledToolbar {
|
|
1010
|
+
return [_config[@"controlled"] boolValue] && [_config[@"toolbar"] isKindOfClass:NSDictionary.class];
|
|
1011
|
+
}
|
|
1012
|
+
- (BOOL)toolbarCanCommit {
|
|
1013
|
+
if (!self.window) return NO;
|
|
1014
|
+
if (![self ownsSettledToolbar]) return YES;
|
|
1015
|
+
if (_toolbarPagerBusy || _toolbarPageIndex < 0) return NO;
|
|
1016
|
+
NSArray *pages = [_config[@"pageKeys"] isKindOfClass:NSArray.class] ? _config[@"pageKeys"] : @[];
|
|
1017
|
+
NSArray *items = [_items valueForKey:@"key"];
|
|
1018
|
+
BOOL valid = pages.count > 0 && [pages containsObject:_config[@"selectedKey"] ?: @""] &&
|
|
1019
|
+
[NSSet setWithArray:pages].count == pages.count && [NSSet setWithArray:items].count == items.count;
|
|
1020
|
+
for (id key in pages) if (![items containsObject:key]) { valid = NO; break; }
|
|
1021
|
+
return !valid || (_toolbarPageIndex < pages.count && [pages[_toolbarPageIndex] isEqual:_config[@"selectedKey"]]);
|
|
1022
|
+
}
|
|
1023
|
+
- (void)setToolbarInteractive:(BOOL)interactive {
|
|
1024
|
+
_toolbarButton.enabled = interactive;
|
|
1025
|
+
_toolbarButton.userInteractionEnabled = interactive;
|
|
1026
|
+
_toolbarButton.accessibilityElementsHidden = !interactive;
|
|
1027
|
+
_toolbarButton.isAccessibilityElement = interactive;
|
|
1028
|
+
if (!interactive) {
|
|
1029
|
+
_toolbarTouchKey = nil;
|
|
1030
|
+
_toolbarButton.highlighted = NO;
|
|
1031
|
+
_toolbarButton.hovering = NO;
|
|
1032
|
+
[_toolbarButton refreshBackground];
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
- (void)cancelToolbarRequest {
|
|
1036
|
+
if (_iconRequestPending || _readyToolbarImage) {
|
|
1037
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
1038
|
+
_iconOwner = [NSObject new];
|
|
1039
|
+
_iconRequestPending = NO;
|
|
1040
|
+
_readyToolbarImage = nil;
|
|
1041
|
+
_readyToolbarIdentity = nil;
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
- (void)suspendToolbar {
|
|
1045
|
+
[self setToolbarInteractive:NO];
|
|
1046
|
+
[self cancelToolbarRequest];
|
|
1047
|
+
_desiredToolbarKey = nil;
|
|
1048
|
+
_desiredToolbarEntry = nil;
|
|
1049
|
+
if (_toolbarPresenceAnimating) {
|
|
1050
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
1051
|
+
CGFloat alpha = presentation ? presentation.opacity : _toolbarButton.alpha;
|
|
1052
|
+
++_toolbarAnimationGeneration;
|
|
1053
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
1054
|
+
_toolbarButton.alpha = alpha;
|
|
1055
|
+
_toolbarPresenceAnimating = NO;
|
|
1056
|
+
_toolbarPresenceInterrupted = YES;
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
- (void)updateToolbarPagerIndex:(NSInteger)index busy:(BOOL)busy {
|
|
1060
|
+
if (_toolbarPagerBusy == busy && (busy || _toolbarPageIndex == index)) return;
|
|
1061
|
+
_toolbarPagerBusy = busy;
|
|
1062
|
+
if (!busy) _toolbarPageIndex = index;
|
|
1063
|
+
NSString *key = [_config[@"controlled"] boolValue] ? _config[@"selectedKey"] : _toolbarKey;
|
|
1064
|
+
[self updateToolbarForKey:key];
|
|
1065
|
+
}
|
|
1066
|
+
- (void)setToolbarVisible:(BOOL)visible {
|
|
1067
|
+
if (_toolbarVisible == visible && !_toolbarPresenceInterrupted) return;
|
|
1068
|
+
_toolbarVisible = visible;
|
|
1069
|
+
_toolbarPresenceInterrupted = NO;
|
|
1070
|
+
NSUInteger generation = ++_toolbarAnimationGeneration;
|
|
1071
|
+
CALayer *presentation = _toolbarButton.layer.presentationLayer;
|
|
1072
|
+
CGFloat alpha = presentation ? presentation.opacity : _toolbarButton.alpha;
|
|
1073
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
1074
|
+
_toolbarButton.alpha = alpha;
|
|
1075
|
+
if (visible) _toolbarButton.hidden = NO;
|
|
1076
|
+
if (!self.window) {
|
|
1077
|
+
_toolbarPresenceAnimating = NO;
|
|
1078
|
+
_toolbarButton.alpha = visible ? 1 : 0;
|
|
1079
|
+
_toolbarButton.hidden = !visible;
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
_toolbarPresenceAnimating = YES;
|
|
1083
|
+
[UIView animateWithDuration:0.10 delay:0 options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction animations:^{
|
|
1084
|
+
self->_toolbarButton.alpha = visible ? 1 : 0;
|
|
1085
|
+
} completion:^(BOOL finished) {
|
|
1086
|
+
if (self->_toolbarAnimationGeneration != generation) return;
|
|
1087
|
+
self->_toolbarPresenceAnimating = NO;
|
|
1088
|
+
if (!visible) self->_toolbarButton.hidden = YES;
|
|
1089
|
+
}];
|
|
1090
|
+
}
|
|
1091
|
+
- (void)commitToolbarImage:(UIImage *)image identity:(NSString *)identity {
|
|
1092
|
+
if (![self toolbarCanCommit] || !_desiredToolbarKey || !_desiredToolbarEntry) return;
|
|
1093
|
+
NSDictionary *entry = _desiredToolbarEntry;
|
|
1094
|
+
BOOL changed = ![_toolbarKey isEqualToString:_desiredToolbarKey] || ![_committedToolbarEntry isEqual:entry] || _loadedToolbarImage != image;
|
|
1095
|
+
if (![_toolbarKey isEqualToString:_desiredToolbarKey]) [self setToolbarInteractive:NO];
|
|
1096
|
+
_toolbarKey = [_desiredToolbarKey copy];
|
|
1097
|
+
_committedToolbarEntry = entry;
|
|
1098
|
+
_toolbarSize = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
1099
|
+
_toolbarIconSize = RNCPagerToolbarDimension(entry, @"iconSize", MIN(24, _toolbarSize), 1, _toolbarSize);
|
|
1100
|
+
_toolbarContentWidth = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, _toolbarSize), 0, _toolbarSize);
|
|
1101
|
+
_toolbarTrailingInset = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
1102
|
+
_toolbarButton.layer.cornerRadius = _toolbarSize / 2;
|
|
1103
|
+
_toolbarButton.iconSize = _toolbarIconSize;
|
|
1104
|
+
[_toolbarButton setNeedsLayout];
|
|
1105
|
+
_toolbarButton.baseColor = [entry[@"selected"] boolValue] ? ([RCTConvert UIColor:entry[@"selectedBackgroundColor"]] ?: UIColor.clearColor) : UIColor.clearColor;
|
|
1106
|
+
_toolbarButton.hoverColor = [RCTConvert UIColor:entry[@"hoverBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
1107
|
+
_toolbarButton.pressedColor = [RCTConvert UIColor:entry[@"pressedBackgroundColor"]] ?: _toolbarButton.baseColor;
|
|
1108
|
+
[_toolbarButton refreshBackground];
|
|
1109
|
+
_toolbarButton.accessibilityLabel = entry[@"accessibilityLabel"];
|
|
1110
|
+
_toolbarButton.accessibilityIdentifier = entry[@"testID"];
|
|
1111
|
+
[_toolbarButton setImage:image forState:UIControlStateNormal];
|
|
1112
|
+
_loadedIconIdentity = identity;
|
|
1113
|
+
_loadedToolbarImage = image;
|
|
1114
|
+
_readyToolbarImage = nil;
|
|
1115
|
+
_readyToolbarIdentity = nil;
|
|
1116
|
+
_iconRequestPending = NO;
|
|
1117
|
+
[self setToolbarVisible:YES];
|
|
1118
|
+
[self setToolbarInteractive:YES];
|
|
1119
|
+
if (changed) [self setNeedsLayout];
|
|
1120
|
+
}
|
|
1121
|
+
- (void)applyReadyToolbarImage {
|
|
1122
|
+
if (!_readyToolbarImage || ![_readyToolbarIdentity isEqualToString:_iconIdentity] || ![self toolbarCanCommit]) return;
|
|
1123
|
+
[self commitToolbarImage:_readyToolbarImage identity:_readyToolbarIdentity];
|
|
1124
|
+
}
|
|
1125
|
+
- (void)hideFailedToolbar {
|
|
1126
|
+
[self setToolbarInteractive:NO];
|
|
1127
|
+
_toolbarKey = nil;
|
|
1128
|
+
_committedToolbarEntry = nil;
|
|
1129
|
+
[_toolbarButton setImage:nil forState:UIControlStateNormal];
|
|
1130
|
+
_toolbarButton.accessibilityLabel = nil;
|
|
1131
|
+
_toolbarButton.accessibilityIdentifier = nil;
|
|
1132
|
+
[self setToolbarVisible:NO];
|
|
1133
|
+
}
|
|
1134
|
+
- (void)updateToolbarForKey:(NSString *)key {
|
|
1135
|
+
if (![self ownsSettledToolbar]) { [self updateLegacyToolbarForKey:key]; return; }
|
|
1136
|
+
NSDictionary *toolbar = [_config[@"toolbar"] isKindOfClass:NSDictionary.class] ? _config[@"toolbar"] : nil;
|
|
1137
|
+
_toolbarReservedWidth = toolbar == nil ? 0 : 44;
|
|
1138
|
+
if (toolbar.count > 0) {
|
|
1139
|
+
_toolbarReservedWidth = 0;
|
|
1140
|
+
for (id value in toolbar.allValues) {
|
|
1141
|
+
NSDictionary *entry = [value isKindOfClass:NSDictionary.class] ? value : @{};
|
|
1142
|
+
CGFloat size = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
1143
|
+
CGFloat content = RNCPagerToolbarDimension(entry, @"contentWidth", MIN(24, size), 0, size);
|
|
1144
|
+
CGFloat trailing = RNCPagerToolbarDimension(entry, @"trailingInset", 20, 0, 128);
|
|
1145
|
+
_toolbarReservedWidth = MAX(_toolbarReservedWidth, content + trailing);
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
if (![self toolbarCanCommit]) { [self suspendToolbar]; return; }
|
|
1149
|
+
NSDictionary *entry = [toolbar[key ?: @""] isKindOfClass:NSDictionary.class] ? toolbar[key] : nil;
|
|
1150
|
+
NSString *name = [entry[@"iconName"] isKindOfClass:NSString.class] ? entry[@"iconName"] : nil;
|
|
1151
|
+
if (!name.length) {
|
|
1152
|
+
[self cancelToolbarRequest];
|
|
1153
|
+
_desiredToolbarKey = nil;
|
|
1154
|
+
_desiredToolbarEntry = nil;
|
|
1155
|
+
_toolbarKey = nil;
|
|
1156
|
+
_committedToolbarEntry = nil;
|
|
1157
|
+
[self setToolbarInteractive:NO];
|
|
1158
|
+
_toolbarButton.accessibilityLabel = nil;
|
|
1159
|
+
_toolbarButton.accessibilityIdentifier = nil;
|
|
1160
|
+
[self setToolbarVisible:NO];
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
_desiredToolbarKey = [key copy];
|
|
1164
|
+
_desiredToolbarEntry = [entry copy];
|
|
1165
|
+
CGFloat size = RNCPagerToolbarDimension(entry, @"size", 38, 1, 128);
|
|
1166
|
+
CGFloat iconSize = RNCPagerToolbarDimension(entry, @"iconSize", MIN(24, size), 1, size);
|
|
1167
|
+
NSString *color = [entry[@"iconColor"] isKindOfClass:NSString.class] ? entry[@"iconColor"] : @"#000000";
|
|
1168
|
+
CGFloat scale = self.window.screen.scale ?: UIScreen.mainScreen.scale;
|
|
1169
|
+
NSString *identity = [NSString stringWithFormat:@"%@|%@|%g|%g", name, color, scale, iconSize];
|
|
1170
|
+
if ([_loadedIconIdentity isEqualToString:identity] && _loadedToolbarImage) {
|
|
1171
|
+
[self cancelToolbarRequest];
|
|
1172
|
+
_iconIdentity = identity;
|
|
1173
|
+
[self commitToolbarImage:_loadedToolbarImage identity:identity];
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
if ([_iconIdentity isEqualToString:identity] && (_iconRequestPending || _readyToolbarImage)) {
|
|
1177
|
+
[self setToolbarInteractive:NO];
|
|
1178
|
+
[self applyReadyToolbarImage];
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
[self cancelToolbarRequest];
|
|
1182
|
+
_iconIdentity = identity;
|
|
1183
|
+
[self setToolbarInteractive:NO];
|
|
1184
|
+
if ([_failedIconIdentity isEqualToString:identity]) { [self hideFailedToolbar]; return; }
|
|
1185
|
+
NSObject *owner = [NSObject new];
|
|
1186
|
+
_iconOwner = owner;
|
|
1187
|
+
_iconRequestPending = YES;
|
|
1188
|
+
__weak __typeof__(self) weakSelf = self;
|
|
1189
|
+
[[self iconBridge] requestWithOwner:owner name:name color:color width:(NSInteger)ceil(iconSize * scale) height:(NSInteger)ceil(iconSize * scale) scale:scale complete:^(UIImage *image) {
|
|
1190
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
1191
|
+
if (!self || self->_iconOwner != owner || ![self->_iconIdentity isEqualToString:identity] || !self.window) return;
|
|
1192
|
+
self->_iconRequestPending = NO;
|
|
1193
|
+
if (!image) {
|
|
1194
|
+
self->_failedIconIdentity = identity;
|
|
1195
|
+
if ([self toolbarCanCommit]) [self hideFailedToolbar];
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
UIImage *scaled = image.CGImage ? [UIImage imageWithCGImage:image.CGImage scale:scale orientation:UIImageOrientationUp] : image;
|
|
1199
|
+
self->_readyToolbarImage = [scaled imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
|
1200
|
+
self->_readyToolbarIdentity = identity;
|
|
1201
|
+
[self applyReadyToolbarImage];
|
|
1202
|
+
}];
|
|
1203
|
+
}
|
|
1204
|
+
- (void)didMoveToWindow {
|
|
1205
|
+
[super didMoveToWindow];
|
|
1206
|
+
if (![self ownsSettledToolbar]) {
|
|
1207
|
+
if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner];
|
|
1208
|
+
_iconIdentity = nil;
|
|
1209
|
+
_loadedIconIdentity = nil;
|
|
1210
|
+
_iconRequestPending = NO;
|
|
1211
|
+
_readyToolbarImage = nil;
|
|
1212
|
+
_readyToolbarIdentity = nil;
|
|
1213
|
+
_failedIconIdentity = nil;
|
|
1214
|
+
_iconOwner = [NSObject new];
|
|
1215
|
+
_toolbarAnimationGeneration++;
|
|
1216
|
+
[_toolbarButton.layer removeAllAnimations];
|
|
1217
|
+
_toolbarVisible = NO;
|
|
1218
|
+
_toolbarButton.alpha = 0;
|
|
1219
|
+
_toolbarButton.hidden = YES;
|
|
1220
|
+
_toolbarButton.enabled = NO;
|
|
1221
|
+
_toolbarButton.userInteractionEnabled = NO;
|
|
1222
|
+
_toolbarButton.accessibilityElementsHidden = YES;
|
|
1223
|
+
_toolbarTouchKey = nil;
|
|
1224
|
+
_toolbarTouchStarted = NO;
|
|
1225
|
+
_toolbarButton.highlighted = NO;
|
|
1226
|
+
_toolbarButton.hovering = NO;
|
|
1227
|
+
[self updateToolbarForKey:_toolbarKey];
|
|
1228
|
+
return;
|
|
1229
|
+
}
|
|
1230
|
+
[self suspendToolbar];
|
|
1231
|
+
_toolbarPagerBusy = YES;
|
|
1232
|
+
_toolbarPageIndex = -1;
|
|
1233
|
+
_toolbarTouchStarted = NO;
|
|
1234
|
+
if (self.window && self.onToolbarNeedsSettlement) self.onToolbarNeedsSettlement();
|
|
1235
|
+
}
|
|
1236
|
+
- (void)dealloc { if (_iconOwner) [[self iconBridge] cancelWithOwner:_iconOwner]; }
|
|
1237
|
+
- (void)toolbarTouchDown {
|
|
1238
|
+
_toolbarTouchStarted = YES;
|
|
1239
|
+
_toolbarTouchKey = _toolbarButton.enabled && [self toolbarCanCommit] ? [_toolbarKey copy] : nil;
|
|
1240
|
+
}
|
|
1241
|
+
- (void)toolbarCancel {
|
|
1242
|
+
_toolbarTouchKey = nil;
|
|
1243
|
+
if (![self ownsSettledToolbar]) _toolbarTouchStarted = NO;
|
|
1244
|
+
}
|
|
1245
|
+
- (void)toolbarPress:(UIControl *)sender forEvent:(UIEvent *)event {
|
|
1246
|
+
if (![self ownsSettledToolbar]) {
|
|
1247
|
+
NSString *key = _toolbarTouchStarted ? _toolbarTouchKey : _toolbarKey;
|
|
1248
|
+
_toolbarTouchStarted = NO; _toolbarTouchKey = nil;
|
|
1249
|
+
if (key && [key isEqualToString:_toolbarKey] && !_toolbarButton.hidden && _toolbarButton.enabled && self.window && self.onToolbarPress)
|
|
1250
|
+
self.onToolbarPress(key, [_toolbarButton convertRect:_toolbarButton.bounds toView:self.window]);
|
|
1251
|
+
return;
|
|
1252
|
+
}
|
|
1253
|
+
BOOL pointer = event && event.type == UIEventTypeTouches;
|
|
1254
|
+
NSString *key = pointer ? (_toolbarTouchStarted ? _toolbarTouchKey : nil) : _toolbarKey;
|
|
1255
|
+
if (pointer) { _toolbarTouchStarted = NO; _toolbarTouchKey = nil; }
|
|
1256
|
+
BOOL selectedMatches = ![_config[@"controlled"] boolValue] || [key isEqual:_config[@"selectedKey"]];
|
|
1257
|
+
if (key && selectedMatches && [key isEqualToString:_toolbarKey] && [self toolbarCanCommit] &&
|
|
1258
|
+
!_toolbarButton.hidden && _toolbarButton.enabled && self.onToolbarPress) {
|
|
1259
|
+
self.onToolbarPress(key, [_toolbarButton convertRect:_toolbarButton.bounds toView:self.window]);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
|
|
696
1263
|
- (void)setProgress:(CGFloat)progress
|
|
697
1264
|
{
|
|
1265
|
+
_hasMappedPageProgress = NO;
|
|
1266
|
+
_nativeProgress = progress;
|
|
1267
|
+
_hasNativeProgress = YES;
|
|
698
1268
|
if (_items.count == 0) return;
|
|
699
1269
|
if (fabs(_progress - progress) >= 0.001) _shouldCenterSelectedItem = YES;
|
|
1270
|
+
if ([_config[@"controlled"] boolValue]) {
|
|
1271
|
+
NSArray *pageKeys = [_config[@"pageKeys"] isKindOfClass:NSArray.class] ? _config[@"pageKeys"] : @[];
|
|
1272
|
+
NSMutableArray<NSNumber *> *indices = [NSMutableArray new];
|
|
1273
|
+
NSSet *unique = [NSSet setWithArray:pageKeys];
|
|
1274
|
+
NSArray *itemKeys = [_items valueForKey:@"key"];
|
|
1275
|
+
BOOL valid = pageKeys.count > 0 && [pageKeys containsObject:_config[@"selectedKey"] ?: @""] && unique.count == pageKeys.count && [NSSet setWithArray:itemKeys].count == itemKeys.count;
|
|
1276
|
+
for (NSString *key in pageKeys) {
|
|
1277
|
+
NSUInteger index = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:key]; }];
|
|
1278
|
+
if (index == NSNotFound) { valid = NO; break; }
|
|
1279
|
+
[indices addObject:@(index)];
|
|
1280
|
+
}
|
|
1281
|
+
if (!valid) {
|
|
1282
|
+
NSUInteger selected = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:self->_config[@"selectedKey"]]; }];
|
|
1283
|
+
if (selected == NSNotFound) return;
|
|
1284
|
+
_progressSource = selected; _progressDestination = selected; _progressFraction = 0;
|
|
1285
|
+
_progress = selected;
|
|
1286
|
+
if (_indicatorFrames.count == _items.count) [self updatePresentation];
|
|
1287
|
+
return;
|
|
1288
|
+
}
|
|
1289
|
+
_hasMappedPageProgress = YES;
|
|
1290
|
+
CGFloat bounded = RNCClamp(progress, 0, pageKeys.count - 1);
|
|
1291
|
+
NSInteger lower = (NSInteger)floor(bounded);
|
|
1292
|
+
NSInteger upper = MIN(lower + 1, pageKeys.count - 1);
|
|
1293
|
+
_progressSource = indices[lower].integerValue;
|
|
1294
|
+
_progressDestination = indices[upper].integerValue;
|
|
1295
|
+
_progressFraction = bounded - lower;
|
|
1296
|
+
progress = _progressSource + (_progressDestination - _progressSource) * _progressFraction;
|
|
1297
|
+
}
|
|
700
1298
|
_progress = RNCClamp(progress, 0, _items.count - 1);
|
|
701
1299
|
if (_indicatorFrames.count == _items.count) [self updatePresentation];
|
|
702
1300
|
}
|
|
703
1301
|
|
|
1302
|
+
- (NSInteger)visualFontOwnerIndex:(NSInteger)semanticIndex {
|
|
1303
|
+
if (_textVariant && [_config[@"controlled"] boolValue] && _hasMappedPageProgress) {
|
|
1304
|
+
return _progressFraction < 0.5 ? _progressSource : _progressDestination;
|
|
1305
|
+
}
|
|
1306
|
+
return semanticIndex;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
704
1309
|
- (void)updatePresentation
|
|
705
1310
|
{
|
|
706
1311
|
NSInteger count = _buttons.count;
|
|
@@ -718,11 +1323,22 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
718
1323
|
_segmented ? CGRectGetHeight(fromFrame) : _indicatorHeight
|
|
719
1324
|
);
|
|
720
1325
|
_indicatorView.frame = indicatorFrame;
|
|
721
|
-
_indicatorView.hidden = CGRectGetHeight(indicatorFrame) <= 0;
|
|
1326
|
+
_indicatorView.hidden = _textVariant || CGRectGetHeight(indicatorFrame) <= 0;
|
|
722
1327
|
|
|
723
1328
|
NSInteger selectedIndex = (NSInteger)round(progress);
|
|
1329
|
+
if ([_config[@"controlled"] boolValue]) {
|
|
1330
|
+
NSUInteger selected = [_items indexOfObjectPassingTest:^BOOL(NSDictionary *item, NSUInteger i, BOOL *stop) { return [item[@"key"] isEqual:self->_config[@"selectedKey"]]; }];
|
|
1331
|
+
selectedIndex = selected == NSNotFound ? -1 : (NSInteger)selected;
|
|
1332
|
+
}
|
|
1333
|
+
NSInteger fontOwnerIndex = [self visualFontOwnerIndex:selectedIndex];
|
|
1334
|
+
NSString *key = selectedIndex >= 0 && selectedIndex < count ? _items[selectedIndex][@"key"] : nil;
|
|
1335
|
+
[self updateToolbarForKey:key];
|
|
724
1336
|
[_buttons enumerateObjectsUsingBlock:^(UIButton *button, NSUInteger index, BOOL *stop) {
|
|
725
1337
|
CGFloat emphasis = 1 - MIN(1, fabs(progress - index));
|
|
1338
|
+
if ([self->_config[@"controlled"] boolValue]) {
|
|
1339
|
+
emphasis = index == self->_progressSource ? 1 - self->_progressFraction : 0;
|
|
1340
|
+
if (index == self->_progressDestination) emphasis += self->_progressFraction;
|
|
1341
|
+
}
|
|
726
1342
|
UIColor *color = RNCInterpolateColor(
|
|
727
1343
|
self->_inactiveTextColor,
|
|
728
1344
|
self->_activeTextColor,
|
|
@@ -730,6 +1346,11 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
730
1346
|
self.traitCollection
|
|
731
1347
|
);
|
|
732
1348
|
[button setTitleColor:color forState:UIControlStateNormal];
|
|
1349
|
+
if (self->_textVariant) {
|
|
1350
|
+
button.titleLabel.font = index == fontOwnerIndex
|
|
1351
|
+
? ([UIFont fontWithName:self->_config[@"activeFontFamily"] ?: @"" size:self->_fontSize] ?: [UIFont systemFontOfSize:self->_fontSize weight:UIFontWeightSemibold])
|
|
1352
|
+
: self.tabFont;
|
|
1353
|
+
}
|
|
733
1354
|
button.accessibilityTraits = index == selectedIndex
|
|
734
1355
|
? UIAccessibilityTraitButton | UIAccessibilityTraitSelected
|
|
735
1356
|
: UIAccessibilityTraitButton;
|
|
@@ -1534,6 +2155,24 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1534
2155
|
- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)other { return YES; }
|
|
1535
2156
|
@end
|
|
1536
2157
|
|
|
2158
|
+
@protocol RNCPagerPresentationProgressTarget <NSObject>
|
|
2159
|
+
- (void)sampleProgrammaticTabProgress:(CADisplayLink *)link;
|
|
2160
|
+
- (void)sampleToolbarSettlement:(CADisplayLink *)link;
|
|
2161
|
+
@end
|
|
2162
|
+
|
|
2163
|
+
@interface RNCPagerPresentationProgressProxy : NSObject
|
|
2164
|
+
@property (nonatomic, weak) id<RNCPagerPresentationProgressTarget> target;
|
|
2165
|
+
@property (nonatomic, assign) BOOL toolbarSettlement;
|
|
2166
|
+
@end
|
|
2167
|
+
@implementation RNCPagerPresentationProgressProxy
|
|
2168
|
+
- (void)tick:(CADisplayLink *)link {
|
|
2169
|
+
id<RNCPagerPresentationProgressTarget> target = self.target;
|
|
2170
|
+
if (target && self.toolbarSettlement) [target sampleToolbarSettlement:link];
|
|
2171
|
+
else if (target) [target sampleProgrammaticTabProgress:link];
|
|
2172
|
+
else [link invalidate];
|
|
2173
|
+
}
|
|
2174
|
+
@end
|
|
2175
|
+
|
|
1537
2176
|
@interface RNCCollapsiblePagerViewComponentView () <
|
|
1538
2177
|
RCTRNCCollapsiblePagerViewViewProtocol,
|
|
1539
2178
|
UIPageViewControllerDataSource,
|
|
@@ -1542,8 +2181,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1542
2181
|
UIGestureRecognizerDelegate,
|
|
1543
2182
|
RCTMountingTransactionObserving,
|
|
1544
2183
|
RNCNativeScrollerInsetCoordinator,
|
|
1545
|
-
RNCPagerViewNestedOverlayObserver
|
|
2184
|
+
RNCPagerViewNestedOverlayObserver,
|
|
2185
|
+
RNCPagerPresentationProgressTarget
|
|
1546
2186
|
>
|
|
2187
|
+
- (void)syncNativeToolbarState;
|
|
2188
|
+
- (void)cancelNativeToolbarSettlement;
|
|
2189
|
+
- (void)scheduleNativeToolbarSettlement;
|
|
1547
2190
|
- (void)finishPagerScrollEmittingSelection:(BOOL)emitSelection;
|
|
1548
2191
|
- (void)cancelPendingNativePagerScroll;
|
|
1549
2192
|
- (void)completeTransitionOnNextRunLoopForGeneration:(NSUInteger)generation
|
|
@@ -1662,6 +2305,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1662
2305
|
BOOL _nativeSmoothHeaderScrollEnabled;
|
|
1663
2306
|
UIPanGestureRecognizer *_blockerGesture;
|
|
1664
2307
|
BOOL _transitioning;
|
|
2308
|
+
BOOL _toolbarTransitionPending;
|
|
2309
|
+
CADisplayLink *_toolbarSettleDisplayLink;
|
|
2310
|
+
NSUInteger _toolbarSettleGeneration;
|
|
2311
|
+
NSUInteger _toolbarSettleTransition;
|
|
2312
|
+
NSInteger _toolbarSettlePage;
|
|
2313
|
+
CGFloat _toolbarSettleWidth;
|
|
2314
|
+
NSUInteger _toolbarSettleTicks;
|
|
2315
|
+
BOOL _toolbarSettleAttempted;
|
|
1665
2316
|
BOOL _isPagerDragging;
|
|
1666
2317
|
BOOL _directNativePagerEnabled;
|
|
1667
2318
|
BOOL _pendingDirectNativePagerEnabled;
|
|
@@ -1672,6 +2323,10 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1672
2323
|
NSUInteger _pendingNativePagerScrollGeneration;
|
|
1673
2324
|
NSUInteger _pendingNativePagerScrollTransition;
|
|
1674
2325
|
CGFloat _pendingNativePagerScrollTarget;
|
|
2326
|
+
CADisplayLink *_tabProgressDisplayLink;
|
|
2327
|
+
NSUInteger _tabProgressGeneration;
|
|
2328
|
+
NSUInteger _tabProgressTransition;
|
|
2329
|
+
CGFloat _tabProgressWidth;
|
|
1675
2330
|
NSIndexSet *_programmaticPageMaskIndexes;
|
|
1676
2331
|
NSMapTable<UIScrollView *, RNCCollapsiblePagerTransitionEdgeOwner *> *_programmaticScrollEdges;
|
|
1677
2332
|
NSMutableArray<RNCCollapsiblePagerTransitionEdgeOwner *> *_programmaticScrollEdgeOwners;
|
|
@@ -1851,6 +2506,23 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1851
2506
|
});
|
|
1852
2507
|
}
|
|
1853
2508
|
};
|
|
2509
|
+
_nativeTabBarView.onToolbarNeedsSettlement = ^{
|
|
2510
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
2511
|
+
if (!self) return;
|
|
2512
|
+
NSUInteger generation = self->_generation;
|
|
2513
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
2514
|
+
if (self->_generation == generation) [self syncNativeToolbarState];
|
|
2515
|
+
});
|
|
2516
|
+
};
|
|
2517
|
+
_nativeTabBarView.onToolbarPress = ^(NSString *key, CGRect rect) {
|
|
2518
|
+
__strong __typeof__(weakSelf) self = weakSelf;
|
|
2519
|
+
if (!self || self->_isBeingRecycled) return;
|
|
2520
|
+
const auto emitter = [self eventEmitter];
|
|
2521
|
+
if (emitter) emitter->onNativeToolbarPress({
|
|
2522
|
+
.key = std::string(key.UTF8String ?: ""),
|
|
2523
|
+
.anchorRect = {.x = rect.origin.x, .y = rect.origin.y, .width = rect.size.width, .height = rect.size.height}
|
|
2524
|
+
});
|
|
2525
|
+
};
|
|
1854
2526
|
[_sharedHeaderHostView addSubview:_nativeTabBarView];
|
|
1855
2527
|
|
|
1856
2528
|
_nativeSubHeaderView = [RNCCollapsiblePagerNativeSubHeaderView new];
|
|
@@ -1935,11 +2607,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
1935
2607
|
- (void)didMoveToWindow
|
|
1936
2608
|
{
|
|
1937
2609
|
[super didMoveToWindow];
|
|
2610
|
+
[self cancelNativeToolbarSettlement];
|
|
2611
|
+
if (self.window == nil) [self stopProgrammaticTabProgress];
|
|
1938
2612
|
if (self.window == nil && _pendingNativePagerScrollCompletion != nil) {
|
|
1939
2613
|
[self cancelPendingNativePagerScroll];
|
|
1940
2614
|
_pagerScrollView.scrollEnabled = _scrollEnabled;
|
|
1941
2615
|
[self finishPagerScrollEmittingSelection:NO];
|
|
1942
2616
|
}
|
|
2617
|
+
[self syncNativeToolbarState];
|
|
1943
2618
|
[self registerExternalOuterPager];
|
|
1944
2619
|
BOOL externalHeightChanged = [self resolveExternalTopOverlay];
|
|
1945
2620
|
if (self.window == nil) [self restoreScrollEdgeContentMasks];
|
|
@@ -2054,6 +2729,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2054
2729
|
BOOL wasTransitioning = _externalOuterTransitioning;
|
|
2055
2730
|
_externalOuterPageActive = active;
|
|
2056
2731
|
_externalOuterTransitioning = transitioning;
|
|
2732
|
+
[self syncNativeToolbarState];
|
|
2057
2733
|
if (transitioning && !wasTransitioning) {
|
|
2058
2734
|
[self updateTopScrollEdgeForScrollView:nil];
|
|
2059
2735
|
[self liftSharedHeadersForPagerTransition:@"outer"];
|
|
@@ -2418,8 +3094,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2418
3094
|
[host addSubview:marker];
|
|
2419
3095
|
return marker;
|
|
2420
3096
|
};
|
|
2421
|
-
owner.ownsTransitionHosts =
|
|
2422
|
-
_externalTopOverlayTag > 0 && pageIndex != _currentIndex;
|
|
3097
|
+
owner.ownsTransitionHosts = pageIndex != _currentIndex;
|
|
2423
3098
|
UIView *(^transitionHost)(UIView *) = ^UIView *(UIView *referenceHost) {
|
|
2424
3099
|
CGRect frame = [referenceHost convertRect:referenceHost.bounds toView:owner.page];
|
|
2425
3100
|
CGRect viewport = [scrollView convertRect:scrollView.bounds toView:owner.page];
|
|
@@ -2680,6 +3355,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2680
3355
|
|
|
2681
3356
|
- (void)dealloc
|
|
2682
3357
|
{
|
|
3358
|
+
[self cancelNativeToolbarSettlement];
|
|
3359
|
+
[self stopProgrammaticTabProgress];
|
|
2683
3360
|
[self clearProgrammaticScrollEdges];
|
|
2684
3361
|
[self updateTopScrollEdgeForScrollView:nil];
|
|
2685
3362
|
[self cancelHeaderSettledCheck];
|
|
@@ -2734,6 +3411,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2734
3411
|
}
|
|
2735
3412
|
_hasPendingDirectNativePagerChange = NO;
|
|
2736
3413
|
if (_directNativePagerEnabled == enabled) return;
|
|
3414
|
+
[self cancelNativeToolbarSettlement];
|
|
2737
3415
|
[self cancelPendingNativePagerScroll];
|
|
2738
3416
|
|
|
2739
3417
|
[self restoreScrollEdgeContentMasks];
|
|
@@ -2796,12 +3474,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2796
3474
|
return width * MAX(0, physicalIndex);
|
|
2797
3475
|
}
|
|
2798
3476
|
|
|
2799
|
-
- (CGFloat)
|
|
3477
|
+
- (CGFloat)directPagerProgressForOffset:(CGFloat)offset
|
|
2800
3478
|
{
|
|
2801
3479
|
CGFloat width = CGRectGetWidth(_pagerScrollView.bounds);
|
|
2802
3480
|
if (width <= 0 || _pageControllers.count == 0) return _currentIndex;
|
|
2803
3481
|
CGFloat physicalProgress = RNCClamp(
|
|
2804
|
-
|
|
3482
|
+
offset / width,
|
|
2805
3483
|
0,
|
|
2806
3484
|
_pageControllers.count - 1
|
|
2807
3485
|
);
|
|
@@ -2810,6 +3488,50 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
2810
3488
|
: _pageControllers.count - 1 - physicalProgress;
|
|
2811
3489
|
}
|
|
2812
3490
|
|
|
3491
|
+
- (CGFloat)directPagerProgress {
|
|
3492
|
+
return [self directPagerProgressForOffset:_pagerScrollView.contentOffset.x];
|
|
3493
|
+
}
|
|
3494
|
+
|
|
3495
|
+
- (void)stopProgrammaticTabProgress {
|
|
3496
|
+
if (_tabProgressDisplayLink != nil && _tabProgressWidth > 0 && fabs(CGRectGetWidth(_pagerScrollView.bounds) - _tabProgressWidth) < 0.5) {
|
|
3497
|
+
CALayer *presentation = _pagerScrollView.layer.presentationLayer;
|
|
3498
|
+
if (presentation && std::isfinite(presentation.bounds.origin.x)) {
|
|
3499
|
+
[_nativeTabBarView setProgress:[self directPagerProgressForOffset:presentation.bounds.origin.x]];
|
|
3500
|
+
}
|
|
3501
|
+
}
|
|
3502
|
+
[_tabProgressDisplayLink invalidate];
|
|
3503
|
+
_tabProgressDisplayLink = nil;
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
- (void)updateNativeTabProgress:(CGFloat)progress {
|
|
3507
|
+
if (_tabProgressDisplayLink == nil) [_nativeTabBarView setProgress:progress];
|
|
3508
|
+
}
|
|
3509
|
+
|
|
3510
|
+
- (void)sampleProgrammaticTabProgress:(CADisplayLink *)link {
|
|
3511
|
+
if (link != _tabProgressDisplayLink) { [link invalidate]; return; }
|
|
3512
|
+
if (self.window == nil || !_transitioning || ![_nativeTabBarView usesControlledTextProgress] ||
|
|
3513
|
+
_tabProgressGeneration != _generation || _tabProgressTransition != _transitionId ||
|
|
3514
|
+
!std::isfinite(CGRectGetWidth(_pagerScrollView.bounds)) || _tabProgressWidth <= 0 ||
|
|
3515
|
+
fabs(CGRectGetWidth(_pagerScrollView.bounds) - _tabProgressWidth) >= 0.5) {
|
|
3516
|
+
[self stopProgrammaticTabProgress];
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
CALayer *presentation = _pagerScrollView.layer.presentationLayer;
|
|
3520
|
+
if (presentation == nil || !std::isfinite(presentation.bounds.origin.x)) return;
|
|
3521
|
+
[_nativeTabBarView setProgress:[self directPagerProgressForOffset:presentation.bounds.origin.x]];
|
|
3522
|
+
}
|
|
3523
|
+
|
|
3524
|
+
- (void)startProgrammaticTabProgress {
|
|
3525
|
+
[self stopProgrammaticTabProgress];
|
|
3526
|
+
RNCPagerPresentationProgressProxy *proxy = [RNCPagerPresentationProgressProxy new];
|
|
3527
|
+
proxy.target = self;
|
|
3528
|
+
_tabProgressWidth = CGRectGetWidth(_pagerScrollView.bounds);
|
|
3529
|
+
_tabProgressGeneration = _generation;
|
|
3530
|
+
_tabProgressTransition = _transitionId;
|
|
3531
|
+
_tabProgressDisplayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(tick:)];
|
|
3532
|
+
[_tabProgressDisplayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
3533
|
+
}
|
|
3534
|
+
|
|
2813
3535
|
#pragma mark - Smooth shared header ownership
|
|
2814
3536
|
|
|
2815
3537
|
- (void)restorePinnedStickyHeader
|
|
@@ -3068,8 +3790,81 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3068
3790
|
}
|
|
3069
3791
|
}
|
|
3070
3792
|
|
|
3793
|
+
- (void)cancelNativeToolbarSettlement {
|
|
3794
|
+
[_toolbarSettleDisplayLink invalidate];
|
|
3795
|
+
_toolbarSettleDisplayLink = nil;
|
|
3796
|
+
_toolbarSettleAttempted = NO;
|
|
3797
|
+
_toolbarSettleTicks = 0;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
- (void)scheduleNativeToolbarSettlement {
|
|
3801
|
+
CGFloat width = CGRectGetWidth(_pagerScrollView.bounds);
|
|
3802
|
+
BOOL sameAttempt = _toolbarSettleAttempted && _toolbarSettleGeneration == _generation &&
|
|
3803
|
+
_toolbarSettleTransition == _transitionId && _toolbarSettlePage == _currentIndex &&
|
|
3804
|
+
_toolbarSettleWidth == width;
|
|
3805
|
+
if (sameAttempt) return;
|
|
3806
|
+
[self cancelNativeToolbarSettlement];
|
|
3807
|
+
_toolbarSettleAttempted = YES;
|
|
3808
|
+
_toolbarSettleGeneration = _generation;
|
|
3809
|
+
_toolbarSettleTransition = _transitionId;
|
|
3810
|
+
_toolbarSettlePage = _currentIndex;
|
|
3811
|
+
_toolbarSettleWidth = width;
|
|
3812
|
+
RNCPagerPresentationProgressProxy *proxy = [RNCPagerPresentationProgressProxy new];
|
|
3813
|
+
proxy.target = self;
|
|
3814
|
+
proxy.toolbarSettlement = YES;
|
|
3815
|
+
_toolbarSettleDisplayLink = [CADisplayLink displayLinkWithTarget:proxy selector:@selector(tick:)];
|
|
3816
|
+
[_toolbarSettleDisplayLink addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
|
|
3817
|
+
}
|
|
3818
|
+
|
|
3819
|
+
- (void)sampleToolbarSettlement:(CADisplayLink *)link {
|
|
3820
|
+
if (link != _toolbarSettleDisplayLink) { [link invalidate]; return; }
|
|
3821
|
+
if (_toolbarSettleGeneration != _generation || _toolbarSettleTransition != _transitionId ||
|
|
3822
|
+
_toolbarSettlePage != _currentIndex ||
|
|
3823
|
+
_toolbarSettleWidth != CGRectGetWidth(_pagerScrollView.bounds) ||
|
|
3824
|
+
!self.window || _nativeTabBarView.window != self.window || ![_nativeTabBarView ownsSettledToolbar]) {
|
|
3825
|
+
[self cancelNativeToolbarSettlement];
|
|
3826
|
+
return;
|
|
3827
|
+
}
|
|
3828
|
+
++_toolbarSettleTicks;
|
|
3829
|
+
[self syncNativeToolbarState];
|
|
3830
|
+
if (link == _toolbarSettleDisplayLink && _toolbarSettleTicks >= 2) {
|
|
3831
|
+
[link invalidate];
|
|
3832
|
+
_toolbarSettleDisplayLink = nil;
|
|
3833
|
+
}
|
|
3834
|
+
}
|
|
3835
|
+
|
|
3836
|
+
- (void)syncNativeToolbarState {
|
|
3837
|
+
BOOL logicalBusy = !self.window || _isBeingRecycled || !_hasAppliedInitialPage || _toolbarTransitionPending ||
|
|
3838
|
+
_transitioning || _isPagerDragging || _externalOuterTransitioning ||
|
|
3839
|
+
(_externalOuterPager != nil && !_externalOuterPageActive) || _needsSlotRebuild ||
|
|
3840
|
+
_pendingGoToIndex >= 0 || _transitionPageOffsets != nil || _pendingNativePagerScrollCompletion != nil ||
|
|
3841
|
+
_tabProgressDisplayLink != nil || _currentIndex < 0 || _currentIndex >= _pageControllers.count ||
|
|
3842
|
+
CGRectGetWidth(self.bounds) <= 0 || CGRectGetHeight(self.bounds) <= 0 ||
|
|
3843
|
+
_pagerScrollView.dragging || _pagerScrollView.decelerating;
|
|
3844
|
+
BOOL busy = logicalBusy || _pagerScrollView.tracking;
|
|
3845
|
+
if (!busy && _directNativePagerEnabled) {
|
|
3846
|
+
CALayer *presentation = _pagerScrollView.layer.presentationLayer;
|
|
3847
|
+
CGFloat offset = presentation ? presentation.bounds.origin.x : _pagerScrollView.contentOffset.x;
|
|
3848
|
+
busy = CGRectGetWidth(_pagerScrollView.bounds) <= 0 || !std::isfinite(offset) ||
|
|
3849
|
+
fabs(offset - [self directPagerOffsetForIndex:_currentIndex]) >= 0.5;
|
|
3850
|
+
} else if (!busy) {
|
|
3851
|
+
busy = _pageViewController.viewControllers.firstObject != _pageControllers[_currentIndex];
|
|
3852
|
+
}
|
|
3853
|
+
[_nativeTabBarView updateToolbarPagerIndex:_currentIndex busy:busy];
|
|
3854
|
+
if (busy && !logicalBusy && [_nativeTabBarView ownsSettledToolbar] &&
|
|
3855
|
+
_nativeTabBarView.window == self.window && !_nativeTabBarView.hidden &&
|
|
3856
|
+
CGRectGetWidth(_pagerScrollView.bounds) > 0) {
|
|
3857
|
+
[self scheduleNativeToolbarSettlement];
|
|
3858
|
+
} else {
|
|
3859
|
+
[self cancelNativeToolbarSettlement];
|
|
3860
|
+
}
|
|
3861
|
+
}
|
|
3862
|
+
|
|
3071
3863
|
- (void)beginPageStateTransition
|
|
3072
3864
|
{
|
|
3865
|
+
[self cancelNativeToolbarSettlement];
|
|
3866
|
+
_toolbarTransitionPending = YES;
|
|
3867
|
+
[_nativeTabBarView updateToolbarPagerIndex:-1 busy:YES];
|
|
3073
3868
|
[self cancelPagerTop];
|
|
3074
3869
|
if (!_nativeSmoothHeaderScrollEnabled || _headerHeight <= 0 || _transitionPageOffsets != nil) return;
|
|
3075
3870
|
_transitionSourceDisplayBody = MAX(_observedScrollView.contentOffset.y + _observedScrollView.contentInset.top - _headerHeight, 0);
|
|
@@ -3444,7 +4239,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3444
4239
|
animated:NO
|
|
3445
4240
|
completion:nil];
|
|
3446
4241
|
}
|
|
3447
|
-
[
|
|
4242
|
+
[self updateNativeTabProgress:_currentIndex];
|
|
3448
4243
|
}
|
|
3449
4244
|
if (_stickyHeaderView != nil) [_sharedHeaderHostView bringSubviewToFront:_stickyHeaderView];
|
|
3450
4245
|
if (!_nativeTabBarView.hidden) [_sharedHeaderHostView bringSubviewToFront:_nativeTabBarView];
|
|
@@ -3462,6 +4257,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3462
4257
|
- (void)layoutSubviews
|
|
3463
4258
|
{
|
|
3464
4259
|
[super layoutSubviews];
|
|
4260
|
+
if (_tabProgressDisplayLink != nil && fabs(CGRectGetWidth(self.bounds) - _tabProgressWidth) >= 0.5) {
|
|
4261
|
+
[self stopProgrammaticTabProgress];
|
|
4262
|
+
}
|
|
3465
4263
|
void (^resizedPagerCompletion)(BOOL) = nil;
|
|
3466
4264
|
NSInteger resizedPagerDestination = -1;
|
|
3467
4265
|
NSUInteger resizedPagerGeneration = 0;
|
|
@@ -3504,7 +4302,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3504
4302
|
animated:NO
|
|
3505
4303
|
completion:nil];
|
|
3506
4304
|
}
|
|
3507
|
-
[
|
|
4305
|
+
[self updateNativeTabProgress:_currentIndex];
|
|
3508
4306
|
}
|
|
3509
4307
|
// UIKit frame assignment is undefined while a view has a non-identity transform.
|
|
3510
4308
|
// Restore transforms before settling page and shared-header frames.
|
|
@@ -3556,10 +4354,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3556
4354
|
) animated:NO];
|
|
3557
4355
|
resizedPagerCompletion(YES);
|
|
3558
4356
|
}
|
|
4357
|
+
[self syncNativeToolbarState];
|
|
3559
4358
|
}
|
|
3560
4359
|
|
|
3561
4360
|
- (void)prepareForRecycle
|
|
3562
4361
|
{
|
|
4362
|
+
[self cancelNativeToolbarSettlement];
|
|
3563
4363
|
[self cancelPendingNativePagerScroll];
|
|
3564
4364
|
[_externalOuterPager removeNestedOverlayObserver:self];
|
|
3565
4365
|
_externalOuterPager = nil;
|
|
@@ -3569,6 +4369,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3569
4369
|
[self cancelHeaderSettledCheck];
|
|
3570
4370
|
[self publishHeaderSettledAtTop:NO identityChanged:NO];
|
|
3571
4371
|
_isBeingRecycled = YES;
|
|
4372
|
+
_toolbarTransitionPending = NO;
|
|
4373
|
+
[_nativeTabBarView updateToolbarPagerIndex:-1 busy:YES];
|
|
3572
4374
|
_pendingTopOverlayAppearanceCommit = NO;
|
|
3573
4375
|
[self syncScrollEdgeAppearance];
|
|
3574
4376
|
_generation++;
|
|
@@ -3706,7 +4508,8 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3706
4508
|
_needsPropsReapply = YES;
|
|
3707
4509
|
_layoutDirection = @"ltr";
|
|
3708
4510
|
[_nativeTabBarView updateItemsJSON:@"[]"];
|
|
3709
|
-
[_nativeTabBarView
|
|
4511
|
+
[_nativeTabBarView updateConfigJSON:@"{}"];
|
|
4512
|
+
[self updateNativeTabProgress:0];
|
|
3710
4513
|
[_nativeSubHeaderView updateConfigJSON:@"{}"];
|
|
3711
4514
|
_isBeingRecycled = NO;
|
|
3712
4515
|
}
|
|
@@ -3835,6 +4638,9 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3835
4638
|
_retainedPages = RCTNSStringFromString(newViewProps.retainedPages);
|
|
3836
4639
|
[self setNeedsLayout];
|
|
3837
4640
|
}
|
|
4641
|
+
if (_needsPropsReapply || oldViewProps.nativeTabBarConfig != newViewProps.nativeTabBarConfig) {
|
|
4642
|
+
[_nativeTabBarView updateConfigJSON:RCTNSStringFromString(newViewProps.nativeTabBarConfig)];
|
|
4643
|
+
}
|
|
3838
4644
|
if (_needsPropsReapply || oldViewProps.nativeTabBarItems != newViewProps.nativeTabBarItems) {
|
|
3839
4645
|
NSString *itemsJSON = RCTNSStringFromString(newViewProps.nativeTabBarItems);
|
|
3840
4646
|
[_nativeTabBarView updateItemsJSON:itemsJSON];
|
|
@@ -3899,6 +4705,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
3899
4705
|
[self logHomeVisualGeometry:@"props-inset" scrollView:_observedScrollView];
|
|
3900
4706
|
}
|
|
3901
4707
|
_applyingTopOverlayInsetChange = NO;
|
|
4708
|
+
[self syncNativeToolbarState];
|
|
3902
4709
|
if (!_nativeTabBarView.hidden) {
|
|
3903
4710
|
[_nativeTabBarView.superview bringSubviewToFront:_nativeTabBarView];
|
|
3904
4711
|
}
|
|
@@ -5324,6 +6131,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5324
6131
|
|
|
5325
6132
|
- (void)cancelPendingNativePagerScroll
|
|
5326
6133
|
{
|
|
6134
|
+
[self stopProgrammaticTabProgress];
|
|
5327
6135
|
if (_pendingNativePagerScrollCompletion == nil) return;
|
|
5328
6136
|
_pendingNativePagerScrollCompletion = nil;
|
|
5329
6137
|
_pendingNativePagerScrollGeneration = 0;
|
|
@@ -5342,6 +6150,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5342
6150
|
{
|
|
5343
6151
|
[self cancelTopInsetReboundCheck];
|
|
5344
6152
|
if (_isBeingRecycled || index < 0 || index >= _pageControllers.count) return;
|
|
6153
|
+
[self cancelNativeToolbarSettlement];
|
|
5345
6154
|
_hasReceivedPageCommand = YES;
|
|
5346
6155
|
_hasAppliedInitialPage = YES;
|
|
5347
6156
|
if (_transitioning || _isPagerDragging) {
|
|
@@ -5363,6 +6172,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5363
6172
|
[self attachScrollObserverForCurrentPage];
|
|
5364
6173
|
[self emitPageSelected:index];
|
|
5365
6174
|
[self emitDiagnostics:@"page-selected"];
|
|
6175
|
+
[self syncNativeToolbarState];
|
|
5366
6176
|
return;
|
|
5367
6177
|
}
|
|
5368
6178
|
|
|
@@ -5391,13 +6201,14 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5391
6201
|
CGFloat targetOffset = [self directPagerOffsetForIndex:index];
|
|
5392
6202
|
void (^animations)(void) = ^{
|
|
5393
6203
|
[self->_pagerScrollView setContentOffset:CGPointMake(targetOffset, 0)];
|
|
5394
|
-
[self->_nativeTabBarView setProgress:index];
|
|
6204
|
+
if (self->_tabProgressDisplayLink == nil) [self->_nativeTabBarView setProgress:index];
|
|
5395
6205
|
[self updatePageStateTransitionProgress:index];
|
|
5396
6206
|
};
|
|
5397
6207
|
void (^completion)(BOOL) = ^(BOOL finished) {
|
|
5398
6208
|
__strong __typeof__(weakSelf) self = weakSelf;
|
|
5399
6209
|
if (self == nil || self->_generation != capturedGeneration ||
|
|
5400
6210
|
self->_transitionId != capturedTransition || self->_isBeingRecycled) return;
|
|
6211
|
+
[self stopProgrammaticTabProgress];
|
|
5401
6212
|
self->_pagerScrollView.scrollEnabled = self->_scrollEnabled;
|
|
5402
6213
|
if (finished) {
|
|
5403
6214
|
[self detachScrollObserver];
|
|
@@ -5433,7 +6244,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5433
6244
|
_pagerScrollView.scrollEnabled = NO;
|
|
5434
6245
|
if (useNativeAnimatedScroll) {
|
|
5435
6246
|
if (fabs(_pagerScrollView.contentOffset.x - targetOffset) <= 0.5) {
|
|
5436
|
-
[
|
|
6247
|
+
[self updateNativeTabProgress:index];
|
|
5437
6248
|
[self updatePageStateTransitionProgress:index];
|
|
5438
6249
|
completion(YES);
|
|
5439
6250
|
} else {
|
|
@@ -5444,6 +6255,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5444
6255
|
[_pagerScrollView setContentOffset:CGPointMake(targetOffset, 0) animated:YES];
|
|
5445
6256
|
}
|
|
5446
6257
|
} else {
|
|
6258
|
+
if ([_nativeTabBarView usesControlledTextProgress]) [self startProgrammaticTabProgress];
|
|
5447
6259
|
[UIView animateWithDuration:0.28
|
|
5448
6260
|
delay:0
|
|
5449
6261
|
options:UIViewAnimationOptionCurveEaseInOut |
|
|
@@ -5508,10 +6320,12 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5508
6320
|
[self attachScrollObserverForCurrentPage];
|
|
5509
6321
|
}
|
|
5510
6322
|
[self drainPendingGoTo];
|
|
6323
|
+
if (!self->_transitioning && !self->_isPagerDragging && self->_pendingGoToIndex < 0) self->_toolbarTransitionPending = NO;
|
|
5511
6324
|
if (!hadProgrammaticScrollEdges && !self->_transitioning && !self->_needsSlotRebuild) {
|
|
5512
6325
|
[self attachScrollObserverForCurrentPage];
|
|
5513
6326
|
}
|
|
5514
6327
|
[self updateScrollEdgeContentMasks];
|
|
6328
|
+
[self syncNativeToolbarState];
|
|
5515
6329
|
});
|
|
5516
6330
|
}
|
|
5517
6331
|
|
|
@@ -5631,7 +6445,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5631
6445
|
[self directPagerOffsetForIndex:index],
|
|
5632
6446
|
0
|
|
5633
6447
|
) animated:NO];
|
|
5634
|
-
[
|
|
6448
|
+
[self updateNativeTabProgress:index];
|
|
5635
6449
|
[self attachScrollObserverForCurrentPageAfterTransition:YES];
|
|
5636
6450
|
if (emitSelection &&
|
|
5637
6451
|
(_pendingGoToIndex < 0 || _pendingGoToIndex == index)) {
|
|
@@ -5682,7 +6496,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5682
6496
|
[self detachScrollObserver];
|
|
5683
6497
|
_currentIndex = index;
|
|
5684
6498
|
_destinationIndex = index;
|
|
5685
|
-
[
|
|
6499
|
+
[self updateNativeTabProgress:index];
|
|
5686
6500
|
[self attachScrollObserverForCurrentPageAfterTransition:YES];
|
|
5687
6501
|
// A newer tab tap remains authoritative while its queued command runs.
|
|
5688
6502
|
if (emitSelection &&
|
|
@@ -5743,7 +6557,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5743
6557
|
position = MAX(0, MIN(position, (NSInteger)_pageControllers.count - 1));
|
|
5744
6558
|
[self updatePageStateTransitionProgress:progress];
|
|
5745
6559
|
[self updateScrollEdgeContentMasks];
|
|
5746
|
-
[
|
|
6560
|
+
if (_tabProgressDisplayLink == nil) [self updateNativeTabProgress:progress];
|
|
5747
6561
|
const auto emitter = [self eventEmitter];
|
|
5748
6562
|
if (emitter) {
|
|
5749
6563
|
emitter->onPageScroll({.position = (double)position, .offset = (double)offset});
|
|
@@ -5762,7 +6576,7 @@ static void RNCLogNativeTabScrollBoundary(NSString *owner,
|
|
|
5762
6576
|
}
|
|
5763
6577
|
[self updatePageStateTransitionProgress:tabProgress];
|
|
5764
6578
|
[self updateScrollEdgeContentMasks];
|
|
5765
|
-
[
|
|
6579
|
+
[self updateNativeTabProgress:tabProgress];
|
|
5766
6580
|
const auto emitter = [self eventEmitter];
|
|
5767
6581
|
if (emitter) {
|
|
5768
6582
|
emitter->onPageScroll({.position = (double)position, .offset = (double)offset});
|