123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463 |
-
- #import "RCCNotification.h"
- #import <React/RCTRootView.h>
- #import "RCTHelpers.h"
-
- @interface NotificationView : UIView
- @property (nonatomic, strong) RCTRootView *reactView;
- @property (nonatomic, strong) UIView *slideAnimGapView;
- @property (nonatomic, strong) NSDictionary *params;
- @property (nonatomic, strong) NSTimer *autoDismissTimer;
- @property (nonatomic) BOOL yellowBoxRemoved;
- @end
-
- @implementation NotificationView
-
- -(id)initWithParams:(NSDictionary*)params
- {
- self = [super initWithFrame:CGRectZero];
- if (self)
- {
- self.params = params;
- self.yellowBoxRemoved = NO;
-
- self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
-
- self.reactView = [[RCTRootView alloc] initWithBridge:[[RCCManager sharedInstance] getBridge] moduleName:params[@"component"] initialProperties:params[@"passProps"]];
- self.reactView.backgroundColor = [UIColor clearColor];
- self.reactView.sizeFlexibility = RCTRootViewSizeFlexibilityWidthAndHeight;
- self.reactView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
- [self addSubview:self.reactView];
-
- [self.reactView.contentView.layer addObserver:self forKeyPath:@"frame" options:0 context:nil];
- [self.reactView.contentView.layer addObserver:self forKeyPath:@"bounds" options:0 context:NULL];
-
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onRNReload) name:RCTJavaScriptWillStartLoadingNotification object:nil];
-
- if ([params[@"dismissWithSwipe"] boolValue])
- {
- UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(performDismiss)];
- swipeGesture.direction = [self swipeDirection];
- [self addGestureRecognizer:swipeGesture];
- }
-
- if (params[@"shadowRadius"] != nil && [params[@"shadowRadius"] floatValue] > 0)
- {
- self.layer.shadowColor = [UIColor blackColor].CGColor;
- self.layer.shadowOffset = CGSizeMake(0, 0);
- self.layer.shadowRadius = [params[@"shadowRadius"] floatValue];
- self.layer.shadowOpacity = 0.6;
- }
-
- self.hidden = YES;
- }
- return self;
- }
-
- -(void)layoutSubviews
- {
- [super layoutSubviews];
-
- if(!self.yellowBoxRemoved)
- {
- self.yellowBoxRemoved = [RCTHelpers removeYellowBox:self.reactView];
- }
- }
-
- -(UISwipeGestureRecognizerDirection)swipeDirection
- {
- UISwipeGestureRecognizerDirection direction = UISwipeGestureRecognizerDirectionUp;
-
- NSString *animationType = [self.params valueForKeyPath:@"animation.type"];
- if ([animationType isEqualToString:@"swing"] || [animationType isEqualToString:@"slide-down"])
- direction = UISwipeGestureRecognizerDirectionUp;
- else if ([animationType isEqualToString:@"slide-left"])
- direction = UISwipeGestureRecognizerDirectionRight;
- else if ([animationType isEqualToString:@"slide-right"])
- direction = UISwipeGestureRecognizerDirectionLeft;
-
- return direction;
- }
-
- -(void)killAutoDismissTimer
- {
- if (self.autoDismissTimer != nil)
- {
- [self.autoDismissTimer invalidate];
- self.autoDismissTimer = nil;
- }
- }
-
- -(void)removeObservers
- {
- [[NSNotificationCenter defaultCenter] removeObserver:self];
- [self.reactView.contentView.layer removeObserver:self forKeyPath:@"frame" context:nil];
- [self.reactView.contentView.layer removeObserver:self forKeyPath:@"bounds" context:NULL];
- }
-
- -(void)cleanup
- {
- [self removeObservers];
- [self killAutoDismissTimer];
- }
-
- -(void)dealloc
- {
- [self cleanup];
- }
-
- -(void)onRNReload
- {
- [self cleanup];
- [self removeFromSuperview];
- self.reactView = nil;
- }
-
- -(UIColor*)reactViewAvgColor
- {
- if (self.reactView.contentView.frame.size.width == 0 || self.reactView.contentView.frame.size.height == 0)
- {
- return [UIColor clearColor];
- }
-
- UIGraphicsBeginImageContextWithOptions(CGSizeMake(1, 1), YES, 0);
- [self.reactView.contentView drawViewHierarchyInRect:CGRectMake(0, 0, 1, 1) afterScreenUpdates:YES];
- UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
- const UInt8* data = CFDataGetBytePtr(pixelData);
- CFRelease(pixelData);
-
- //after scale defaults to bgr
- CGFloat red = data[2] / 255.0f,
- green = data[1] / 255.0f,
- blue = data[0] / 255.0f,
- alpha = data[3] / 255.0f;
-
- UIColor *color = [UIColor colorWithRed:red green:green blue:blue alpha:alpha];
- return color;
- }
-
- -(BOOL)shouldAddSlidingAnimGapView
- {
- if (![[self.params valueForKeyPath:@"animation.animated"] boolValue])
- {
- return NO;
- }
-
- NSString *animationType = [self.params valueForKeyPath:@"animation.type"];
- if (![animationType isEqualToString:@"slide-down"])
- {
- return NO;
- }
-
- if (![self.params valueForKeyPath:@"animation.damping"])
- {
- return NO;
- }
-
- CGFloat damping = [[self.params valueForKeyPath:@"animation.damping"] floatValue];
- if (damping >= 1)
- {
- return NO;
- }
-
- return YES;
- }
-
- -(BOOL)isBottomPosition
- {
- return ((self.params[@"position"] != nil) && ([self.params[@"position"] isEqualToString:@"bottom"]));
- }
-
- - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
- {
- CGSize frameSize = CGSizeZero;
- if ([object isKindOfClass:[CALayer class]])
- frameSize = ((CALayer*)object).frame.size;
- if ([object isKindOfClass:[UIView class]])
- frameSize = ((UIView*)object).frame.size;
-
- if (!CGSizeEqualToSize(frameSize, self.reactView.frame.size))
- {
- BOOL isBottomPosition = [self isBottomPosition];
- CGFloat yPos = isBottomPosition ? self.superview.bounds.size.height - frameSize.height : 0;
- self.frame = CGRectMake((self.superview.frame.size.width - frameSize.width) / 2.0, yPos, frameSize.width, frameSize.height);
- self.reactView.frame = CGRectMake(0, 0, frameSize.width, frameSize.height);
- self.reactView.contentView.frame = CGRectMake(0, 0, frameSize.width, frameSize.height);
-
- //if necessary, add a view with the same color to cover the gap if there's a slide animation with spring
- if ([self shouldAddSlidingAnimGapView])
- {
- if (self.slideAnimGapView == nil)
- {
- self.slideAnimGapView = [[UIView alloc] initWithFrame:CGRectZero];
- [self.reactView addSubview:self.slideAnimGapView];
- }
-
- CGFloat yPos = isBottomPosition ? frameSize.height : -20;
- self.slideAnimGapView.frame = CGRectMake(0, yPos, self.superview.frame.size.width, 20);
-
- dispatch_async(dispatch_get_main_queue(), ^
- {
- self.slideAnimGapView.backgroundColor = [self reactViewAvgColor];
- });
- }
-
- if (self.params[@"shadowRadius"] != nil && [self.params[@"shadowRadius"] floatValue] > 0)
- {
- self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
- }
- }
- }
-
- -(void)performDismiss
- {
- [RCCNotification dismissWithParams:self.params resolver:nil rejecter:nil];
- }
-
- -(void)startAutoDismissTimerIfNecessary
- {
- if (self.params[@"autoDismissTimerSec"])
- {
- [self killAutoDismissTimer];
-
- CGFloat interval = [self.params[@"autoDismissTimerSec"] floatValue];
- interval = MAX(interval, 1);
- self.autoDismissTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(performDismiss) userInfo:nil repeats:NO];
- }
- }
-
- -(void)applyAnimations
- {
- NSString *animationType = [self.params valueForKeyPath:@"animation.type"];
- if ([animationType isEqualToString:@"swing"])
- {
- CATransform3D transform = CATransform3DIdentity;
- transform.m34 = -0.002f;
- transform.m24 = -0.007f;
- if (self.layer.anchorPoint.x == 0.5 && self.layer.anchorPoint.y == 0.5)
- {
- CGPoint anchorPoint = CGPointMake(0.5, 0);
- CGFloat yOffset = -self.layer.frame.size.height / 2.0;
- if([self isBottomPosition])
- {
- anchorPoint = CGPointMake(0.5, 1);
- yOffset = self.layer.frame.size.height / 2.0;
- }
-
- self.layer.anchorPoint = anchorPoint;
- self.layer.frame = CGRectOffset(self.layer.frame, 0, yOffset);
- }
- self.layer.zPosition = 1000;
- self.layer.transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0);
- }
- else
- {
- int slideAnimationSign = [self isBottomPosition] ? 1 : -1;
- CGAffineTransform transform = CGAffineTransformIdentity;
- if ([animationType isEqualToString:@"slide-down"])
- transform = CGAffineTransformMakeTranslation(0, slideAnimationSign * self.frame.size.height);
- else if ([animationType isEqualToString:@"slide-left"])
- transform = CGAffineTransformMakeTranslation(self.frame.size.width, 0);
- else if ([animationType isEqualToString:@"slide-right"])
- transform = CGAffineTransformMakeTranslation(-self.frame.size.width, 0);
-
- self.transform = transform;
- }
-
- if ( [[self.params valueForKeyPath:@"animation.fade"] boolValue])
- {
- self.alpha = 0;
- }
- }
-
- #pragma mark - show/dismiss
-
- -(void)showAnimationEnded:(void (^)(void))completion
- {
- self.alpha = 1;
-
- if (completion)
- {
- completion();
- }
-
- [self startAutoDismissTimerIfNecessary];
- }
-
- -(void)dismissAnimationEnded:(void (^)(void))completion
- {
- if (completion)
- {
- completion();
- }
-
- [self removeFromSuperview];
- }
-
- -(void)performShowWithCompletion:(void (^)(void))completion
- {
- self.hidden = NO;
-
- if ([[self.params valueForKeyPath:@"animation.animated"] boolValue])
- {
- CGFloat duration = [[self.params valueForKeyPath:@"animation.duration"] floatValue];
- CGFloat delay = [[self.params valueForKeyPath:@"animation.delay"] floatValue];
-
- CGFloat damping = 1;
- if ([self.params valueForKeyPath:@"animation.damping"] != nil)
- {
- damping = [[self.params valueForKeyPath:@"animation.damping"] floatValue];
- damping = MAX(damping, 0);
- damping = MIN(damping, 1);
- }
-
- [self applyAnimations];
-
- [UIView animateWithDuration:duration delay:delay usingSpringWithDamping:damping initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseOut animations:^()
- {
- self.alpha = 1;
- self.transform = CGAffineTransformIdentity;
- self.layer.transform = CATransform3DIdentity;
- }
- completion:^(BOOL finished)
- {
- [self showAnimationEnded:completion];
- }];
- }
- else
- {
- [self showAnimationEnded:completion];
- }
- }
-
- -(void)showWithCompletion:(void (^)(void))completion
- {
- if (self.frame.size.height == 0 || self.frame.size.width == 0)
- {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.05 * NSEC_PER_SEC)),
- dispatch_get_main_queue(),
- ^{
- [self showWithCompletion:completion];
- });
- }
- else
- {
- [self performShowWithCompletion:completion];
- }
- }
-
- -(void)dismissWithCompletion:(void (^)(void))completion
- {
- [self killAutoDismissTimer];
- [self.reactView cancelTouches];
-
- if ([[self.params valueForKeyPath:@"animation.animated"] boolValue])
- {
- CGFloat duration = [[self.params valueForKeyPath:@"animation.duration"] floatValue] * 0.75;
- [UIView animateWithDuration:duration delay:0 usingSpringWithDamping:1 initialSpringVelocity:0 options:UIViewAnimationOptionCurveEaseIn
- animations:^()
- {
- [self applyAnimations];
- }
- completion:^(BOOL finished)
- {
- [self dismissAnimationEnded:completion];
- }];
- }
- else
- {
- [self dismissAnimationEnded:completion];
- }
- }
-
- @end
-
- @interface PendingNotification : NSObject
- @property (nonatomic, copy) void (^resolve)(id result);
- @property (nonatomic, copy) void (^reject)(NSString *code, NSString *message, NSError *error);
- @property (nonatomic, strong) NSDictionary *params;
- @end
-
- @implementation PendingNotification
- @end
-
- @implementation RCCNotification
-
- static NSTimeInterval gLastShownTime = NSTimeIntervalSince1970;
- static NSMutableArray *gPendingNotifications = nil;
- static NSMutableArray *gShownNotificationViews = nil;
-
- +(void)showWithParams:(NSDictionary*)params resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
- {
- if(gPendingNotifications == nil)
- gPendingNotifications = [NSMutableArray array];
-
- if(gShownNotificationViews == nil)
- gShownNotificationViews = [NSMutableArray array];
-
- if ([gShownNotificationViews count] > 0)
- {
- for (NotificationView *notificationView in gShownNotificationViews)
- {
- [notificationView killAutoDismissTimer];
- }
-
- //limit the amount of consecutive notifications per second. If they arrive too fast, the last one will be remembered as pending
- if(CFAbsoluteTimeGetCurrent() - gLastShownTime < 1)
- {
- PendingNotification *pendingNotification = [PendingNotification new];
- pendingNotification.params = params;
- pendingNotification.resolve = resolve;
- pendingNotification.reject = reject;
- [gPendingNotifications addObject:pendingNotification];
- return;
- }
- }
-
- gLastShownTime = CFAbsoluteTimeGetCurrent();
-
- UIWindow *window = [[RCCManager sharedInstance] getAppWindow];
- NotificationView *notificationView = [[NotificationView alloc] initWithParams:params];
- [window addSubview:notificationView];
- [gShownNotificationViews addObject:notificationView];
- [notificationView showWithCompletion:^()
- {
- if (resolve)
- {
- resolve(nil);
- }
- }];
- }
-
- +(void)dismissWithParams:(NSDictionary*)params resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject
- {
- int count = [gShownNotificationViews count];
- for (int i = count - 1 ; i >= 0; i--)
- {
- NotificationView *notificationView = [gShownNotificationViews objectAtIndex:i];
- [gShownNotificationViews removeObject:notificationView];
- [notificationView dismissWithCompletion:^()
- {
- if (i == (count - 1))
- {
- if(resolve)
- {
- resolve(nil);
- }
-
- if ([gPendingNotifications count] > 0)
- {
- PendingNotification *pendingNotification = [gPendingNotifications lastObject];
- [self showWithParams:pendingNotification.params resolver:pendingNotification.resolve rejecter:pendingNotification.reject];
- [gPendingNotifications removeLastObject];
- }
- }
- }];
- }
- }
-
- @end
|