1
2#import "EXStatusBarManager.h"
3#import "EXUnversioned.h"
4
5#import <React/RCTEventDispatcher.h>
6#import <React/RCTLog.h>
7#import <React/RCTUtils.h>
8
9#if !TARGET_OS_TV
10@implementation RCTConvert (EXStatusBar)
11
12RCT_ENUM_CONVERTER(UIStatusBarStyle, (@{
13  @"default": @(UIStatusBarStyleDefault),
14  @"light-content": @(UIStatusBarStyleLightContent),
15  @"dark-content": @(UIStatusBarStyleDefault),
16}), UIStatusBarStyleDefault, integerValue);
17
18RCT_ENUM_CONVERTER(UIStatusBarAnimation, (@{
19  @"none": @(UIStatusBarAnimationNone),
20  @"fade": @(UIStatusBarAnimationFade),
21  @"slide": @(UIStatusBarAnimationSlide),
22}), UIStatusBarAnimationNone, integerValue);
23
24@end
25#endif
26
27@interface EXStatusBarManager ()
28
29@property (nonatomic, strong) NSMutableDictionary *capturedStatusBarProperties;
30
31@end
32
33@implementation EXStatusBarManager
34
35+ (NSString *)moduleName { return @"RCTStatusBarManager"; }
36
37- (NSArray<NSString *> *)supportedEvents
38{
39  return @[@"statusBarFrameDidChange",
40           @"statusBarFrameWillChange"];
41}
42
43#if !TARGET_OS_TV
44
45- (void)setBridge:(RCTBridge *)bridge
46{
47  [super setBridge:bridge];
48  _capturedStatusBarProperties = [[self _currentStatusBarProperties] mutableCopy];
49  [[NSNotificationCenter defaultCenter] addObserver:self
50                                           selector:@selector(_bridgeDidForeground:)
51                                               name:EX_UNVERSIONED(@"EXKernelBridgeDidForegroundNotification")
52                                             object:self.bridge];
53
54  [[NSNotificationCenter defaultCenter] addObserver:self
55                                           selector:@selector(_bridgeDidBackground:)
56                                               name:EX_UNVERSIONED(@"EXKernelBridgeDidBackgroundNotification")
57                                             object:self.bridge];
58}
59
60- (void)dealloc
61{
62  [self stopObserving];
63}
64
65- (void)startObserving
66{
67  NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
68  [nc addObserver:self selector:@selector(applicationDidChangeStatusBarFrame:) name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
69  [nc addObserver:self selector:@selector(applicationWillChangeStatusBarFrame:) name:UIApplicationWillChangeStatusBarFrameNotification object:nil];
70}
71
72- (void)stopObserving
73{
74  [[NSNotificationCenter defaultCenter] removeObserver:self];
75}
76
77- (dispatch_queue_t)methodQueue
78{
79  return dispatch_get_main_queue();
80}
81
82- (void)emitEvent:(NSString *)eventName forNotification:(NSNotification *)notification
83{
84  CGRect frame = [notification.userInfo[UIApplicationStatusBarFrameUserInfoKey] CGRectValue];
85  NSDictionary *event = @{
86    @"frame": @{
87      @"x": @(frame.origin.x),
88      @"y": @(frame.origin.y),
89      @"width": @(frame.size.width),
90      @"height": @(frame.size.height),
91    },
92  };
93  [self sendEventWithName:eventName body:event];
94}
95
96- (void)applicationDidChangeStatusBarFrame:(NSNotification *)notification
97{
98  [self emitEvent:@"statusBarFrameDidChange" forNotification:notification];
99}
100
101- (void)applicationWillChangeStatusBarFrame:(NSNotification *)notification
102{
103  [self emitEvent:@"statusBarFrameWillChange" forNotification:notification];
104}
105
106RCT_EXPORT_METHOD(getHeight:(RCTResponseSenderBlock)callback)
107{
108  callback(@[@{
109    @"height": @([UIApplication sharedApplication].statusBarFrame.size.height),
110  }]);
111}
112
113RCT_EXPORT_METHOD(setStyle:(UIStatusBarStyle)statusBarStyle
114                  animated:(BOOL)animated)
115{
116  if ([[self class] _viewControllerBasedStatusBarAppearance]) {
117    RCTLogError(@"RCTStatusBarManager module requires that the \
118                UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
119  } else {
120#pragma clang diagnostic push
121#pragma clang diagnostic ignored "-Wdeprecated-declarations"
122    [RCTSharedApplication() setStatusBarStyle:statusBarStyle
123                                     animated:animated];
124    _capturedStatusBarProperties[@"style"] = @(statusBarStyle);
125#pragma clang diagnostic pop
126  }
127}
128
129RCT_EXPORT_METHOD(setHidden:(BOOL)hidden
130                  withAnimation:(UIStatusBarAnimation)animation)
131{
132  if ([[self class] _viewControllerBasedStatusBarAppearance]) {
133    RCTLogError(@"RCTStatusBarManager module requires that the \
134                UIViewControllerBasedStatusBarAppearance key in the Info.plist is set to NO");
135  } else {
136#pragma clang diagnostic push
137#pragma clang diagnostic ignored "-Wdeprecated-declarations"
138    [RCTSharedApplication() setStatusBarHidden:hidden
139                                 withAnimation:animation];
140    _capturedStatusBarProperties[@"hidden"] = @(hidden);
141#pragma clang diagnostic pop
142  }
143}
144
145RCT_EXPORT_METHOD(setNetworkActivityIndicatorVisible:(BOOL)visible)
146{
147  RCTSharedApplication().networkActivityIndicatorVisible = visible;
148  _capturedStatusBarProperties[@"networkActivityIndicatorVisible"] = @(visible);
149}
150
151/**
152 *  Used by the expo menu to restore status bar state between bridges.
153 *  Normally nobody should use this method because it bypasses the JS state used by the StatusBar component.
154 */
155RCT_REMAP_METHOD(_captureProperties,
156                 _capturePropertiesWithResolver:(RCTPromiseResolveBlock)resolve
157                 rejecter:(RCTPromiseRejectBlock)reject)
158{
159  resolve([self _currentStatusBarProperties]);
160}
161
162/**
163 *  Used by the expo menu to restore status bar state between bridges.
164 *  Normally nobody should use this method because it bypasses the JS state used by the StatusBar component.
165 */
166RCT_EXPORT_METHOD(_applyPropertiesAndForget:(NSDictionary *)properties)
167{
168  [self _applyCapturedProperties:properties];
169}
170
171#pragma mark - internal
172
173+ (BOOL)_viewControllerBasedStatusBarAppearance
174{
175  static BOOL value;
176  static dispatch_once_t onceToken;
177  dispatch_once(&onceToken, ^{
178    value = [[[NSBundle mainBundle] objectForInfoDictionaryKey:
179              @"UIViewControllerBasedStatusBarAppearance"] ?: @YES boolValue];
180  });
181
182  return value;
183}
184
185- (NSDictionary *)_currentStatusBarProperties
186{
187  UIApplication *currentApplication = RCTSharedApplication();
188  return @{
189    @"style": @(currentApplication.statusBarStyle),
190    @"networkActivityIndicatorVisible": @(currentApplication.isNetworkActivityIndicatorVisible),
191    @"hidden": @(currentApplication.isStatusBarHidden),
192  };
193}
194
195- (void)_applyCapturedProperties:(NSDictionary *)properties
196{
197  UIApplication *currentApplication = RCTSharedApplication();
198  if (![[self class] _viewControllerBasedStatusBarAppearance]) {
199#pragma clang diagnostic push
200#pragma clang diagnostic ignored "-Wdeprecated-declarations"
201    [currentApplication setStatusBarStyle:(UIStatusBarStyle)[properties[@"style"] integerValue] animated:NO];
202    [currentApplication setStatusBarHidden:[properties[@"hidden"] boolValue] withAnimation:UIStatusBarAnimationNone];
203#pragma clang diagnostic pop
204  }
205  currentApplication.networkActivityIndicatorVisible = [properties[@"networkActivityIndicatorVisible"] boolValue];
206}
207
208- (void)_bridgeDidForeground:(__unused NSNotification *)notif
209{
210  [self _applyCapturedProperties:_capturedStatusBarProperties];
211}
212
213- (void)_bridgeDidBackground:(__unused NSNotification *)notif
214{
215  _capturedStatusBarProperties = [[self _currentStatusBarProperties] mutableCopy];
216}
217
218#endif //TARGET_OS_TV
219
220@end
221