Commit dbd360b3 authored by Javier Ernesto Flores Robles's avatar Javier Ernesto Flores Robles Committed by Commit Bot

[iOS][MF] Animate suggestions out and in

In order to add the animations some refactoring was required.
This also updates the suggestions view and form accessory view instead
of recreating them every time new suggestions arrive.
Continues the UI layering of these views, by removing the navigator
related logic from them.
Relies on setting content insets instead of removing suggestions to
lock on the manual fill icons.
Refactors names of delegates and properties to more appropriate ones
Fixes an issue when disabled suggestions was ignored when jumping
between fields.



Bug: 905651, 907084, 845472, 905660
Change-Id: I4ceebcabadbb26e6d948c26f9691d5684a143b79
Reviewed-on: https://chromium-review.googlesource.com/c/1346833
Commit-Queue: Javier Ernesto Flores Robles <javierrobles@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#610611}
parent ffbda136
......@@ -77,9 +77,9 @@ source_set("autofill") {
source_set("autofill_shared") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"form_input_accessory_view_delegate.h",
"form_input_accessory_view_handler.h",
"form_input_accessory_view_handler.mm",
"form_input_navigator.h",
"form_input_suggestions_provider.h",
"form_suggestion_client.h",
]
......
......@@ -13,6 +13,16 @@
@protocol FormInputAccessoryConsumer<NSObject>
// Delegate used for form navigation.
@property(nonatomic, weak) id<FormInputAccessoryViewDelegate>
navigationDelegate;
// Enables or disables the next button if any.
@property(nonatomic) BOOL formInputNextButtonEnabled;
// Enables or disables the previous button if any.
@property(nonatomic) BOOL formInputPreviousButtonEnabled;
// Removes the animations on the custom keyboard view.
- (void)removeAnimationsOnKeyboardView;
......@@ -33,8 +43,6 @@
// |isHardwareKeyboard| is true if a hardware keyboard is in use.
- (void)showAccessorySuggestions:(NSArray<FormSuggestion*>*)suggestions
suggestionClient:(id<FormSuggestionClient>)suggestionClient
navigationDelegate:
(id<FormInputAccessoryViewDelegate>)navigationDelegate
isHardwareKeyboard:(BOOL)hardwareKeyboard;
@end
......
......@@ -7,13 +7,32 @@
#import <UIKit/UIKit.h>
@protocol FormInputAccessoryViewDelegate;
@class FormInputAccessoryView;
// Informs the receiver of actions in the accessory view.
@protocol FormInputAccessoryViewDelegate
- (void)formInputAccessoryViewDidTapNextButton:(FormInputAccessoryView*)sender;
- (void)formInputAccessoryViewDidTapPreviousButton:
(FormInputAccessoryView*)sender;
- (void)formInputAccessoryViewDidTapCloseButton:(FormInputAccessoryView*)sender;
@end
// Subview of the accessory view for web forms. Shows a custom view with form
// navigation controls above the keyboard. Enables input clicks by way of the
// playInputClick method.
@interface FormInputAccessoryView : UIView<UIInputViewAudioFeedback>
// The previous button if the view was set up with a navigation delegate. Nil
// otherwise.
@property(nonatomic, readonly, weak) UIButton* previousButton;
// The next button if the view was set up with a navigation delegate. Nil
// otherwise.
@property(nonatomic, readonly, weak) UIButton* nextButton;
// The leading view.
@property(nonatomic, readonly, weak) UIView* leadingView;
// Sets up the view with the given |leadingView|. Navigation controls are shown
// on the trailing side and use |delegate| for actions.
- (void)setUpWithLeadingView:(UIView*)leadingView
......
......@@ -29,6 +29,14 @@ extern CGFloat const kInputAccessoryHeight;
// Presents a view above the keyboard.
- (void)presentView:(UIView*)view;
// Frees the manual fallback icons as the first option in the suggestions bar,
// and animates any suggestion back to their original position.
- (void)unlockManualFallbackView;
// Shows the manual fallback icons as the first option in the suggestions bar,
// and locks them in that position.
- (void)lockManualFallbackView;
@end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_CONTROLLER_H_
......@@ -5,13 +5,12 @@
#ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_HANDLER_H_
#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_HANDLER_H_
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
#import "ios/chrome/browser/autofill/form_input_navigator.h"
@class JsSuggestionManager;
// This handles user actions in the default keyboard accessory view buttons.
@interface FormInputAccessoryViewHandler
: NSObject<FormInputAccessoryViewDelegate>
@interface FormInputAccessoryViewHandler : NSObject <FormInputNavigator>
// The JS manager for interacting with the underlying form.
@property(nonatomic, weak) JsSuggestionManager* JSSuggestionManager;
......
......@@ -182,7 +182,7 @@ NSArray* FindDescendantToolbarItemsForActionName(
new autofill::KeyboardAccessoryMetricsLogger());
}
#pragma mark - FormInputAccessoryViewDelegate
#pragma mark - FormInputNavigator
- (void)closeKeyboardWithButtonPress {
[self closeKeyboardLoggingButtonPressed:YES];
......
......@@ -2,13 +2,13 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_DELEGATE_H_
#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_DELEGATE_H_
#ifndef IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_NAVIGATOR_H_
#define IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_NAVIGATOR_H_
#import <Foundation/Foundation.h>
// Handles user interaction with a FormInputAccessoryView.
@protocol FormInputAccessoryViewDelegate<NSObject>
// Handles navigation in a form.
@protocol FormInputNavigator <NSObject>
// Called when the close button is pressed by the user.
- (void)closeKeyboardWithButtonPress;
......@@ -34,8 +34,8 @@
// previous element was found, and the second indicating if a next element was
// found. |completionHandler| cannot be nil.
- (void)fetchPreviousAndNextElementsPresenceWithCompletionHandler:
(void (^)(BOOL, BOOL))completionHandler;
(void (^)(BOOL, BOOL))completionHandler;
@end
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_ACCESSORY_VIEW_DELEGATE_H_
#endif // IOS_CHROME_BROWSER_AUTOFILL_FORM_INPUT_NAVIGATOR_H_
......@@ -19,7 +19,7 @@ class WebState;
} // namespace web
@class FormSuggestion;
@protocol FormInputAccessoryViewDelegate;
@protocol FormInputNavigator;
@protocol FormInputSuggestionsProvider;
// Block type to provide form suggestions asynchronously.
......@@ -31,8 +31,7 @@ typedef void (^FormSuggestionsReadyCompletion)(
@protocol FormInputSuggestionsProvider<FormSuggestionClient>
// A delegate for form navigation.
@property(nonatomic, assign) id<FormInputAccessoryViewDelegate>
accessoryViewDelegate;
@property(nonatomic, weak) id<FormInputNavigator> formInputNavigator;
// Asynchronously retrieves form suggestions from this provider for the
// specified form/field and returns it via |accessoryViewUpdateBlock|. View
......
......@@ -15,7 +15,7 @@
#import "components/autofill/ios/browser/form_suggestion_provider.h"
#include "components/autofill/ios/form_util/form_activity_params.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_controller.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
#import "ios/chrome/browser/autofill/form_input_navigator.h"
#import "ios/chrome/browser/autofill/form_input_suggestions_provider.h"
#import "ios/chrome/browser/autofill/form_suggestion_view.h"
#import "ios/chrome/browser/passwords/password_generation_utils.h"
......@@ -63,9 +63,6 @@ AutofillSuggestionState::AutofillSuggestionState(
} // namespace
@interface FormSuggestionController ()<FormInputSuggestionsProvider> {
// Form navigation delegate.
__weak id<FormInputAccessoryViewDelegate> _delegate;
// Callback to update the accessory view.
FormSuggestionsReadyCompletion accessoryViewUpdateBlock_;
......@@ -106,6 +103,8 @@ AutofillSuggestionState::AutofillSuggestionState(
__weak id<FormSuggestionProvider> _provider;
}
@synthesize formInputNavigator = _formInputNavigator;
- (instancetype)initWithWebState:(web::WebState*)webState
providers:(NSArray*)providers
JsSuggestionManager:(JsSuggestionManager*)jsSuggestionManager {
......@@ -319,20 +318,12 @@ AutofillSuggestionState::AutofillSuggestionState(
frameID:base::SysUTF8ToNSString(
_suggestionState->frame_identifier)
completionHandler:^{
[[weakSelf accessoryViewDelegate] closeKeyboardWithoutButtonPress];
[[weakSelf formInputNavigator] closeKeyboardWithoutButtonPress];
}];
}
#pragma mark FormInputSuggestionsProvider
- (id<FormInputAccessoryViewDelegate>)accessoryViewDelegate {
return _delegate;
}
- (void)setAccessoryViewDelegate:(id<FormInputAccessoryViewDelegate>)delegate {
_delegate = delegate;
}
- (void)retrieveSuggestionsForForm:(const autofill::FormActivityParams&)params
webState:(web::WebState*)webState
accessoryViewUpdateBlock:
......
......@@ -186,7 +186,6 @@ class FormSuggestionControllerTest : public PlatformTest {
[[[mock_consumer_ stub] andDo:mockShow]
showAccessorySuggestions:[OCMArg any]
suggestionClient:[OCMArg any]
navigationDelegate:[OCMArg any]
isHardwareKeyboard:NO];
// Mock restore keyboard to verify cleanup.
......
......@@ -19,10 +19,16 @@
// A view added at the end of the current suggestions.
@property(nonatomic, strong) UIView* trailingView;
// Initializes with |frame| and |client| to show |suggestions|.
- (instancetype)initWithFrame:(CGRect)frame
client:(id<FormSuggestionClient>)client
suggestions:(NSArray<FormSuggestion*>*)suggestions;
// Updates with |client| and |suggestions|.
- (void)updateClient:(id<FormSuggestionClient>)client
suggestions:(NSArray<FormSuggestion*>*)suggestions;
// Animates the content insets back to zero.
- (void)unlockTrailingView;
// Animates the content insets so the trailing view is showed as the first
// thing.
- (void)lockTrailingView;
@end
......
......@@ -10,6 +10,7 @@
#import "components/autofill/ios/browser/form_suggestion.h"
#import "ios/chrome/browser/autofill/form_suggestion_client.h"
#import "ios/chrome/browser/autofill/form_suggestion_label.h"
#include "ios/chrome/browser/ui/util/rtl_geometry.h"
#include "ios/chrome/common/ui_util/constraints_ui_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
......@@ -30,33 +31,64 @@ const CGFloat kSuggestionHorizontalMargin = 6;
@interface FormSuggestionView ()
// Creates and adds subviews.
- (void)setupSubviews;
// The FormSuggestions that are displayed by this view.
@property(nonatomic) NSArray<FormSuggestion*>* suggestions;
// The stack view with the suggestions.
@property(nonatomic) UIStackView* stackView;
// Handles user interactions.
@property(nonatomic, weak) id<FormSuggestionClient> client;
@end
@implementation FormSuggestionView {
// The FormSuggestions that are displayed by this view.
NSArray* _suggestions;
@implementation FormSuggestionView
#pragma mark - Public
// The stack view with the suggestions.
UIStackView* _stackView;
- (void)updateClient:(id<FormSuggestionClient>)client
suggestions:(NSArray<FormSuggestion*>*)suggestions {
if ([self.suggestions isEqualToArray:suggestions] &&
(self.client == client || !suggestions.count)) {
return;
}
self.client = client;
self.suggestions = [suggestions copy];
// Handles user interactions.
id<FormSuggestionClient> _client;
if (self.stackView) {
for (UIView* view in [self.stackView.arrangedSubviews copy]) {
[self.stackView removeArrangedSubview:view];
[view removeFromSuperview];
}
self.contentInset = UIEdgeInsetsZero;
[self createAndInsertArrangedSubviews];
}
}
@synthesize trailingView = _trailingView;
- (void)unlockTrailingView {
if (!self.superview) {
return;
}
[UIView animateWithDuration:0.2
animations:^{
self.contentInset = UIEdgeInsetsZero;
}];
}
- (instancetype)initWithFrame:(CGRect)frame
client:(id<FormSuggestionClient>)client
suggestions:(NSArray<FormSuggestion*>*)suggestions {
self = [super initWithFrame:frame];
if (self) {
_client = client;
_suggestions = [suggestions copy];
- (void)lockTrailingView {
if (!self.superview || !self.trailingView) {
return;
}
return self;
LayoutOffset layoutOffset = CGRectGetLeadingLayoutOffsetInBoundingRect(
self.trailingView.frame, {CGPointZero, self.contentSize});
// Because the way the scroll view is transformed for RTL, the insets don't
// need to be directed.
UIEdgeInsets lockedContentInsets = UIEdgeInsetsMake(0, -layoutOffset, 0, 0);
[UIView animateWithDuration:0.2
animations:^{
self.contentInset = lockedContentInsets;
}];
}
#pragma mark - UIView
......@@ -71,10 +103,12 @@ const CGFloat kSuggestionHorizontalMargin = 6;
#pragma mark - Helper methods
// Creates and adds subviews.
- (void)setupSubviews {
self.showsVerticalScrollIndicator = NO;
self.showsHorizontalScrollIndicator = NO;
self.canCancelContentTouches = YES;
self.alwaysBounceHorizontal = YES;
UIStackView* stackView = [[UIStackView alloc] initWithArrangedSubviews:@[]];
stackView.axis = UILayoutConstraintAxisHorizontal;
......@@ -95,7 +129,11 @@ const CGFloat kSuggestionHorizontalMargin = 6;
self.transform = CGAffineTransformMakeRotation(M_PI);
stackView.transform = CGAffineTransformMakeRotation(M_PI);
}
self.stackView = stackView;
[self createAndInsertArrangedSubviews];
}
- (void)createAndInsertArrangedSubviews {
auto setupBlock = ^(FormSuggestion* suggestion, NSUInteger idx, BOOL* stop) {
// Disable user interaction with suggestion if it is Google Pay logo.
BOOL userInteractionEnabled =
......@@ -105,9 +143,9 @@ const CGFloat kSuggestionHorizontalMargin = 6;
[[FormSuggestionLabel alloc] initWithSuggestion:suggestion
index:idx
userInteractionEnabled:userInteractionEnabled
numSuggestions:[_suggestions count]
client:_client];
[stackView addArrangedSubview:label];
numSuggestions:[self.suggestions count]
client:self.client];
[self.stackView addArrangedSubview:label];
// If first suggestion is Google Pay logo animate it below the fold.
if (idx == 0U &&
......@@ -126,17 +164,10 @@ const CGFloat kSuggestionHorizontalMargin = 6;
});
}
};
[_suggestions enumerateObjectsUsingBlock:setupBlock];
[self.suggestions enumerateObjectsUsingBlock:setupBlock];
if (self.trailingView) {
[stackView addArrangedSubview:self.trailingView];
[self.stackView addArrangedSubview:self.trailingView];
}
_stackView = stackView;
}
#pragma mark - Getters
- (NSArray*)suggestions {
return _suggestions;
}
#pragma mark - Setters
......@@ -144,6 +175,7 @@ const CGFloat kSuggestionHorizontalMargin = 6;
- (void)setTrailingView:(UIView*)subview {
if (_trailingView.superview) {
[_stackView removeArrangedSubview:_trailingView];
[_trailingView removeFromSuperview];
}
_trailingView = subview;
if (_stackView) {
......
......@@ -177,7 +177,6 @@
}
[self.childCoordinators addObject:passwordCoordinator];
[self.formInputAccessoryMediator disableSuggestions];
}
- (void)startCardsFromButton:(UIButton*)button {
......@@ -195,7 +194,6 @@
}
[self.childCoordinators addObject:cardCoordinator];
[self.formInputAccessoryMediator disableSuggestions];
}
- (void)startAddressFromButton:(UIButton*)button {
......@@ -212,7 +210,6 @@
}
[self.childCoordinators addObject:addressCoordinator];
[self.formInputAccessoryMediator disableSuggestions];
}
#pragma mark - ManualFillAccessoryViewControllerDelegate
......@@ -220,21 +217,28 @@
- (void)keyboardButtonPressed {
[self stopChildren];
[self.formInputAccessoryMediator enableSuggestions];
[self.formInputAccessoryViewController unlockManualFallbackView];
}
- (void)accountButtonPressed:(UIButton*)sender {
[self stopChildren];
[self startAddressFromButton:sender];
[self.formInputAccessoryViewController lockManualFallbackView];
[self.formInputAccessoryMediator disableSuggestions];
}
- (void)cardButtonPressed:(UIButton*)sender {
[self stopChildren];
[self startCardsFromButton:sender];
[self.formInputAccessoryViewController lockManualFallbackView];
[self.formInputAccessoryMediator disableSuggestions];
}
- (void)passwordButtonPressed:(UIButton*)sender {
[self stopChildren];
[self startPasswordsFromButton:sender];
[self.formInputAccessoryViewController lockManualFallbackView];
[self.formInputAccessoryMediator disableSuggestions];
}
#pragma mark - PasswordCoordinatorDelegate
......
......@@ -7,7 +7,7 @@
#import <Foundation/Foundation.h>
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
#import "ios/chrome/browser/autofill/form_input_navigator.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
......
......@@ -14,6 +14,7 @@
#import "components/autofill/ios/form_util/form_activity_observer_bridge.h"
#include "components/autofill/ios/form_util/form_activity_params.h"
#import "ios/chrome/browser/autofill/form_input_accessory_consumer.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_handler.h"
#import "ios/chrome/browser/autofill/form_input_suggestions_provider.h"
#import "ios/chrome/browser/autofill/form_suggestion_tab_helper.h"
......@@ -33,10 +34,11 @@
#error "This file requires ARC support."
#endif
@interface FormInputAccessoryMediator ()<FormActivityObserver,
CRWWebStateObserver,
KeyboardObserverHelperDelegate,
WebStateListObserving>
@interface FormInputAccessoryMediator () <FormActivityObserver,
FormInputAccessoryViewDelegate,
CRWWebStateObserver,
KeyboardObserverHelperDelegate,
WebStateListObserving>
// The JS manager for interacting with the underlying form.
@property(nonatomic, weak) JsSuggestionManager* JSSuggestionManager;
......@@ -100,6 +102,7 @@
self = [super init];
if (self) {
_consumer = consumer;
_consumer.navigationDelegate = self;
if (webStateList) {
_webStateList = webStateList;
_webStateListObserver =
......@@ -209,12 +212,29 @@
return;
}
[_formInputAccessoryHandler
[self.formInputAccessoryHandler
setLastFocusFormActivityWebFrameID:base::SysUTF8ToNSString(
params.frame_id)];
[self synchronizeNavigationControls];
[self retrieveSuggestionsForForm:params webState:webState];
}
#pragma mark - FormInputAccessoryViewDelegate
- (void)formInputAccessoryViewDidTapNextButton:(FormInputAccessoryView*)sender {
[self.formInputAccessoryHandler selectNextElementWithButtonPress];
}
- (void)formInputAccessoryViewDidTapPreviousButton:
(FormInputAccessoryView*)sender {
[self.formInputAccessoryHandler selectPreviousElementWithButtonPress];
}
- (void)formInputAccessoryViewDidTapCloseButton:
(FormInputAccessoryView*)sender {
[self.formInputAccessoryHandler closeKeyboardWithButtonPress];
}
#pragma mark - CRWWebStateObserver
- (void)webStateWasShown:(web::WebState*)webState {
......@@ -263,7 +283,6 @@
- (void)disableSuggestions {
self.suggestionsDisabled = YES;
[self updateWithProvider:nil suggestions:nil];
}
- (void)enableSuggestions {
......@@ -282,11 +301,24 @@
}
[_currentProvider inputAccessoryViewControllerDidReset];
_currentProvider = currentProvider;
[_currentProvider setAccessoryViewDelegate:self.formInputAccessoryHandler];
_currentProvider.formInputNavigator = self.formInputAccessoryHandler;
}
#pragma mark - Private
// Update the status of the consumer form navigation buttons to match the
// handler state.
- (void)synchronizeNavigationControls {
__weak __typeof(self) weakSelf = self;
[self.formInputAccessoryHandler
fetchPreviousAndNextElementsPresenceWithCompletionHandler:^(
BOOL previousButtonEnabled, BOOL nextButtonEnabled) {
weakSelf.consumer.formInputNextButtonEnabled = nextButtonEnabled;
weakSelf.consumer.formInputPreviousButtonEnabled =
previousButtonEnabled;
}];
}
// Updates the accessory mediator with the passed web state, its JS suggestion
// manager and the registered providers. If NULL is passed it will instead clear
// those properties in the mediator.
......@@ -405,19 +437,15 @@ queryViewBlockForProvider:(id<FormInputSuggestionsProvider>)provider
// If the suggestions are disabled, post this view with no suggestions to the
// consumer. This allows the navigation buttons be in sync.
if (self.suggestionsDisabled) {
[self.consumer showAccessorySuggestions:@[]
suggestionClient:provider
navigationDelegate:self.formInputAccessoryHandler
isHardwareKeyboard:self.hardwareKeyboard];
return;
} else {
// If suggestions are enabled update |currentProvider|.
self.currentProvider = provider;
// Post it to the consumer.
[self.consumer showAccessorySuggestions:suggestions
suggestionClient:provider
isHardwareKeyboard:self.hardwareKeyboard];
}
// Post it to the consumer.
[self.consumer showAccessorySuggestions:suggestions
suggestionClient:provider
navigationDelegate:self.formInputAccessoryHandler
isHardwareKeyboard:self.hardwareKeyboard];
}
#pragma mark - Keyboard Notifications
......
......@@ -39,7 +39,7 @@ constexpr CGFloat ManualFillIconsRightInset = 24;
} // namespace
static NSTimeInterval MFAnimationDuration = 0;
static NSTimeInterval MFAnimationDuration = 0.2;
@interface ManualFillAccessoryViewController ()
......
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