1// Copyright 2018-present 650 Industries. All rights reserved.
2
3#import <EXSplashScreen/EXSplashScreenModule.h>
4#import <EXSplashScreen/EXSplashScreenService.h>
5#import <React/RCTRootView.h>
6#import <UMCore/UMAppLifecycleService.h>
7#import <UMCore/UMUtilities.h>
8
9@protocol EXSplashScreenUtilService
10
11- (UIViewController *)currentViewController;
12
13@end
14
15@interface EXSplashScreenModule ()
16
17@property (nonatomic, weak) UMModuleRegistry *moduleRegistry;
18@property (nonatomic, weak) id<UMUtilitiesInterface> utilities;
19
20@end
21
22@implementation EXSplashScreenModule
23
24UM_EXPORT_MODULE(ExpoSplashScreen);
25
26- (void)setModuleRegistry:(UMModuleRegistry *)moduleRegistry
27{
28  _moduleRegistry = moduleRegistry;
29  _utilities = [moduleRegistry getModuleImplementingProtocol:@protocol(UMUtilitiesInterface)];
30  [[moduleRegistry getModuleImplementingProtocol:@protocol(UMAppLifecycleService)] registerAppLifecycleListener:self];
31}
32
33UM_EXPORT_METHOD_AS(hideAsync,
34                    hideWithResolve:(UMPromiseResolveBlock)resolve
35                    reject:(UMPromiseRejectBlock)reject)
36{
37  UM_WEAKIFY(self);
38  dispatch_async(dispatch_get_main_queue(), ^{
39    UM_ENSURE_STRONGIFY(self);
40    UIViewController *currentViewController = [self reactViewController];
41    [[self splashScreenService] hideSplashScreenFor:currentViewController
42                                    successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); }
43                                    failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_HIDE", message, nil); }];
44  });
45}
46
47UM_EXPORT_METHOD_AS(preventAutoHideAsync,
48                    preventAutoHideWithResolve:(UMPromiseResolveBlock)resolve
49                    reject:(UMPromiseRejectBlock)reject)
50{
51  UM_WEAKIFY(self);
52  dispatch_async(dispatch_get_main_queue(), ^{
53    UM_ENSURE_STRONGIFY(self);
54    UIViewController *currentViewController = [self reactViewController];
55    [[self splashScreenService] preventSplashScreenAutoHideFor:currentViewController
56                                               successCallback:^(BOOL hasEffect){ resolve(@(hasEffect)); }
57                                               failureCallback:^(NSString *message){ reject(@"ERR_SPLASH_SCREEN_CANNOT_PREVENT_AUTOHIDE", message, nil); }];
58  });
59}
60
61# pragma mark - UMAppLifecycleListener
62
63- (void)onAppBackgrounded {}
64
65- (void)onAppForegrounded {}
66
67- (void)onAppContentDidAppear
68{
69  UM_WEAKIFY(self);
70  dispatch_async(dispatch_get_main_queue(), ^{
71    UM_ENSURE_STRONGIFY(self);
72    UIViewController* currentViewController = [self reactViewController];
73    [[self splashScreenService] onAppContentDidAppear:currentViewController];
74  });
75}
76
77- (void)onAppContentWillReload
78{
79  UM_WEAKIFY(self);
80  dispatch_async(dispatch_get_main_queue(), ^{
81    UM_ENSURE_STRONGIFY(self);
82    UIViewController* currentViewController = [self reactViewController];
83    [[self splashScreenService] onAppContentWillReload:currentViewController];
84  });
85}
86
87# pragma mark - internals
88
89/**
90 * Tries to obtain singleton module that is registered as "SplashScreen".
91 * Silent agreement is that registered module conforms to "EXSplashScreenService" protocol.
92 */
93- (id<EXSplashScreenService>)splashScreenService
94{
95  return [self.moduleRegistry getSingletonModuleForName:@"SplashScreen"];
96}
97
98/**
99 * Tries to obtain a reference to the UIViewController for the main RCTRootView
100 * by iterating through all of the application's windows and their viewControllers
101 * until it finds one with a RCTRootView.
102 */
103- (UIViewController *)reactViewController
104{
105  dispatch_assert_queue(dispatch_get_main_queue());
106
107  // first check to see if the host application has a module that provides the reference we want
108  // (this is the case in Expo Go and in the ExpoKit pod used in `expo build` apps)
109  id<EXSplashScreenUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"];
110  if (utilService != nil) {
111    return [utilService currentViewController];
112  }
113
114  UIViewController *controller = [self viewControllerContainingRCTRootView];
115  if (!controller) {
116    // no RCTRootView was found, so just fall back to the key window's root view controller
117    return self.utilities.currentViewController;
118  }
119
120  UIViewController *presentedController = controller.presentedViewController;
121  while (presentedController && ![presentedController isBeingDismissed]) {
122    controller = presentedController;
123    presentedController = controller.presentedViewController;
124  }
125  return controller;
126}
127
128- (nullable UIViewController *)viewControllerContainingRCTRootView
129{
130  NSArray<UIWindow *> *allWindows;
131  if (@available(iOS 13, *)) {
132    NSSet<UIScene *> *allWindowScenes = UIApplication.sharedApplication.connectedScenes;
133    NSMutableArray<UIWindow *> *allForegroundWindows = [NSMutableArray new];
134    for (UIScene *scene in allWindowScenes.allObjects) {
135      if ([scene isKindOfClass:[UIWindowScene class]] && scene.activationState == UISceneActivationStateForegroundActive) {
136        [allForegroundWindows addObjectsFromArray:((UIWindowScene *)scene).windows];
137      }
138    }
139    allWindows = allForegroundWindows;
140  } else {
141    allWindows = UIApplication.sharedApplication.windows;
142  }
143
144  for (UIWindow *window in allWindows) {
145    UIViewController *controller = window.rootViewController;
146    if ([controller.view isKindOfClass:[RCTRootView class]]) {
147      return controller;
148    }
149    UIViewController *presentedController = controller.presentedViewController;
150    while (presentedController && ![presentedController isBeingDismissed]) {
151      if ([presentedController.view isKindOfClass:[RCTRootView class]]) {
152        return presentedController;
153      }
154      controller = presentedController;
155      presentedController = controller.presentedViewController;
156    }
157  }
158
159  // no RCTRootView was found
160  return nil;
161}
162
163@end
164