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 "ABI48_0_0RCTRedBoxExtraDataViewController.h"
9
10@interface ABI48_0_0RCTRedBoxExtraDataCell : UITableViewCell
11
12@property (nonatomic, strong) UILabel *keyLabel;
13@property (nonatomic, strong) UILabel *valueLabel;
14
15@end
16
17@implementation ABI48_0_0RCTRedBoxExtraDataCell
18
19- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
20{
21  if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]) {
22    self.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
23    UILayoutGuide *contentLayout = self.contentView.layoutMarginsGuide;
24
25    self.keyLabel = [UILabel new];
26    [self.contentView addSubview:self.keyLabel];
27
28    self.keyLabel.translatesAutoresizingMaskIntoConstraints = NO;
29    [self.keyLabel.leadingAnchor constraintEqualToAnchor:contentLayout.leadingAnchor].active = YES;
30    [self.keyLabel.topAnchor constraintEqualToAnchor:contentLayout.topAnchor].active = YES;
31    [self.keyLabel.bottomAnchor constraintEqualToAnchor:contentLayout.bottomAnchor].active = YES;
32    [self.keyLabel.widthAnchor constraintEqualToAnchor:contentLayout.widthAnchor multiplier:0.3].active = YES;
33
34    self.keyLabel.textColor = [UIColor whiteColor];
35    self.keyLabel.numberOfLines = 0;
36    self.keyLabel.lineBreakMode = NSLineBreakByWordWrapping;
37    self.keyLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:12.0f];
38    self.valueLabel = [UILabel new];
39    [self.contentView addSubview:self.valueLabel];
40
41    self.valueLabel.translatesAutoresizingMaskIntoConstraints = NO;
42    [self.valueLabel.leadingAnchor constraintEqualToAnchor:self.keyLabel.trailingAnchor constant:10.f].active = YES;
43    [self.valueLabel.trailingAnchor constraintEqualToAnchor:contentLayout.trailingAnchor].active = YES;
44    [self.valueLabel.topAnchor constraintEqualToAnchor:contentLayout.topAnchor].active = YES;
45    [self.valueLabel.bottomAnchor constraintEqualToAnchor:contentLayout.bottomAnchor].active = YES;
46
47    self.valueLabel.textColor = [UIColor whiteColor];
48    self.valueLabel.numberOfLines = 0;
49    self.valueLabel.lineBreakMode = NSLineBreakByWordWrapping;
50    self.valueLabel.font = [UIFont fontWithName:@"Menlo-Regular" size:12.0f];
51  }
52  return self;
53}
54
55@end
56
57@interface ABI48_0_0RCTRedBoxExtraDataViewController ()
58
59@end
60
61@implementation ABI48_0_0RCTRedBoxExtraDataViewController {
62  UITableView *_tableView;
63  NSMutableArray *_extraDataTitle;
64  NSMutableArray *_extraData;
65}
66
67@synthesize actionDelegate = _actionDelegate;
68
69- (instancetype)init
70{
71  if (self = [super init]) {
72    _extraData = [NSMutableArray new];
73    _extraDataTitle = [NSMutableArray new];
74    self.view.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
75
76    _tableView = [UITableView new];
77    _tableView.delegate = self;
78    _tableView.dataSource = self;
79    _tableView.backgroundColor = [UIColor clearColor];
80    _tableView.estimatedRowHeight = 200;
81    _tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
82    _tableView.rowHeight = UITableViewAutomaticDimension;
83    _tableView.allowsSelection = NO;
84
85#if TARGET_OS_SIMULATOR || TARGET_OS_MACCATALYST
86    NSString *reloadText = @"Reload JS (\u2318R)";
87    NSString *dismissText = @"Dismiss (ESC)";
88#else
89    NSString *reloadText = @"Reload JS";
90    NSString *dismissText = @"Dismiss";
91#endif
92
93    UIButton *dismissButton = [UIButton buttonWithType:UIButtonTypeCustom];
94    dismissButton.translatesAutoresizingMaskIntoConstraints = NO;
95    dismissButton.accessibilityIdentifier = @"redbox-extra-data-dismiss";
96    dismissButton.titleLabel.font = [UIFont systemFontOfSize:13];
97    [dismissButton setTitle:dismissText forState:UIControlStateNormal];
98    [dismissButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
99    [dismissButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
100    [dismissButton addTarget:self action:@selector(dismiss) forControlEvents:UIControlEventTouchUpInside];
101
102    UIButton *reloadButton = [UIButton buttonWithType:UIButtonTypeCustom];
103    reloadButton.accessibilityIdentifier = @"redbox-reload";
104    reloadButton.titleLabel.font = [UIFont systemFontOfSize:13];
105    [reloadButton setTitle:reloadText forState:UIControlStateNormal];
106    [reloadButton setTitleColor:[UIColor colorWithWhite:1 alpha:0.5] forState:UIControlStateNormal];
107    [reloadButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];
108    [reloadButton addTarget:self action:@selector(reload) forControlEvents:UIControlEventTouchUpInside];
109
110    UIStackView *buttonStackView = [UIStackView new];
111    buttonStackView.axis = UILayoutConstraintAxisHorizontal;
112    buttonStackView.distribution = UIStackViewDistributionEqualSpacing;
113    buttonStackView.alignment = UIStackViewAlignmentFill;
114    buttonStackView.spacing = 20;
115
116    [buttonStackView addArrangedSubview:dismissButton];
117    [buttonStackView addArrangedSubview:reloadButton];
118    buttonStackView.translatesAutoresizingMaskIntoConstraints = NO;
119
120    UIStackView *mainStackView = [UIStackView new];
121    mainStackView.axis = UILayoutConstraintAxisVertical;
122    mainStackView.backgroundColor = [UIColor colorWithRed:0.8 green:0 blue:0 alpha:1];
123    [mainStackView addArrangedSubview:_tableView];
124    [mainStackView addArrangedSubview:buttonStackView];
125    mainStackView.translatesAutoresizingMaskIntoConstraints = NO;
126
127    [self.view addSubview:mainStackView];
128
129    CGFloat tableHeight = self.view.bounds.size.height - 60.f;
130    [_tableView.heightAnchor constraintEqualToConstant:tableHeight].active = YES;
131    [_tableView.widthAnchor constraintEqualToAnchor:self.view.widthAnchor].active = YES;
132
133    CGFloat buttonWidth = self.view.bounds.size.width / 4;
134    [dismissButton.heightAnchor constraintEqualToConstant:60].active = YES;
135    [dismissButton.widthAnchor constraintEqualToConstant:buttonWidth].active = YES;
136    [reloadButton.heightAnchor constraintEqualToConstant:60].active = YES;
137    [reloadButton.widthAnchor constraintEqualToConstant:buttonWidth].active = YES;
138  }
139  return self;
140}
141
142- (void)viewDidAppear:(BOOL)animated
143{
144  [super viewDidAppear:animated];
145  [_tableView reloadData];
146}
147
148- (NSInteger)tableView:(__unused UITableView *)tableView numberOfRowsInSection:(NSInteger)section
149{
150  return [[_extraData objectAtIndex:section] count];
151}
152
153- (CGFloat)tableView:(__unused UITableView *)tableView heightForHeaderInSection:(__unused NSInteger)section
154{
155  return 40;
156}
157
158- (UIView *)tableView:(__unused UITableView *)tableView viewForHeaderInSection:(NSInteger)section
159{
160  UIView *view = [UIView new];
161  view.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:1];
162
163  UILabel *header = [UILabel new];
164  [view addSubview:header];
165
166  header.translatesAutoresizingMaskIntoConstraints = NO;
167  [header.leadingAnchor constraintEqualToAnchor:view.leadingAnchor constant:5].active = YES;
168  [header.trailingAnchor constraintEqualToAnchor:view.trailingAnchor].active = YES;
169  [header.topAnchor constraintEqualToAnchor:view.topAnchor].active = YES;
170  [header.bottomAnchor constraintEqualToAnchor:view.bottomAnchor].active = YES;
171
172  header.textColor = [UIColor whiteColor];
173  header.font = [UIFont fontWithName:@"Menlo-Bold" size:14.0f];
174  header.text = [_extraDataTitle[section] uppercaseString];
175
176  return view;
177}
178
179- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
180{
181  static NSString *reuseIdentifier = @"RedBoxExtraData";
182
183  ABI48_0_0RCTRedBoxExtraDataCell *cell =
184      (ABI48_0_0RCTRedBoxExtraDataCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
185
186  if (cell == nil) {
187    cell = [[ABI48_0_0RCTRedBoxExtraDataCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
188  }
189
190  NSArray *dataKVPair = _extraData[indexPath.section][indexPath.row];
191  cell.keyLabel.text = dataKVPair[0];
192  cell.valueLabel.text = dataKVPair[1];
193
194  return cell;
195}
196
197- (NSInteger)numberOfSectionsInTableView:(__unused UITableView *)tableView
198{
199  return _extraDataTitle.count;
200}
201
202- (void)addExtraData:(NSDictionary *)data forIdentifier:(NSString *)identifier
203{
204  dispatch_async(dispatch_get_main_queue(), ^{
205    NSMutableArray *newData = [NSMutableArray new];
206    for (id key in data) {
207      [newData addObject:@[
208        [NSString stringWithFormat:@"%@", key],
209        [NSString stringWithFormat:@"%@", [data objectForKey:key]]
210      ]];
211    }
212
213    NSInteger idx = [self->_extraDataTitle indexOfObject:identifier];
214    if (idx == NSNotFound) {
215      [self->_extraDataTitle addObject:identifier];
216      [self->_extraData addObject:newData];
217    } else {
218      [self->_extraData replaceObjectAtIndex:idx withObject:newData];
219    }
220
221    [self->_tableView reloadData];
222  });
223}
224
225- (void)dismiss
226{
227  [self dismissViewControllerAnimated:YES completion:nil];
228}
229
230- (void)reload
231{
232  [_actionDelegate reload];
233}
234
235#pragma mark - Key commands
236
237- (NSArray<UIKeyCommand *> *)keyCommands
238{
239  return @[
240    // Dismiss
241    [UIKeyCommand keyCommandWithInput:UIKeyInputEscape modifierFlags:0 action:@selector(dismiss)],
242    // Reload
243    [UIKeyCommand keyCommandWithInput:@"r" modifierFlags:UIKeyModifierCommand action:@selector(reload)]
244  ];
245}
246
247@end
248