blob: dcd75a72e6bb82d96e411ef5c95d4579ee3cf501 [file] [log] [blame]
// Copyright 2013 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.
#import "ios/chrome/browser/autofill/form_suggestion_view.h"
#include "base/i18n/rtl.h"
#include "base/logging.h"
#include "components/autofill/core/browser/popup_item_ids.h"
#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)
#error "This file requires ARC support."
#endif
namespace {
// Vertical margin between suggestions and the edge of the suggestion content
// frame.
const CGFloat kSuggestionVerticalMargin = 6;
// Horizontal margin around suggestions (i.e. between suggestions, and between
// the end suggestions and the suggestion content frame).
const CGFloat kSuggestionHorizontalMargin = 6;
} // namespace
@interface FormSuggestionView ()
// 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
#pragma mark - Public
- (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];
if (self.stackView) {
for (UIView* view in [self.stackView.arrangedSubviews copy]) {
[self.stackView removeArrangedSubview:view];
[view removeFromSuperview];
}
self.contentInset = UIEdgeInsetsZero;
[self createAndInsertArrangedSubviews];
}
}
- (void)unlockTrailingView {
if (!self.superview) {
return;
}
[UIView animateWithDuration:0.2
animations:^{
self.contentInset = UIEdgeInsetsZero;
}];
}
- (void)lockTrailingView {
if (!self.superview || !self.trailingView) {
return;
}
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
- (void)willMoveToSuperview:(UIView*)newSuperview {
// Create and add subviews the first time this moves to a superview.
if (newSuperview && self.subviews.count == 0) {
[self setupSubviews];
}
[super willMoveToSuperview:newSuperview];
}
#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;
stackView.layoutMarginsRelativeArrangement = YES;
stackView.layoutMargins =
UIEdgeInsetsMake(kSuggestionVerticalMargin, kSuggestionHorizontalMargin,
kSuggestionVerticalMargin, kSuggestionHorizontalMargin);
stackView.spacing = kSuggestionHorizontalMargin;
stackView.translatesAutoresizingMaskIntoConstraints = NO;
[self addSubview:stackView];
AddSameConstraints(stackView, self);
[stackView.heightAnchor constraintEqualToAnchor:self.heightAnchor].active =
true;
// Rotate the UIScrollView and its UIStackView subview 180 degrees so that the
// first suggestion actually shows up first.
if (base::i18n::IsRTL()) {
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 =
suggestion.identifier != autofill::POPUP_ITEM_ID_GOOGLE_PAY_BRANDING;
UIView* label =
[[FormSuggestionLabel alloc] initWithSuggestion:suggestion
index:idx
userInteractionEnabled:userInteractionEnabled
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 &&
suggestion.identifier == autofill::POPUP_ITEM_ID_GOOGLE_PAY_BRANDING) {
const CGFloat firstLabelWidth =
[label systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]
.width +
kSuggestionHorizontalMargin;
dispatch_time_t popTime =
dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC);
__weak FormSuggestionView* weakSelf = self;
dispatch_after(popTime, dispatch_get_main_queue(), ^{
[weakSelf setContentOffset:CGPointMake(firstLabelWidth,
weakSelf.contentOffset.y)
animated:YES];
});
}
};
[self.suggestions enumerateObjectsUsingBlock:setupBlock];
if (self.trailingView) {
[self.stackView addArrangedSubview:self.trailingView];
}
}
#pragma mark - Setters
- (void)setTrailingView:(UIView*)subview {
if (_trailingView.superview) {
[_stackView removeArrangedSubview:_trailingView];
[_trailingView removeFromSuperview];
}
_trailingView = subview;
if (_stackView) {
[_stackView addArrangedSubview:_trailingView];
}
}
@end