1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8#import "ABI47_0_0RCTAlertManager.h"
9
10#import <ABI47_0_0FBReactNativeSpec/ABI47_0_0FBReactNativeSpec.h>
11#import <ABI47_0_0RCTTypeSafety/ABI47_0_0RCTConvertHelpers.h>
12#import <ABI47_0_0React/ABI47_0_0RCTAssert.h>
13#import <ABI47_0_0React/ABI47_0_0RCTConvert.h>
14#import <ABI47_0_0React/ABI47_0_0RCTLog.h>
15#import <ABI47_0_0React/ABI47_0_0RCTUtils.h>
16
17#import "ABI47_0_0CoreModulesPlugins.h"
18#import "ABI47_0_0RCTAlertController.h"
19
20@implementation ABI47_0_0RCTConvert (UIAlertViewStyle)
21
22ABI47_0_0RCT_ENUM_CONVERTER(
23    ABI47_0_0RCTAlertViewStyle,
24    (@{
25      @"default" : @(ABI47_0_0RCTAlertViewStyleDefault),
26      @"secure-text" : @(ABI47_0_0RCTAlertViewStyleSecureTextInput),
27      @"plain-text" : @(ABI47_0_0RCTAlertViewStylePlainTextInput),
28      @"login-password" : @(ABI47_0_0RCTAlertViewStyleLoginAndPasswordInput),
29    }),
30    ABI47_0_0RCTAlertViewStyleDefault,
31    integerValue)
32
33@end
34
35@implementation ABI47_0_0RCTConvert (UIUserInterfaceStyle)
36
37ABI47_0_0RCT_ENUM_CONVERTER(
38    UIUserInterfaceStyle,
39    (@{
40      @"unspecified" : @(UIUserInterfaceStyleUnspecified),
41      @"light" : @(UIUserInterfaceStyleLight),
42      @"dark" : @(UIUserInterfaceStyleDark),
43    }),
44    UIUserInterfaceStyleUnspecified,
45    integerValue)
46
47@end
48
49@interface ABI47_0_0RCTAlertManager () <ABI47_0_0NativeAlertManagerSpec>
50
51@end
52
53@implementation ABI47_0_0RCTAlertManager {
54  NSHashTable *_alertControllers;
55}
56
57ABI47_0_0RCT_EXPORT_MODULE()
58
59- (dispatch_queue_t)methodQueue
60{
61  return dispatch_get_main_queue();
62}
63
64- (void)invalidate
65{
66  for (UIAlertController *alertController in _alertControllers) {
67    [alertController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
68  }
69}
70
71/**
72 * @param {NSDictionary} args Dictionary of the form
73 *
74 *   @{
75 *     @"message": @"<Alert message>",
76 *     @"buttons": @[
77 *       @{@"<key1>": @"<title1>"},
78 *       @{@"<key2>": @"<title2>"},
79 *     ],
80 *     @"cancelButtonKey": @"<key2>",
81 *   }
82 * The key from the `buttons` dictionary is passed back in the callback on click.
83 * Buttons are displayed in the order they are specified.
84 */
85ABI47_0_0RCT_EXPORT_METHOD(alertWithArgs : (ABI47_0_0JS::NativeAlertManager::Args &)args callback : (ABI47_0_0RCTResponseSenderBlock)callback)
86{
87  NSString *title = [ABI47_0_0RCTConvert NSString:args.title()];
88  NSString *message = [ABI47_0_0RCTConvert NSString:args.message()];
89  ABI47_0_0RCTAlertViewStyle type = [ABI47_0_0RCTConvert ABI47_0_0RCTAlertViewStyle:args.type()];
90  NSArray<NSDictionary *> *buttons =
91      [ABI47_0_0RCTConvert NSDictionaryArray:ABI47_0_0RCTConvertOptionalVecToArray(args.buttons(), ^id(id<NSObject> element) {
92                    return element;
93                  })];
94  NSString *defaultValue = [ABI47_0_0RCTConvert NSString:args.defaultValue()];
95  NSString *cancelButtonKey = [ABI47_0_0RCTConvert NSString:args.cancelButtonKey()];
96  NSString *destructiveButtonKey = [ABI47_0_0RCTConvert NSString:args.destructiveButtonKey()];
97  UIKeyboardType keyboardType = [ABI47_0_0RCTConvert UIKeyboardType:args.keyboardType()];
98
99  if (!title && !message) {
100    ABI47_0_0RCTLogError(@"Must specify either an alert title, or message, or both");
101    return;
102  }
103
104  if (buttons.count == 0) {
105    if (type == ABI47_0_0RCTAlertViewStyleDefault) {
106      buttons = @[ @{@"0" : ABI47_0_0RCTUIKitLocalizedString(@"OK")} ];
107      cancelButtonKey = @"0";
108    } else {
109      buttons = @[
110        @{@"0" : ABI47_0_0RCTUIKitLocalizedString(@"OK")},
111        @{@"1" : ABI47_0_0RCTUIKitLocalizedString(@"Cancel")},
112      ];
113      cancelButtonKey = @"1";
114    }
115  }
116
117  ABI47_0_0RCTAlertController *alertController = [ABI47_0_0RCTAlertController alertControllerWithTitle:title
118                                                                             message:nil
119                                                                      preferredStyle:UIAlertControllerStyleAlert];
120
121#if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && defined(__IPHONE_13_0) && \
122    __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_13_0
123  if (@available(iOS 13.0, *)) {
124    UIUserInterfaceStyle userInterfaceStyle = [ABI47_0_0RCTConvert UIUserInterfaceStyle:args.userInterfaceStyle()];
125    alertController.overrideUserInterfaceStyle = userInterfaceStyle;
126  }
127#endif
128
129  switch (type) {
130    case ABI47_0_0RCTAlertViewStylePlainTextInput: {
131      [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
132        textField.secureTextEntry = NO;
133        textField.text = defaultValue;
134        textField.keyboardType = keyboardType;
135      }];
136      break;
137    }
138    case ABI47_0_0RCTAlertViewStyleSecureTextInput: {
139      [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
140        textField.placeholder = ABI47_0_0RCTUIKitLocalizedString(@"Password");
141        textField.secureTextEntry = YES;
142        textField.text = defaultValue;
143        textField.keyboardType = keyboardType;
144      }];
145      break;
146    }
147    case ABI47_0_0RCTAlertViewStyleLoginAndPasswordInput: {
148      [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
149        textField.placeholder = ABI47_0_0RCTUIKitLocalizedString(@"Login");
150        textField.text = defaultValue;
151        textField.keyboardType = keyboardType;
152      }];
153      [alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
154        textField.placeholder = ABI47_0_0RCTUIKitLocalizedString(@"Password");
155        textField.secureTextEntry = YES;
156      }];
157      break;
158    }
159    case ABI47_0_0RCTAlertViewStyleDefault:
160      break;
161  }
162
163  alertController.message = message;
164
165  for (NSDictionary<NSString *, id> *button in buttons) {
166    if (button.count != 1) {
167      ABI47_0_0RCTLogError(@"Button definitions should have exactly one key.");
168    }
169    NSString *buttonKey = button.allKeys.firstObject;
170    NSString *buttonTitle = [ABI47_0_0RCTConvert NSString:button[buttonKey]];
171    UIAlertActionStyle buttonStyle = UIAlertActionStyleDefault;
172    if ([buttonKey isEqualToString:cancelButtonKey]) {
173      buttonStyle = UIAlertActionStyleCancel;
174    } else if ([buttonKey isEqualToString:destructiveButtonKey]) {
175      buttonStyle = UIAlertActionStyleDestructive;
176    }
177    __weak ABI47_0_0RCTAlertController *weakAlertController = alertController;
178    [alertController
179        addAction:[UIAlertAction
180                      actionWithTitle:buttonTitle
181                                style:buttonStyle
182                              handler:^(__unused UIAlertAction *action) {
183                                switch (type) {
184                                  case ABI47_0_0RCTAlertViewStylePlainTextInput:
185                                  case ABI47_0_0RCTAlertViewStyleSecureTextInput:
186                                    callback(@[ buttonKey, [weakAlertController.textFields.firstObject text] ]);
187                                    [weakAlertController hide];
188                                    break;
189                                  case ABI47_0_0RCTAlertViewStyleLoginAndPasswordInput: {
190                                    NSDictionary<NSString *, NSString *> *loginCredentials = @{
191                                      @"login" : [weakAlertController.textFields.firstObject text],
192                                      @"password" : [weakAlertController.textFields.lastObject text]
193                                    };
194                                    callback(@[ buttonKey, loginCredentials ]);
195                                    [weakAlertController hide];
196                                    break;
197                                  }
198                                  case ABI47_0_0RCTAlertViewStyleDefault:
199                                    callback(@[ buttonKey ]);
200                                    [weakAlertController hide];
201                                    break;
202                                }
203                              }]];
204  }
205
206  if (!_alertControllers) {
207    _alertControllers = [NSHashTable weakObjectsHashTable];
208  }
209  [_alertControllers addObject:alertController];
210
211  dispatch_async(dispatch_get_main_queue(), ^{
212    [alertController show:YES completion:nil];
213  });
214}
215
216- (std::shared_ptr<ABI47_0_0facebook::ABI47_0_0React::TurboModule>)getTurboModule:
217    (const ABI47_0_0facebook::ABI47_0_0React::ObjCTurboModule::InitParams &)params
218{
219  return std::make_shared<ABI47_0_0facebook::ABI47_0_0React::NativeAlertManagerSpecJSI>(params);
220}
221
222@end
223
224Class ABI47_0_0RCTAlertManagerCls(void)
225{
226  return ABI47_0_0RCTAlertManager.class;
227}
228