Commit f5407a18 authored by nicholss's avatar nicholss Committed by Commit Bot

Adding error handling to the connection flow.

Adding a reconnect button and generic error display for reconnection flow.
Adding some logic around enabling the connect button based on pin length.
Adding a red circle to the error state view in connection flow.

Review-Url: https://codereview.chromium.org/2971903002
Cr-Commit-Position: refs/heads/master@{#484967}
parent c04dbb71
......@@ -46,6 +46,8 @@ source_set("common_source_set") {
"remoting_theme.mm",
"remoting_view_controller.h",
"remoting_view_controller.mm",
"session_reconnect_view.h",
"session_reconnect_view.mm",
]
deps = [
......
......@@ -15,6 +15,7 @@ typedef NS_ENUM(NSInteger, ClientConnectionViewState) {
ClientViewPinPrompt,
ClientViewConnected,
ClientViewClosed,
ClientViewError,
};
// This is the view that shows the user feedback while the client connection is
......
......@@ -20,6 +20,9 @@
// passcode.
@interface PinEntryView : UIView
// Clears the pin entry view.
- (void)clearPinEntry;
// This delegate will handle interactions on the cells in the collection.
@property(weak, nonatomic) id<PinEntryDelegate> delegate;
......
......@@ -12,10 +12,12 @@
#import "remoting/ios/app/remoting_theme.h"
static const CGFloat kMargin = 5.f;
static const CGFloat kPadding = 6.f;
static const CGFloat kPadding = 8.f;
static const CGFloat kLineSpace = 12.f;
@interface PinEntryView () {
static const int kMinPinLength = 6;
@interface PinEntryView ()<UITextFieldDelegate> {
UISwitch* _pairingSwitch;
UILabel* _pairingLabel;
MDCFloatingButton* _pinButton;
......@@ -36,6 +38,7 @@ static const CGFloat kLineSpace = 12.f;
_pairingSwitch.tintColor =
[UIColor colorWithRed:1.f green:1.f blue:1.f alpha:0.5];
_pairingSwitch.transform = CGAffineTransformMakeScale(0.5, 0.5);
_pairingSwitch.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_pairingSwitch];
_pairingLabel = [[UILabel alloc] init];
......@@ -43,6 +46,7 @@ static const CGFloat kLineSpace = 12.f;
[UIColor colorWithRed:1.f green:1.f blue:1.f alpha:0.5];
_pairingLabel.font = [UIFont systemFontOfSize:12.f];
_pairingLabel.text = @"Remember my PIN on this device.";
_pairingLabel.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_pairingLabel];
_pinButton =
......@@ -52,6 +56,8 @@ static const CGFloat kLineSpace = 12.f;
action:@selector(didTapPinEntry:)
forControlEvents:UIControlEventTouchUpInside];
_pinButton.translatesAutoresizingMaskIntoConstraints = NO;
_pinButton.enabled = NO;
_pinButton.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_pinButton];
_pinEntry = [[UITextField alloc] init];
......@@ -65,11 +71,51 @@ static const CGFloat kLineSpace = 12.f;
NSForegroundColorAttributeName :
[UIColor colorWithRed:1.f green:1.f blue:1.f alpha:0.5]
}];
_pinEntry.translatesAutoresizingMaskIntoConstraints = NO;
_pinEntry.delegate = self;
[self addSubview:_pinEntry];
[self
initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings(
_pairingSwitch, _pairingLabel,
_pinButton, _pinEntry)];
}
return self;
}
- (void)initializeLayoutConstraintsWithViews:(NSDictionary*)views {
// Metrics to use in visual format strings.
NSDictionary* layoutMetrics = @{
@"margin" : @(kMargin),
@"padding" : @(kPadding),
@"lineSpace" : @(kLineSpace),
};
[self addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:
@"H:|-[_pinEntry]-(padding)-[_pinButton]-|"
options:NSLayoutFormatAlignAllCenterY
metrics:layoutMetrics
views:views]];
[self addConstraints:
[NSLayoutConstraint
constraintsWithVisualFormat:
@"H:|-[_pairingSwitch]-(padding)-[_pairingLabel]-|"
options:NSLayoutFormatAlignAllCenterY
metrics:layoutMetrics
views:views]];
[self addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:
@"V:|-[_pinButton]-(lineSpace)-[_pairingSwitch]"
options:0
metrics:layoutMetrics
views:views]];
[self setNeedsUpdateConstraints];
}
#pragma mark - UIView
- (BOOL)canBecomeFirstResponder {
......@@ -84,31 +130,32 @@ static const CGFloat kLineSpace = 12.f;
return [_pinEntry endEditing:force];
}
- (void)layoutSubviews {
[super layoutSubviews];
#pragma mark - UITextFieldDelegate
[_pinButton sizeToFit];
CGFloat buttonSize = _pinButton.frame.size.width; // Assume circle.
_pinEntry.frame =
CGRectMake(kMargin, 0.f,
self.frame.size.width - kPadding - kMargin * 2.f - buttonSize,
buttonSize);
- (BOOL)textField:(UITextField*)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString*)string {
if (textField == _pinEntry) {
NSUInteger length = _pinEntry.text.length - range.length + string.length;
_pinButton.enabled = length >= kMinPinLength;
}
return YES;
}
[_pinButton sizeToFit];
_pinButton.frame =
CGRectMake(self.frame.size.width - kPadding - kMargin - buttonSize, 0.f,
buttonSize, buttonSize);
- (BOOL)textFieldShouldReturn:(UITextField*)textField {
NSLog(@"textFieldShouldReturn");
if ([_pinButton isEnabled]) {
[self didTapPinEntry:textField];
return YES;
}
return NO;
}
[_pairingSwitch sizeToFit];
_pairingSwitch.center = CGPointMake(
kMargin + _pairingSwitch.frame.size.width / 2.f,
buttonSize + _pairingSwitch.frame.size.height / 2.f + kLineSpace);
#pragma mark - Public
_pairingLabel.frame =
CGRectMake(kMargin + _pairingSwitch.frame.size.width + kPadding,
buttonSize + kLineSpace, 0.f, 0.f);
[_pairingLabel sizeToFit];
- (void)clearPinEntry {
_pinEntry.text = @"";
_pinButton.enabled = NO;
}
#pragma mark - Private
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef REMOTING_IOS_SESSON_RECONNECT_VIEW_H_
#define REMOTING_IOS_SESSON_RECONNECT_VIEW_H_
#import <UIKit/UIKit.h>
@protocol SessionReconnectViewDelegate<NSObject>
// Notifies the delegate that the user tapped the reconnect button.
@optional
- (void)didTapReconnect;
@end
// This view is the container for a session connection error. It will display a
// reconnect button.
@interface SessionReconnectView : UIView
// This delegate will handle interactions on the view.
@property(weak, nonatomic) id<SessionReconnectViewDelegate> delegate;
@end
#endif // REMOTING_IOS_SESSON_RECONNECT_VIEW_H_
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "remoting/ios/app/session_reconnect_view.h"
#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
#import "remoting/ios/app/remoting_theme.h"
static const CGFloat kReconnectButtonWidth = 120.f;
static const CGFloat kReconnectButtonHeight = 30.f;
@interface SessionReconnectView () {
MDCRaisedButton* _reconnectButton;
}
@end
@implementation SessionReconnectView
@synthesize delegate = _delegate;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor clearColor];
_reconnectButton = [[MDCRaisedButton alloc] init];
[_reconnectButton setElevation:4.0f forState:UIControlStateNormal];
[_reconnectButton setTitle:@"Reconnect" forState:UIControlStateNormal];
[_reconnectButton addTarget:self
action:@selector(didTapReconnect:)
forControlEvents:UIControlEventTouchUpInside];
_reconnectButton.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:_reconnectButton];
[self initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings(
_reconnectButton)];
}
return self;
}
- (void)initializeLayoutConstraintsWithViews:(NSDictionary*)views {
NSMutableArray* layoutConstraints = [NSMutableArray array];
[layoutConstraints addObject:[_reconnectButton.centerYAnchor
constraintEqualToAnchor:self.centerYAnchor]];
[layoutConstraints addObject:[_reconnectButton.centerXAnchor
constraintEqualToAnchor:self.centerXAnchor]];
[layoutConstraints
addObject:[_reconnectButton.widthAnchor
constraintEqualToConstant:kReconnectButtonWidth]];
[layoutConstraints
addObject:[_reconnectButton.heightAnchor
constraintEqualToConstant:kReconnectButtonHeight]];
[NSLayoutConstraint activateConstraints:layoutConstraints];
[self setNeedsUpdateConstraints];
}
#pragma mark - Private
- (void)didTapReconnect:(id)sender {
if ([_delegate respondsToSelector:@selector(didTapReconnect)]) {
[_delegate didTapReconnect];
}
}
@end
......@@ -22,6 +22,23 @@ typedef NS_ENUM(NSInteger, SessionState) {
SessionClosed,
};
// Session states that map to |remoting::protocol::ConnectionToHost::Error|.
typedef NS_ENUM(NSInteger, SessionErrorCode) {
SessionErrorOk = 0,
SessionErrorPeerIsOffline,
SessionErrorSessionRejected,
SessionErrorIncompatibleProtocol,
SessionErrorAuthenticationFailed,
SessionErrorInvalidAccount,
SessionErrorChannelConnectionError,
SessionErrorSignalingError,
SessionErrorSignalingTimeout,
SessionErrorHostOverload,
SessionErrorMaxSessionLength,
SessionErrorHostConfigurationError,
SessionErrorUnknownError,
};
// The current state of a session and data needed for session context.
@interface ClientSessionDetails : NSObject
......@@ -29,6 +46,8 @@ typedef NS_ENUM(NSInteger, SessionState) {
@property(nonatomic) HostInfo* hostInfo;
// The current state of the session.
@property(nonatomic, assign) SessionState state;
// The error assoiciated to the current state.
@property(nonatomic, assign) SessionErrorCode error;
@end
......
......@@ -14,6 +14,7 @@
@synthesize hostInfo = _hostInfo;
@synthesize state = _state;
@synthesize error = _error;
- (NSString*)description {
return
......
......@@ -192,34 +192,68 @@ NSString* const kHostSessionPin = @"kHostSessionPin";
error:(remoting::protocol::ErrorCode)error {
switch (state) {
case remoting::protocol::ConnectionToHost::INITIALIZING:
NSLog(@"State --> INITIALIZING");
_sessionDetails.state = SessionInitializing;
break;
case remoting::protocol::ConnectionToHost::CONNECTING:
NSLog(@"State --> CONNECTING");
_sessionDetails.state = SessionConnecting;
break;
case remoting::protocol::ConnectionToHost::AUTHENTICATED:
NSLog(@"State --> AUTHENTICATED");
_sessionDetails.state = SessionAuthenticated;
break;
case remoting::protocol::ConnectionToHost::CONNECTED:
NSLog(@"State --> CONNECTED");
_sessionDetails.state = SessionConnected;
break;
case remoting::protocol::ConnectionToHost::FAILED:
NSLog(@"State --> FAILED");
_sessionDetails.state = SessionFailed;
break;
case remoting::protocol::ConnectionToHost::CLOSED:
NSLog(@"State --> CLOSED");
_sessionDetails.state = SessionClosed;
break;
default:
LOG(ERROR) << "onConnectionState, unknown state: " << state;
}
// TODO(nicholss): Send along the error code when we know what to do about it.
switch (error) {
case remoting::protocol::ErrorCode::OK:
_sessionDetails.error = SessionErrorOk;
break;
case remoting::protocol::ErrorCode::PEER_IS_OFFLINE:
_sessionDetails.error = SessionErrorPeerIsOffline;
break;
case remoting::protocol::ErrorCode::SESSION_REJECTED:
_sessionDetails.error = SessionErrorSessionRejected;
break;
case remoting::protocol::ErrorCode::INCOMPATIBLE_PROTOCOL:
_sessionDetails.error = SessionErrorIncompatibleProtocol;
break;
case remoting::protocol::ErrorCode::AUTHENTICATION_FAILED:
_sessionDetails.error = SessionErrorAuthenticationFailed;
break;
case remoting::protocol::ErrorCode::INVALID_ACCOUNT:
_sessionDetails.error = SessionErrorInvalidAccount;
break;
case remoting::protocol::ErrorCode::CHANNEL_CONNECTION_ERROR:
_sessionDetails.error = SessionErrorChannelConnectionError;
break;
case remoting::protocol::ErrorCode::SIGNALING_ERROR:
_sessionDetails.error = SessionErrorSignalingError;
break;
case remoting::protocol::ErrorCode::SIGNALING_TIMEOUT:
_sessionDetails.error = SessionErrorSignalingTimeout;
break;
case remoting::protocol::ErrorCode::HOST_OVERLOAD:
_sessionDetails.error = SessionErrorHostOverload;
break;
case remoting::protocol::ErrorCode::MAX_SESSION_LENGTH:
_sessionDetails.error = SessionErrorMaxSessionLength;
break;
case remoting::protocol::ErrorCode::HOST_CONFIGURATION_ERROR:
_sessionDetails.error = SessionErrorHostConfigurationError;
case remoting::protocol::ErrorCode::UNKNOWN_ERROR:
_sessionDetails.error = SessionErrorUnknownError;
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionStatusChanged
object:self
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment