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,10 @@ - (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
158+ accessibilityIdentifier: @" redbox-reload"
159+ selector: @selector (reload )];
153160 UIButton *copyButton = [self footerButton: copyText
154161 accessibilityIdentifier: @" redbox-copy"
155162 selector: @selector (copyStack )];
@@ -162,7 +169,7 @@ - (UIView *)createFooterBar
162169 buttonStackView.backgroundColor = RCTRedBox2BackgroundColor ();
163170
164171 [buttonStackView addArrangedSubview: dismissButton];
165- [buttonStackView addArrangedSubview: reloadButton ];
172+ [buttonStackView addArrangedSubview: _reloadButton ];
166173 [buttonStackView addArrangedSubview: copyButton];
167174
168175 for (NSUInteger i = 0 ; i < [_customButtonTitles count ]; i++) {
@@ -281,16 +288,27 @@ - (void)showErrorMessage:(NSString *)message
281288 if (!isRootViewControllerPresented) {
282289 [RCTKeyWindow ().rootViewController presentViewController: self animated: NO completion: nil ];
283290 }
291+
292+ // Update all UI from _errorData (view is now guaranteed to be loaded)
293+ _headerTitleLabel.text = _errorData.isCompileError ? @" Failed to compile" : @" Error" ;
294+ [_stackTraceTableView reloadData ];
295+ [_stackTraceTableView scrollToRowAtIndexPath: [NSIndexPath indexPathForRow: 0 inSection: 0 ]
296+ atScrollPosition: UITableViewScrollPositionTop
297+ animated: NO ];
298+
299+ [self startAutoRetryIfApplicable ];
284300 }
285301}
286302
287303- (void )dismiss
288304{
305+ [self stopAutoRetry ];
289306 [self dismissViewControllerAnimated: NO completion: nil ];
290307}
291308
292309- (void )reload
293310{
311+ [self stopAutoRetry ];
294312 if (_actionDelegate != nil ) {
295313 [_actionDelegate reloadFromRedBoxController: self ];
296314 } else {
@@ -300,6 +318,49 @@ - (void)reload
300318 }
301319}
302320
321+ #pragma mark - Auto-Retry
322+
323+ - (void )startAutoRetryIfApplicable
324+ {
325+ [self stopAutoRetry ];
326+ if (!_errorData.isRetryable ) {
327+ return ;
328+ }
329+ _autoRetryCountdown = (NSInteger )kAutoRetryInterval ;
330+ [self updateReloadButtonTitle ];
331+ _autoRetryTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0
332+ target: self
333+ selector: @selector (autoRetryTick )
334+ userInfo: nil
335+ repeats: YES ];
336+ }
337+
338+ - (void )stopAutoRetry
339+ {
340+ [_autoRetryTimer invalidate ];
341+ _autoRetryTimer = nil ;
342+ if (_reloadButton) {
343+ [_reloadButton setTitle: _reloadBaseText forState: UIControlStateNormal];
344+ }
345+ }
346+
347+ - (void )autoRetryTick
348+ {
349+ _autoRetryCountdown--;
350+ if (_autoRetryCountdown <= 0 ) {
351+ [self stopAutoRetry ];
352+ [self reload ];
353+ } else {
354+ [self updateReloadButtonTitle ];
355+ }
356+ }
357+
358+ - (void )updateReloadButtonTitle
359+ {
360+ NSString *title = [NSString stringWithFormat: @" %@ (%ld s)" , _reloadBaseText, (long )_autoRetryCountdown];
361+ [_reloadButton setTitle: title forState: UIControlStateNormal];
362+ }
363+
303364- (void )copyStack
304365{
305366 NSMutableString *fullStackTrace;
0 commit comments