1// Copyright 2015-present 650 Industries. All rights reserved. 2 3#import "EXAppLoader.h" 4#import "EXErrorView.h" 5#import "EXEnvironment.h" 6#import "EXKernel.h" 7#import "EXKernelAppRecord.h" 8#import "EXUtil.h" 9 10@interface EXErrorView () 11 12@property (nonatomic, strong) UILabel *lblError; 13@property (nonatomic, strong) UIButton *btnRetry; 14@property (nonatomic, strong) UIButton *btnBack; 15@property (nonatomic, strong) UILabel *lblUrl; 16@property (nonatomic, strong) UILabel *lblErrorDetail; 17@property (nonatomic, strong) UIScrollView *vContainer; 18 19- (void)_onTapRetry; 20 21@end 22 23@implementation EXErrorView 24 25- (instancetype)initWithFrame:(CGRect)frame 26{ 27 if (self = [super initWithFrame:frame]) { 28 self.backgroundColor = [UIColor whiteColor]; 29 30 self.vContainer = [[UIScrollView alloc] init]; 31 [self addSubview:_vContainer]; 32 33 // error description label 34 self.lblError = [[UILabel alloc] init]; 35 _lblError.numberOfLines = 0; 36 _lblError.textAlignment = NSTextAlignmentCenter; 37 _lblError.font = [UIFont systemFontOfSize:14.0f]; 38 _lblError.textColor = [UIColor blackColor]; 39 [_vContainer addSubview:_lblError]; 40 41 // retry button 42 self.btnRetry = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 43 [_btnRetry setTitle:@"Try again" forState:UIControlStateNormal]; 44 [_btnRetry addTarget:self action:@selector(_onTapRetry) forControlEvents:UIControlEventTouchUpInside]; 45 [_vContainer addSubview:_btnRetry]; 46 47 // back button 48 self.btnBack = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 49 [_btnBack setTitle:@"Go back to Expo Home" forState:UIControlStateNormal]; 50 [_btnBack addTarget:self action:@selector(_onTapBack) forControlEvents:UIControlEventTouchUpInside]; 51 [_vContainer addSubview:_btnBack]; 52 53 for (UIButton *btnToStyle in @[ _btnRetry, _btnBack ]) { 54 [btnToStyle setTintColor:[EXUtil colorWithRGB:0x49a7e8]]; 55 [btnToStyle.titleLabel setFont:[UIFont boldSystemFontOfSize:14.0f]]; 56 } 57 58 // url label 59 self.lblUrl = [[UILabel alloc] init]; 60 _lblUrl.numberOfLines = 0; 61 _lblUrl.textAlignment = NSTextAlignmentCenter; 62 [_vContainer addSubview:_lblUrl]; 63 64 // error detail label 65 self.lblErrorDetail = [[UILabel alloc] init]; 66 _lblErrorDetail.numberOfLines = 0; 67 _lblErrorDetail.textAlignment = NSTextAlignmentCenter; 68 _lblErrorDetail.textColor = [UIColor darkGrayColor]; 69 [_vContainer addSubview:_lblErrorDetail]; 70 71 for (UILabel *lblToStyle in @[ _lblUrl, _lblErrorDetail ]) { 72 lblToStyle.font = [UIFont systemFontOfSize:14.0f]; 73 } 74 } 75 return self; 76} 77 78- (void)setType:(EXFatalErrorType)type 79{ 80 _type = type; 81 NSString *appOwnerName = @"the requested app"; 82 if (_appRecord) { 83 if (_appRecord == [EXKernel sharedInstance].appRegistry.homeAppRecord) { 84 appOwnerName = @"Expo"; 85 } else if (_appRecord.appLoader.manifest && _appRecord.appLoader.manifest.name) { 86 appOwnerName = [NSString stringWithFormat:@"\"%@\"", _appRecord.appLoader.manifest.name]; 87 } 88 } 89 90 switch (type) { 91 case kEXFatalErrorTypeLoading: { 92 _lblError.text = [NSString stringWithFormat:@"There was a problem loading %@.", appOwnerName]; 93 if (_error.code == kCFURLErrorNotConnectedToInternet) { 94 _lblError.text = [NSString stringWithFormat:@"%@ Make sure you're connected to the internet.", _lblError.text]; 95 } else if (_appRecord.appLoader.manifestUrl) { 96 NSString *url = _appRecord.appLoader.manifestUrl.absoluteString; 97 if ([self _urlLooksLikeLAN:url]) { 98 NSString *extraLANPermissionText = @""; 99 if (@available(iOS 14, *)) { 100 extraLANPermissionText = @", and that you have granted Expo Go the Local Network permission in the Settings app,"; 101 } 102 _lblError.text = [NSString stringWithFormat: 103 @"%@\n\nIt looks like you may be using a LAN URL. " 104 "Make sure your device is on the same network as the server%@ or try using the tunnel connection type.", _lblError.text, extraLANPermissionText]; 105 } 106 } 107 break; 108 } 109 case kEXFatalErrorTypeException: { 110 _lblError.text = [NSString stringWithFormat:@"There was a problem running %@.", appOwnerName]; 111 break; 112 } 113 } 114 [self _resetUIState]; 115} 116 117- (void)setError:(NSError *)error 118{ 119 _error = error; 120 _lblErrorDetail.text = [error localizedDescription]; 121 [self _resetUIState]; 122} 123 124- (void)setAppRecord:(EXKernelAppRecord *)appRecord 125{ 126 _appRecord = appRecord; 127 [self _resetUIState]; 128} 129 130- (void)layoutSubviews 131{ 132 [super layoutSubviews]; 133 134 _vContainer.frame = self.bounds; 135 CGFloat maxLabelWidth = self.bounds.size.width - 32.0f; 136 137 _lblError.frame = CGRectMake(0, 0, maxLabelWidth, CGFLOAT_MAX); 138 [_lblError sizeToFit]; 139 _lblError.center = CGPointMake(self.bounds.size.width * 0.5f, self.bounds.size.height * 0.25f); 140 141 _btnRetry.frame = CGRectMake(0, 0, self.bounds.size.width, 24.0f); 142 _btnRetry.center = CGPointMake(_lblError.center.x, CGRectGetMaxY(_lblError.frame) + 32.0f); 143 144 _btnBack.frame = CGRectMake(0, 0, self.bounds.size.width, 24.0f); 145 _btnBack.center = CGPointMake(_lblError.center.x, CGRectGetMaxY(_btnRetry.frame) + 24); 146 147 _lblUrl.frame = CGRectMake(0, 0, self.bounds.size.width - 48.0f, CGFLOAT_MAX); 148 [_lblUrl sizeToFit]; 149 _lblUrl.center = CGPointMake(_lblError.center.x, CGRectGetMaxY(_btnBack.frame) + 12.0f + CGRectGetMidY(_lblUrl.bounds)); 150 151 _lblErrorDetail.frame = CGRectMake(0, 0, maxLabelWidth, CGFLOAT_MAX); 152 [_lblErrorDetail sizeToFit]; 153 154 _lblErrorDetail.center = CGPointMake(_lblError.center.x, CGRectGetMaxY(_lblUrl.frame) + 24.0f + CGRectGetMidY(_lblErrorDetail.bounds)); 155 156 _vContainer.contentSize = CGSizeMake(_vContainer.bounds.size.width, CGRectGetMaxY(_lblErrorDetail.frame) + 12.0f); 157} 158 159#pragma mark - Internal 160 161- (void)_resetUIState 162{ 163 EXKernelAppRecord *homeRecord = [EXKernel sharedInstance].appRegistry.homeAppRecord; 164 _btnBack.hidden = (!homeRecord || _appRecord == homeRecord); 165 _lblUrl.hidden = (!homeRecord && ![self _isDevDetached]); 166 _lblUrl.text = _appRecord.appLoader.manifestUrl.absoluteString; 167 // TODO: maybe hide retry (see BrowserErrorView) 168 [self setNeedsLayout]; 169} 170 171- (void)_onTapRetry 172{ 173 if (_delegate) { 174 [_delegate errorViewDidSelectRetry:self]; 175 } 176} 177 178- (void)_onTapBack 179{ 180 if ([EXKernel sharedInstance].browserController) { 181 [[EXKernel sharedInstance].browserController moveHomeToVisible]; 182 } 183} 184 185- (BOOL)_urlLooksLikeLAN:(NSString *)url 186{ 187 return ( 188 url && ( 189 [url rangeOfString:@".local"].length > 0 || 190 [url rangeOfString:@"192."].length > 0 || 191 [url rangeOfString:@"10."].length > 0 || 192 [url rangeOfString:@"172."].length > 0 193 ) 194 ); 195} 196 197- (BOOL)_isDevDetached 198{ 199 return [EXEnvironment sharedEnvironment].isDetached && [EXEnvironment sharedEnvironment].isDebugXCodeScheme; 200} 201 202@end 203