1// Copyright © 2018 650 Industries. All rights reserved.
2
3#import <ExpoModulesCore/EXDefines.h>
4#import <ExpoModulesCore/EXUtilities.h>
5
6@interface EXUtilities ()
7
8@property (nonatomic, nullable, weak) EXModuleRegistry *moduleRegistry;
9
10@end
11
12@protocol EXUtilService
13
14- (UIViewController *)currentViewController;
15
16- (nullable NSDictionary *)launchOptions;
17
18@end
19
20@implementation EXUtilities
21
22EX_REGISTER_MODULE();
23
24+ (const NSArray<Protocol *> *)exportedInterfaces
25{
26  return @[@protocol(EXUtilitiesInterface)];
27}
28
29- (void)setModuleRegistry:(EXModuleRegistry *)moduleRegistry
30{
31  _moduleRegistry = moduleRegistry;
32}
33
34- (nullable NSDictionary *)launchOptions
35{
36  id<EXUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"];
37  return [utilService launchOptions];
38}
39
40- (UIViewController *)currentViewController
41{
42  id<EXUtilService> utilService = [_moduleRegistry getSingletonModuleForName:@"Util"];
43
44  if (utilService != nil) {
45    return [utilService currentViewController];
46  }
47
48  UIViewController *controller = [[[UIApplication sharedApplication] keyWindow] rootViewController];
49  UIViewController *presentedController = controller.presentedViewController;
50
51  while (presentedController && ![presentedController isBeingDismissed]) {
52    controller = presentedController;
53    presentedController = controller.presentedViewController;
54  }
55  return controller;
56}
57
58+ (void)performSynchronouslyOnMainThread:(void (^)(void))block
59{
60  if ([NSThread isMainThread]) {
61    block();
62  } else {
63    dispatch_sync(dispatch_get_main_queue(), block);
64  }
65}
66
67// Copied from RN
68+ (BOOL)isMainQueue
69{
70  static void *mainQueueKey = &mainQueueKey;
71  static dispatch_once_t onceToken;
72
73  dispatch_once(&onceToken, ^{
74    dispatch_queue_set_specific(dispatch_get_main_queue(),
75                                mainQueueKey, mainQueueKey, NULL);
76  });
77
78  return dispatch_get_specific(mainQueueKey) == mainQueueKey;
79}
80
81// Copied from RN
82+ (void)unsafeExecuteOnMainQueueOnceSync:(dispatch_once_t *)onceToken block:(dispatch_block_t)block
83{
84  // The solution was borrowed from a post by Ben Alpert:
85  // https://benalpert.com/2014/04/02/dispatch-once-initialization-on-the-main-thread.html
86  // See also: https://www.mikeash.com/pyblog/friday-qa-2014-06-06-secrets-of-dispatch_once.html
87  if ([self isMainQueue]) {
88    dispatch_once(onceToken, block);
89  } else {
90    if (DISPATCH_EXPECT(*onceToken == 0L, NO)) {
91      dispatch_sync(dispatch_get_main_queue(), ^{
92        dispatch_once(onceToken, block);
93      });
94    }
95  }
96}
97
98// Copied from RN
99+ (CGFloat)screenScale
100{
101  static dispatch_once_t onceToken;
102  static CGFloat scale;
103
104  [self unsafeExecuteOnMainQueueOnceSync:&onceToken block:^{
105      scale = [UIScreen mainScreen].scale;
106  }];
107
108  return scale;
109}
110
111
112// Kind of copied from RN to make UIColor:(id)json work
113+ (NSArray<NSNumber *> *)NSNumberArray:(id)json
114{
115  return json;
116}
117
118+ (NSNumber *)NSNumber:(id)json
119{
120  return json;
121}
122
123+ (CGFloat)CGFloat:(id)json
124{
125  return [[self NSNumber:json] floatValue];
126}
127
128+ (NSInteger)NSInteger:(id)json
129{
130  return [[self NSNumber:json] integerValue];
131}
132
133+ (NSUInteger)NSUInteger:(id)json
134{
135  return [[self NSNumber:json] unsignedIntegerValue];
136}
137
138// Copied from RN
139+ (UIColor *)UIColor:(id)json
140{
141  if (!json) {
142    return nil;
143  }
144  if ([json isKindOfClass:[NSArray class]]) {
145    NSArray *components = [self NSNumberArray:json];
146    CGFloat alpha = components.count > 3 ? [self CGFloat:components[3]] : 1.0;
147    return [UIColor colorWithRed:[self CGFloat:components[0]]
148                           green:[self CGFloat:components[1]]
149                            blue:[self CGFloat:components[2]]
150                           alpha:alpha];
151  } else if ([json isKindOfClass:[NSNumber class]]) {
152    NSUInteger argb = [self NSUInteger:json];
153    CGFloat a = ((argb >> 24) & 0xFF) / 255.0;
154    CGFloat r = ((argb >> 16) & 0xFF) / 255.0;
155    CGFloat g = ((argb >> 8) & 0xFF) / 255.0;
156    CGFloat b = (argb & 0xFF) / 255.0;
157    return [UIColor colorWithRed:r green:g blue:b alpha:a];
158  } else {
159    EXLogInfo(@"%@ cannot be converted to a UIColor", json);
160    return nil;
161  }
162}
163
164// Copied from RN
165+ (NSDate *)NSDate:(id)json
166{
167  if ([json isKindOfClass:[NSNumber class]]) {
168    return [NSDate dateWithTimeIntervalSince1970:[json doubleValue] / 1000.0];
169  } else if ([json isKindOfClass:[NSString class]]) {
170    static NSDateFormatter *formatter;
171    static dispatch_once_t onceToken;
172    dispatch_once(&onceToken, ^{
173      formatter = [NSDateFormatter new];
174      formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ";
175      formatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"];
176      formatter.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
177    });
178    NSDate *date = [formatter dateFromString:json];
179    if (!date) {
180      EXLogError(@"JSON String '%@' could not be interpreted as a date. "
181                  "Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
182    }
183    return date;
184  } else if (json) {
185    EXLogError(json, @"a date");
186  }
187  return nil;
188}
189
190// https://stackoverflow.com/questions/14051807/how-can-i-get-a-hex-string-from-uicolor-or-from-rgb
191+ (NSString *)hexStringWithCGColor:(CGColorRef)color
192{
193  const CGFloat *components = CGColorGetComponents(color);
194  size_t count = CGColorGetNumberOfComponents(color);
195
196  if (count == 2) {
197    return [NSString stringWithFormat:@"#%02lX%02lX%02lX",
198            lroundf(components[0] * 255.0),
199            lroundf(components[0] * 255.0),
200            lroundf(components[0] * 255.0)];
201  } else {
202    return [NSString stringWithFormat:@"#%02lX%02lX%02lX",
203            lroundf(components[0] * 255.0),
204            lroundf(components[1] * 255.0),
205            lroundf(components[2] * 255.0)];
206  }
207}
208
209+ (BOOL)catchException:(void(^)(void))tryBlock error:(__autoreleasing NSError **)error
210{
211  @try {
212    tryBlock();
213    return YES;
214  }
215  @catch (NSException *exception) {
216    *error = [[NSError alloc] initWithDomain:exception.name code:0 userInfo:exception.userInfo];
217    return NO;
218  }
219}
220
221@end
222
223UIApplication * EXSharedApplication(void)
224{
225  if ([[[[NSBundle mainBundle] bundlePath] pathExtension] isEqualToString:@"appex"]) {
226    return nil;
227  }
228  return [[UIApplication class] performSelector:@selector(sharedApplication)];
229}
230
231NSError *EXErrorWithMessage(NSString *message)
232{
233  NSDictionary<NSString *, id> *errorInfo = @{NSLocalizedDescriptionKey: message};
234  return [[NSError alloc] initWithDomain:@"EXModulesErrorDomain" code:0 userInfo:errorInfo];
235}
236