| Index: remoting/ios/app/client_connection_view_controller.mm
|
| diff --git a/remoting/ios/app/client_connection_view_controller.mm b/remoting/ios/app/client_connection_view_controller.mm
|
| index 888ee2c358ea998c6de2cf1d4f89f90508397ad5..8e4c8a57a73bdf2a44f47a123ca6a47c1cfbbe79 100644
|
| --- a/remoting/ios/app/client_connection_view_controller.mm
|
| +++ b/remoting/ios/app/client_connection_view_controller.mm
|
| @@ -12,9 +12,11 @@
|
| #import "ios/third_party/material_components_ios/src/components/ActivityIndicator/src/MDCActivityIndicator.h"
|
| #import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
|
| #import "ios/third_party/material_components_ios/src/components/NavigationBar/src/MaterialNavigationBar.h"
|
| +#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
|
| #import "remoting/ios/app/host_view_controller.h"
|
| #import "remoting/ios/app/pin_entry_view.h"
|
| #import "remoting/ios/app/remoting_theme.h"
|
| +#import "remoting/ios/app/session_reconnect_view.h"
|
| #import "remoting/ios/domain/client_session_details.h"
|
| #import "remoting/ios/domain/host_info.h"
|
| #import "remoting/ios/facade/remoting_authentication.h"
|
| @@ -31,7 +33,10 @@ static const CGFloat kActivityIndicatorRadius = 33.f;
|
| static const CGFloat kPinEntryViewWidth = 240.f;
|
| static const CGFloat kPinEntryViewHeight = 90.f;
|
|
|
| -static const CGFloat kCenterShift = -80.f;
|
| +static const CGFloat kReconnectViewWidth = 120.f;
|
| +static const CGFloat kReconnectViewHeight = 90.f;
|
| +
|
| +static const CGFloat kTopPadding = 240.f;
|
| static const CGFloat kPadding = 20.f;
|
| static const CGFloat kMargin = 20.f;
|
|
|
| @@ -39,14 +44,20 @@ static const CGFloat kBarHeight = 58.f;
|
|
|
| static const CGFloat kKeyboardAnimationTime = 0.3;
|
|
|
| -@interface ClientConnectionViewController ()<PinEntryDelegate> {
|
| +@interface ClientConnectionViewController ()<PinEntryDelegate,
|
| + SessionReconnectViewDelegate> {
|
| UIImageView* _iconView;
|
| MDCActivityIndicator* _activityIndicator;
|
| + NSLayoutConstraint* _activityIndicatorTopConstraintFull;
|
| + NSLayoutConstraint* _activityIndicatorTopConstraintKeyboard;
|
| UILabel* _statusLabel;
|
| MDCNavigationBar* _navBar;
|
| PinEntryView* _pinEntryView;
|
| + SessionReconnectView* _reconnectView;
|
| NSString* _remoteHostName;
|
| RemotingClient* _client;
|
| + SessionErrorCode _lastError;
|
| + HostInfo* _hostInfo;
|
| }
|
| @end
|
|
|
| @@ -57,17 +68,7 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| - (instancetype)initWithHostInfo:(HostInfo*)hostInfo {
|
| self = [super init];
|
| if (self) {
|
| - _client = [[RemotingClient alloc] init];
|
| -
|
| - __weak RemotingClient* weakClient = _client;
|
| - [RemotingService.instance.authentication
|
| - callbackWithAccessToken:^(RemotingAuthenticationStatus status,
|
| - NSString* userEmail, NSString* accessToken) {
|
| - [weakClient connectToHost:hostInfo
|
| - username:userEmail
|
| - accessToken:accessToken];
|
| - }];
|
| -
|
| + _hostInfo = hostInfo;
|
| _remoteHostName = hostInfo.hostName;
|
|
|
| // TODO(yuweih): This logic may be reused by other views.
|
| @@ -102,73 +103,161 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| constraintEqualToAnchor:[self.view trailingAnchor]],
|
| [[_navBar heightAnchor] constraintEqualToConstant:kBarHeight],
|
| ]];
|
| +
|
| + [self attemptConnectionToHost];
|
| }
|
| return self;
|
| }
|
|
|
| #pragma mark - UIViewController
|
|
|
| -- (void)loadView {
|
| - [super loadView];
|
| -
|
| +- (void)viewDidLoad {
|
| + [super viewDidLoad];
|
| self.view.backgroundColor = RemotingTheme.connectionViewBackgroundColor;
|
|
|
| _activityIndicator = [[MDCActivityIndicator alloc] initWithFrame:CGRectZero];
|
| + _activityIndicator.radius = kActivityIndicatorRadius;
|
| + _activityIndicator.trackEnabled = YES;
|
| + _activityIndicator.strokeWidth = kActivityIndicatorStrokeWidth;
|
| + _activityIndicator.cycleColors = @[ UIColor.whiteColor ];
|
| + _activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
|
| [self.view addSubview:_activityIndicator];
|
|
|
| _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero];
|
| + _statusLabel.numberOfLines = 1;
|
| + _statusLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
| + _statusLabel.textColor = [UIColor whiteColor];
|
| + _statusLabel.textAlignment = NSTextAlignmentCenter;
|
| + _statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
|
| [self.view addSubview:_statusLabel];
|
|
|
| _iconView = [[UIImageView alloc] initWithFrame:CGRectZero];
|
| - [self.view addSubview:_iconView];
|
| -
|
| - _pinEntryView = [[PinEntryView alloc] init];
|
| - [self.view addSubview:_pinEntryView];
|
| - _pinEntryView.delegate = self;
|
| -}
|
| -
|
| -- (void)viewDidLoad {
|
| - [super viewDidLoad];
|
| -
|
| _iconView.contentMode = UIViewContentModeCenter;
|
| _iconView.alpha = 0.87f;
|
| _iconView.backgroundColor = RemotingTheme.onlineHostColor;
|
| _iconView.layer.cornerRadius = kIconRadius;
|
| _iconView.layer.masksToBounds = YES;
|
| _iconView.image = RemotingTheme.desktopIcon;
|
| + _iconView.translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self.view addSubview:_iconView];
|
|
|
| - _activityIndicator.radius = kActivityIndicatorRadius;
|
| - _activityIndicator.trackEnabled = YES;
|
| - _activityIndicator.strokeWidth = kActivityIndicatorStrokeWidth;
|
| - _activityIndicator.cycleColors = @[ UIColor.whiteColor ];
|
| -
|
| - _statusLabel.numberOfLines = 1;
|
| - _statusLabel.lineBreakMode = NSLineBreakByTruncatingTail;
|
| - _statusLabel.textColor = [UIColor whiteColor];
|
| - _statusLabel.textAlignment = NSTextAlignmentCenter;
|
| + _reconnectView = [[SessionReconnectView alloc] initWithFrame:CGRectZero];
|
| + _reconnectView.hidden = YES;
|
| + _reconnectView.translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self.view addSubview:_reconnectView];
|
| + _reconnectView.delegate = self;
|
|
|
| + _pinEntryView = [[PinEntryView alloc] init];
|
| _pinEntryView.hidden = YES;
|
| -}
|
| -
|
| -- (void)viewWillLayoutSubviews {
|
| - [super viewWillLayoutSubviews];
|
| + _pinEntryView.translatesAutoresizingMaskIntoConstraints = NO;
|
| + [self.view addSubview:_pinEntryView];
|
| + _pinEntryView.delegate = self;
|
|
|
| - _iconView.frame = CGRectMake(0, 0, kIconRadius * 2, kIconRadius * 2.f);
|
| - _iconView.center =
|
| - CGPointMake(self.view.center.x, self.view.center.y + kCenterShift);
|
| + [self
|
| + initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings(
|
| + _activityIndicator, _statusLabel,
|
| + _iconView, _reconnectView,
|
| + _pinEntryView)];
|
| +}
|
|
|
| +- (void)initializeLayoutConstraintsWithViews:(NSDictionary*)views {
|
| + // Metrics to use in visual format strings.
|
| + NSDictionary* layoutMetrics = @{
|
| + @"padding" : @(kPadding),
|
| + @"margin" : @(kMargin),
|
| + @"topPadding" : @(kTopPadding),
|
| + @"iconDiameter" : @(kIconRadius * 2),
|
| + @"pinEntryViewWidth" : @(kPinEntryViewWidth),
|
| + @"pinEntryViewHeight" : @(kPinEntryViewHeight),
|
| + @"reconnectViewWidth" : @(kReconnectViewWidth),
|
| + @"reconnectViewHeight" : @(kReconnectViewHeight),
|
| + };
|
| [_activityIndicator sizeToFit];
|
| - _activityIndicator.center = _iconView.center;
|
| -
|
| - _statusLabel.frame =
|
| - CGRectMake(kMargin, _activityIndicator.center.y + kIconRadius + kPadding,
|
| - self.view.frame.size.width - kMargin * 2.f,
|
| - _statusLabel.font.pointSize * _statusLabel.numberOfLines);
|
| -
|
| - _pinEntryView.frame = CGRectMake(
|
| - (self.view.frame.size.width - kPinEntryViewWidth) / 2.f,
|
| - _statusLabel.frame.origin.y + _statusLabel.frame.size.height + kPadding,
|
| - kPinEntryViewWidth, kPinEntryViewHeight);
|
| + NSString* f;
|
| +
|
| + // Horizontal constraints:
|
| +
|
| + [self.view addConstraints:
|
| + [NSLayoutConstraint
|
| + constraintsWithVisualFormat:@"H:[_iconView(iconDiameter)]"
|
| + options:0
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + [self.view addConstraints:[NSLayoutConstraint
|
| + constraintsWithVisualFormat:
|
| + @"H:|-margin-[_statusLabel]-margin-|"
|
| + options:0
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + [self.view addConstraints:[NSLayoutConstraint
|
| + constraintsWithVisualFormat:
|
| + @"H:[_pinEntryView(pinEntryViewWidth)]"
|
| + options:0
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + [self.view addConstraints:[NSLayoutConstraint
|
| + constraintsWithVisualFormat:
|
| + @"H:[_reconnectView(reconnectViewWidth)]"
|
| + options:0
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + // Anchors:
|
| +
|
| + _activityIndicatorTopConstraintFull =
|
| + [_activityIndicator.topAnchor constraintEqualToAnchor:self.view.topAnchor
|
| + constant:kTopPadding];
|
| + _activityIndicatorTopConstraintFull.active = YES;
|
| +
|
| + [_iconView.centerYAnchor
|
| + constraintEqualToAnchor:_activityIndicator.centerYAnchor]
|
| + .active = YES;
|
| +
|
| + // Vertical constraints:
|
| +
|
| + [self.view addConstraints:
|
| + [NSLayoutConstraint
|
| + constraintsWithVisualFormat:@"V:[_iconView(iconDiameter)]"
|
| + options:0
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + [self.view addConstraints:
|
| + [NSLayoutConstraint
|
| + constraintsWithVisualFormat:
|
| + @"V:[_activityIndicator]-(padding)-[_statusLabel]"
|
| + options:NSLayoutFormatAlignAllCenterX
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + [self.view addConstraints:
|
| + [NSLayoutConstraint
|
| + constraintsWithVisualFormat:
|
| + @"V:[_iconView]-(padding)-[_statusLabel]"
|
| + options:NSLayoutFormatAlignAllCenterX
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + f = @"V:[_statusLabel]-(padding)-[_pinEntryView(pinEntryViewHeight)]";
|
| + [self.view addConstraints:
|
| + [NSLayoutConstraint
|
| + constraintsWithVisualFormat:f
|
| + options:NSLayoutFormatAlignAllCenterX
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + f = @"V:[_statusLabel]-padding-[_reconnectView(reconnectViewHeight)]";
|
| + [self.view addConstraints:
|
| + [NSLayoutConstraint
|
| + constraintsWithVisualFormat:f
|
| + options:NSLayoutFormatAlignAllCenterX
|
| + metrics:layoutMetrics
|
| + views:views]];
|
| +
|
| + [self.view setNeedsUpdateConstraints];
|
| }
|
|
|
| - (void)viewWillAppear:(BOOL)animated {
|
| @@ -217,29 +306,30 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| CGRectValue]
|
| .size;
|
|
|
| - [UIView
|
| - animateWithDuration:kKeyboardAnimationTime
|
| - animations:^{
|
| - CGRect f = self.view.frame;
|
| - CGFloat newHeight =
|
| - self.view.frame.size.height - keyboardSize.height;
|
| - CGFloat overlap =
|
| - newHeight - (_pinEntryView.frame.origin.y +
|
| - _pinEntryView.frame.size.height + kPadding);
|
| - if (overlap < 0) {
|
| - f.origin.y = overlap;
|
| - // TODO(yuweih): This may push the navigation bar off screen.
|
| - self.view.frame = f;
|
| - }
|
| - }];
|
| + CGFloat newHeight = self.view.frame.size.height - keyboardSize.height;
|
| + CGFloat overlap = newHeight - (_pinEntryView.frame.origin.y +
|
| + _pinEntryView.frame.size.height + kPadding);
|
| + if (overlap > 0) {
|
| + overlap = 0;
|
| + }
|
| + _activityIndicatorTopConstraintKeyboard.active = NO;
|
| + _activityIndicatorTopConstraintKeyboard = [_activityIndicator.topAnchor
|
| + constraintEqualToAnchor:self.view.topAnchor
|
| + constant:kTopPadding + overlap];
|
| + _activityIndicatorTopConstraintFull.active = NO;
|
| + _activityIndicatorTopConstraintKeyboard.active = YES;
|
| + [UIView animateWithDuration:kKeyboardAnimationTime
|
| + animations:^{
|
| + [self.view layoutIfNeeded];
|
| + }];
|
| }
|
|
|
| - (void)keyboardWillHide:(NSNotification*)notification {
|
| + _activityIndicatorTopConstraintKeyboard.active = NO;
|
| + _activityIndicatorTopConstraintFull.active = YES;
|
| [UIView animateWithDuration:kKeyboardAnimationTime
|
| animations:^{
|
| - CGRect f = self.view.frame;
|
| - f.origin.y = 0.f;
|
| - self.view.frame = f;
|
| + [self.view layoutIfNeeded];
|
| }];
|
| }
|
|
|
| @@ -258,13 +348,36 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| [self showConnectedState];
|
| break;
|
| case ClientViewClosed:
|
| - [self dismissViewControllerAnimated:YES completion:nil];
|
| + [self.navigationController popToRootViewControllerAnimated:YES];
|
| + break;
|
| + case ClientViewError:
|
| + [self showError];
|
| break;
|
| }
|
| }
|
|
|
| +#pragma mark - SessionReconnectViewDelegate
|
| +
|
| +- (void)didTapReconnect {
|
| + [self attemptConnectionToHost];
|
| +}
|
| +
|
| #pragma mark - Private
|
|
|
| +- (void)attemptConnectionToHost {
|
| + _client = [[RemotingClient alloc] init];
|
| + __weak RemotingClient* weakClient = _client;
|
| + __weak HostInfo* weakHostInfo = _hostInfo;
|
| + [RemotingService.instance.authentication
|
| + callbackWithAccessToken:^(RemotingAuthenticationStatus status,
|
| + NSString* userEmail, NSString* accessToken) {
|
| + [weakClient connectToHost:weakHostInfo
|
| + username:userEmail
|
| + accessToken:accessToken];
|
| + }];
|
| + [self setState:ClientViewConnecting];
|
| +}
|
| +
|
| - (void)showConnectingState {
|
| [_pinEntryView endEditing:YES];
|
| _statusLabel.text =
|
| @@ -274,6 +387,7 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| _activityIndicator.indicatorMode = MDCActivityIndicatorModeIndeterminate;
|
| _activityIndicator.hidden = NO;
|
| _pinEntryView.hidden = YES;
|
| + _reconnectView.hidden = YES;
|
| [_activityIndicator startAnimating];
|
| }
|
|
|
| @@ -282,6 +396,7 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| [_activityIndicator stopAnimating];
|
| _activityIndicator.hidden = YES;
|
| _pinEntryView.hidden = NO;
|
| + _reconnectView.hidden = YES;
|
|
|
| // TODO(yuweih): This may be called before viewDidAppear and miss the keyboard
|
| // callback.
|
| @@ -299,6 +414,7 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| _activityIndicator.cycleColors = @[ [UIColor greenColor] ];
|
| [_activityIndicator startAnimating];
|
| _activityIndicator.progress = 1.0;
|
| + _reconnectView.hidden = YES;
|
|
|
| HostViewController* hostViewController =
|
| [[HostViewController alloc] initWithClient:_client];
|
| @@ -312,6 +428,77 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| [self.navigationController setViewControllers:controllers animated:NO];
|
| }
|
|
|
| +- (void)showError {
|
| + _statusLabel.text =
|
| + [NSString stringWithFormat:@"Error connecting to %@", _remoteHostName];
|
| + _activityIndicator.progress = 0.0;
|
| + _pinEntryView.hidden = YES;
|
| + _activityIndicator.hidden = NO;
|
| + _activityIndicator.indicatorMode = MDCActivityIndicatorModeDeterminate;
|
| + _activityIndicator.cycleColors = @[ [UIColor redColor] ];
|
| + [_activityIndicator startAnimating];
|
| + _activityIndicator.progress = 1.0;
|
| + _reconnectView.hidden = NO;
|
| +
|
| + MDCSnackbarMessage* message = nil;
|
| + switch (_lastError) {
|
| + case SessionErrorOk:
|
| + // Do nothing.
|
| + break;
|
| + case SessionErrorPeerIsOffline:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorPeerIsOffline."];
|
| + break;
|
| + case SessionErrorSessionRejected:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorSessionRejected."];
|
| + break;
|
| + case SessionErrorIncompatibleProtocol:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorIncompatibleProtocol."];
|
| + break;
|
| + case SessionErrorAuthenticationFailed:
|
| + message = [MDCSnackbarMessage messageWithText:@"Error: Invalid Pin."];
|
| + [_pinEntryView clearPinEntry];
|
| + break;
|
| + case SessionErrorInvalidAccount:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorInvalidAccount."];
|
| + break;
|
| + case SessionErrorChannelConnectionError:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorChannelConnectionError."];
|
| + break;
|
| + case SessionErrorSignalingError:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorSignalingError."];
|
| + break;
|
| + case SessionErrorSignalingTimeout:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorSignalingTimeout."];
|
| + break;
|
| + case SessionErrorHostOverload:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorHostOverload."];
|
| + break;
|
| + case SessionErrorMaxSessionLength:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorMaxSessionLength."];
|
| + break;
|
| + case SessionErrorHostConfigurationError:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorHostConfigurationError."];
|
| + break;
|
| + case SessionErrorUnknownError:
|
| + message = [MDCSnackbarMessage
|
| + messageWithText:@"Error: SessionErrorUnknownError."];
|
| + break;
|
| + }
|
| + if (message.text) {
|
| + [MDCSnackbarManager showMessage:message];
|
| + }
|
| +}
|
| +
|
| - (void)didProvidePin:(NSString*)pin createPairing:(BOOL)createPairing {
|
| // TODO(nicholss): There is an open question if createPairing is supported on
|
| // iOS. Need to fingure this out.
|
| @@ -347,7 +534,8 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| state = ClientViewConnected;
|
| break;
|
| case SessionFailed:
|
| - // TODO(nicholss): Implement an error screen.
|
| + state = ClientViewError;
|
| + break;
|
| case SessionClosed:
|
| state = ClientViewClosed;
|
| break;
|
| @@ -355,6 +543,7 @@ static const CGFloat kKeyboardAnimationTime = 0.3;
|
| LOG(ERROR) << "Unknown State for Session, " << sessionDetails.state;
|
| return;
|
| }
|
| + _lastError = sessionDetails.error;
|
| [[NSOperationQueue mainQueue] addOperationWithBlock:^{
|
| [self setState:state];
|
| }];
|
|
|