4747 bool visible = false ;
4848};
4949
50+ static const NSTimeInterval kAutoRetryInterval = 20.0 ;
51+
5052@implementation RCTRedBox2Controller {
5153 UITableView *_stackTraceTableView;
5254 UILabel *_headerTitleLabel;
@@ -58,6 +60,10 @@ @implementation RCTRedBox2Controller {
5860 int _lastErrorCookie;
5961 RCTRedBox2ErrorData *_errorData;
6062 std::array<SectionState, kSectionCount > _sectionStates;
63+ NSTimer *_autoRetryTimer;
64+ NSInteger _autoRetryCountdown;
65+ UIButton *_reloadButton;
66+ NSString *_reloadBaseText;
6167}
6268
6369- (instancetype )initWithCustomButtonTitles : (NSArray <NSString *> *)customButtonTitles
@@ -147,9 +153,8 @@ - (UIView *)createFooterBar
147153 UIButton *dismissButton = [self footerButton: dismissText
148154 accessibilityIdentifier: @" redbox-dismiss"
149155 selector: @selector (dismiss )];
150- UIButton *reloadButton = [self footerButton: reloadText
151- accessibilityIdentifier: @" redbox-reload"
152- selector: @selector (reload )];
156+ _reloadBaseText = reloadText;
157+ _reloadButton = [self footerButton: reloadText accessibilityIdentifier: @" redbox-reload" selector: @selector (reload )];
153158 UIButton *copyButton = [self footerButton: copyText
154159 accessibilityIdentifier: @" redbox-copy"
155160 selector: @selector (copyStack )];
@@ -162,7 +167,7 @@ - (UIView *)createFooterBar
162167 buttonStackView.backgroundColor = RCTRedBox2BackgroundColor ();
163168
164169 [buttonStackView addArrangedSubview: dismissButton];
165- [buttonStackView addArrangedSubview: reloadButton ];
170+ [buttonStackView addArrangedSubview: _reloadButton ];
166171 [buttonStackView addArrangedSubview: copyButton];
167172
168173 for (NSUInteger i = 0 ; i < [_customButtonTitles count ]; i++) {
@@ -281,16 +286,27 @@ - (void)showErrorMessage:(NSString *)message
281286 if (!isRootViewControllerPresented) {
282287 [RCTKeyWindow ().rootViewController presentViewController: self animated: NO completion: nil ];
283288 }
289+
290+ // Update all UI from _errorData (view is now guaranteed to be loaded)
291+ _headerTitleLabel.text = _errorData.isCompileError ? @" Failed to compile" : @" Error" ;
292+ [_stackTraceTableView reloadData ];
293+ [_stackTraceTableView scrollToRowAtIndexPath: [NSIndexPath indexPathForRow: 0 inSection: 0 ]
294+ atScrollPosition: UITableViewScrollPositionTop
295+ animated: NO ];
296+
297+ [self startAutoRetryIfApplicable ];
284298 }
285299}
286300
287301- (void )dismiss
288302{
303+ [self stopAutoRetry ];
289304 [self dismissViewControllerAnimated: NO completion: nil ];
290305}
291306
292307- (void )reload
293308{
309+ [self stopAutoRetry ];
294310 if (_actionDelegate != nil ) {
295311 [_actionDelegate reloadFromRedBoxController: self ];
296312 } else {
@@ -300,6 +316,49 @@ - (void)reload
300316 }
301317}
302318
319+ #pragma mark - Auto-Retry
320+
321+ - (void )startAutoRetryIfApplicable
322+ {
323+ [self stopAutoRetry ];
324+ if (!_errorData.isRetryable ) {
325+ return ;
326+ }
327+ _autoRetryCountdown = (NSInteger )kAutoRetryInterval ;
328+ [self updateReloadButtonTitle ];
329+ _autoRetryTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0
330+ target: self
331+ selector: @selector (autoRetryTick )
332+ userInfo: nil
333+ repeats: YES ];
334+ }
335+
336+ - (void )stopAutoRetry
337+ {
338+ [_autoRetryTimer invalidate ];
339+ _autoRetryTimer = nil ;
340+ if (_reloadButton) {
341+ [_reloadButton setTitle: _reloadBaseText forState: UIControlStateNormal];
342+ }
343+ }
344+
345+ - (void )autoRetryTick
346+ {
347+ _autoRetryCountdown--;
348+ if (_autoRetryCountdown <= 0 ) {
349+ [self stopAutoRetry ];
350+ [self reload ];
351+ } else {
352+ [self updateReloadButtonTitle ];
353+ }
354+ }
355+
356+ - (void )updateReloadButtonTitle
357+ {
358+ NSString *title = [NSString stringWithFormat: @" %@ (%ld s)" , _reloadBaseText, (long )_autoRetryCountdown];
359+ [_reloadButton setTitle: title forState: UIControlStateNormal];
360+ }
361+
303362- (void )copyStack
304363{
305364 NSMutableString *fullStackTrace;
0 commit comments