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