blob: 858bff82bf7e5bac2667c23c38d88dc74d72b517 [file] [log] [blame]
// Copyright 2014 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_input_accessory_view.h"
#import <QuartzCore/QuartzCore.h>
#include "base/i18n/rtl.h"
#include "base/logging.h"
#import "ios/chrome/browser/autofill/form_input_accessory_view_delegate.h"
#import "ios/chrome/browser/ui/image_util.h"
#include "ios/chrome/browser/ui/ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace {
// The alpha value of the background color.
const CGFloat kBackgroundColorAlpha = 1.0;
// Horizontal margin around the custom view.
const CGFloat kCustomViewHorizontalMargin = 2;
// The width of the previous and next buttons.
const CGFloat kNavigationButtonWidth = 44;
// The width of the separators of the previous and next buttons.
const CGFloat kNavigationButtonSeparatorWidth = 1;
// The width of the shadow part of the navigation area separator.
const CGFloat kNavigationAreaSeparatorShadowWidth = 2;
// The width of the navigation area / custom view separator asset.
const CGFloat kNavigationAreaSeparatorWidth = 1;
// Returns YES if the keyboard close button should be shown on the accessory.
BOOL ShouldShowCloseButton() {
return !IsIPadIdiom();
}
// Returns the width of navigation view.
CGFloat GetNavigationViewWidth() {
// The number of naviation buttons (includes close button if shown).
NSUInteger numberNavigationButtons = 2;
if (ShouldShowCloseButton())
numberNavigationButtons++;
return numberNavigationButtons * kNavigationButtonWidth +
(numberNavigationButtons - 1) * kNavigationButtonSeparatorWidth +
kNavigationAreaSeparatorWidth;
}
} // namespace
@interface FormInputAccessoryView ()
// Initializes the view with the given |customView|.
// If the size of |rightFrame| is non-zero, the view will be split into two
// parts with |leftFrame| and |rightFrame|. Otherwise the Autofill view will
// be shown in |leftFrame|.
- (void)initializeViewWithCustomView:(UIView*)customView
leftFrame:(CGRect)leftFrame
rightFrame:(CGRect)rightFrame;
// Returns a view that shows navigation buttons in the |frame|.
- (UIView*)viewForNavigationButtonsInFrame:(CGRect)frame;
// Returns a navigation button for Autofill that has |normalImage| for state
// UIControlStateNormal, a |pressedImage| for states UIControlStateSelected and
// UIControlStateHighlighted, and an optional |disabledImage| for
// UIControlStateDisabled.
- (UIButton*)keyboardNavButtonWithNormalImage:(UIImage*)normalImage
pressedImage:(UIImage*)pressedImage
disabledImage:(UIImage*)disabledImage
target:(id)target
action:(SEL)action
enabled:(BOOL)enabled
originX:(CGFloat)originX
originY:(CGFloat)originY
height:(CGFloat)height;
// Adds a background image to |view|. The supplied image is stretched to fit the
// space by stretching the content its horizontal and vertical centers.
+ (void)addBackgroundImageInView:(UIView*)view
withImageName:(NSString*)imageName;
// Adds an image view in |view| with an image named |imageName| at
// (|originX|, 0). The width is |width| and the height is the height of |view|.
+ (void)addImageViewWithImageName:(NSString*)imageName
originX:(CGFloat)originX
originY:(CGFloat)originY
width:(CGFloat)width
inView:(UIView*)view;
@end
@implementation FormInputAccessoryView {
// The custom view that is displayed in the input accessory view.
UIView* _customView;
// Delegate of this view.
__weak id<FormInputAccessoryViewDelegate> _delegate;
}
- (instancetype)initWithFrame:(CGRect)frame
delegate:(id<FormInputAccessoryViewDelegate>)delegate
customView:(UIView*)customView
leftFrame:(CGRect)leftFrame
rightFrame:(CGRect)rightFrame {
DCHECK(delegate);
self = [super initWithFrame:frame];
if (self) {
_delegate = delegate;
_customView = customView;
[self initializeViewWithCustomView:_customView
leftFrame:leftFrame
rightFrame:rightFrame];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame customView:(UIView*)customView {
self = [super initWithFrame:frame];
if (self) {
_customView = customView;
customView.frame =
CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame));
[self addSubview:customView];
[[self class] addBackgroundImageInView:self
withImageName:@"autofill_keyboard_background"];
}
return self;
}
#pragma mark -
#pragma mark UIInputViewAudioFeedback
- (BOOL)enableInputClicksWhenVisible {
return YES;
}
#pragma mark -
#pragma mark Private Methods
- (void)initializeViewWithCustomView:(UIView*)customView
leftFrame:(CGRect)leftFrame
rightFrame:(CGRect)rightFrame {
UIView* customViewContainer = [[UIView alloc] init];
[self addSubview:customViewContainer];
UIView* navView = [[UIView alloc] init];
[self addSubview:navView];
bool splitKeyboard = CGRectGetWidth(rightFrame) != 0;
BOOL isRTL = base::i18n::IsRTL();
// The computed frame for |customView|.
CGRect customViewFrame;
// Frame of a subview of |navView| in which navigation buttons will be shown.
CGRect navFrame = CGRectZero;
if (splitKeyboard) {
NSString* navViewBackgroundImageName = nil;
NSString* customViewContainerBackgroundImageName = nil;
NSUInteger navFrameOriginX = 0;
if (isRTL) {
navView.frame = leftFrame;
navViewBackgroundImageName = @"autofill_keyboard_background_left";
customViewContainer.frame = rightFrame;
customViewContainerBackgroundImageName =
@"autofill_keyboard_background_right";
// Navigation buttons will be shown on the left side.
navFrameOriginX = 0;
} else {
customViewContainer.frame = leftFrame;
customViewContainerBackgroundImageName =
@"autofill_keyboard_background_left";
navView.frame = rightFrame;
navViewBackgroundImageName = @"autofill_keyboard_background_right";
// Navigation buttons will be shown on the right side.
navFrameOriginX =
CGRectGetWidth(navView.frame) - GetNavigationViewWidth();
}
[[self class]
addBackgroundImageInView:customViewContainer
withImageName:customViewContainerBackgroundImageName];
[[self class] addBackgroundImageInView:navView
withImageName:navViewBackgroundImageName];
// For RTL, the custom view is the right view; the padding should be at the
// left side of this view. Otherwise, the custom view is the left view
// and the space is at the right side.
customViewFrame = CGRectMake(isRTL ? kCustomViewHorizontalMargin : 0, 0,
CGRectGetWidth(customViewContainer.bounds) -
kCustomViewHorizontalMargin,
CGRectGetHeight(customViewContainer.bounds));
navFrame = CGRectMake(navFrameOriginX, 0, GetNavigationViewWidth(),
CGRectGetHeight(navView.frame));
} else {
NSUInteger navViewFrameOriginX = 0;
NSUInteger customViewContainerFrameOrginX = 0;
if (isRTL) {
navViewFrameOriginX = kNavigationAreaSeparatorShadowWidth;
customViewContainerFrameOrginX = GetNavigationViewWidth();
} else {
navViewFrameOriginX =
CGRectGetWidth(leftFrame) - GetNavigationViewWidth();
}
customViewContainer.frame =
CGRectMake(customViewContainerFrameOrginX, 0,
CGRectGetWidth(leftFrame) - GetNavigationViewWidth() +
kNavigationAreaSeparatorShadowWidth,
CGRectGetHeight(leftFrame));
navView.frame = CGRectMake(navViewFrameOriginX, 0, GetNavigationViewWidth(),
CGRectGetHeight(leftFrame));
customViewFrame = customViewContainer.bounds;
navFrame = navView.bounds;
[[self class] addBackgroundImageInView:self
withImageName:@"autofill_keyboard_background"];
}
[customView setFrame:customViewFrame];
[customViewContainer addSubview:customView];
[navView addSubview:[self viewForNavigationButtonsInFrame:navFrame]];
}
UIImage* ButtonImage(NSString* name) {
UIImage* rawImage = [UIImage imageNamed:name];
return StretchableImageFromUIImage(rawImage, 1, 0);
}
- (UIView*)viewForNavigationButtonsInFrame:(CGRect)frame {
UIView* navView = [[UIView alloc] initWithFrame:frame];
BOOL isRTL = base::i18n::IsRTL();
// Vertical space is left for a dividing line.
CGFloat firstRow = 1;
CGFloat currentX = 0;
// Navigation view is at the right side if not RTL. Add a left separator in
// this case.
if (!isRTL) {
[[self class] addImageViewWithImageName:@"autofill_left_sep"
originX:currentX
originY:firstRow
width:kNavigationAreaSeparatorWidth
inView:navView];
currentX = kNavigationAreaSeparatorWidth;
}
UIButton* previousButton = [self
keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_prev")
pressedImage:ButtonImage(@"autofill_prev_pressed")
disabledImage:ButtonImage(@"autofill_prev_inactive")
target:_delegate
action:@selector(
selectPreviousElementWithButtonPress)
enabled:NO
originX:currentX
originY:firstRow
height:CGRectGetHeight(frame)];
[previousButton
setAccessibilityLabel:l10n_util::GetNSString(
IDS_IOS_AUTOFILL_ACCNAME_PREVIOUS_FIELD)];
[navView addSubview:previousButton];
currentX += kNavigationButtonWidth;
// Add internal separator.
[[self class] addImageViewWithImageName:@"autofill_middle_sep"
originX:currentX
originY:firstRow
width:kNavigationButtonSeparatorWidth
inView:navView];
currentX += kNavigationButtonSeparatorWidth;
UIButton* nextButton = [self
keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_next")
pressedImage:ButtonImage(@"autofill_next_pressed")
disabledImage:ButtonImage(@"autofill_next_inactive")
target:_delegate
action:@selector(
selectNextElementWithButtonPress)
enabled:NO
originX:currentX
originY:firstRow
height:CGRectGetHeight(frame)];
[nextButton setAccessibilityLabel:l10n_util::GetNSString(
IDS_IOS_AUTOFILL_ACCNAME_NEXT_FIELD)];
[navView addSubview:nextButton];
currentX += kNavigationButtonWidth;
[_delegate fetchPreviousAndNextElementsPresenceWithCompletionHandler:
^(BOOL hasPreviousElement, BOOL hasNextElement) {
previousButton.enabled = hasPreviousElement;
nextButton.enabled = hasNextElement;
}];
if (ShouldShowCloseButton()) {
// Add internal separator.
[[self class] addImageViewWithImageName:@"autofill_middle_sep"
originX:currentX
originY:firstRow
width:kNavigationButtonSeparatorWidth
inView:navView];
currentX += kNavigationButtonSeparatorWidth;
UIButton* closeButton = [self
keyboardNavButtonWithNormalImage:ButtonImage(@"autofill_close")
pressedImage:ButtonImage(@"autofill_close_pressed")
disabledImage:nil
target:_delegate
action:@selector(closeKeyboardWithButtonPress)
enabled:YES
originX:currentX
originY:firstRow
height:CGRectGetHeight(frame)];
[closeButton
setAccessibilityLabel:l10n_util::GetNSString(
IDS_IOS_AUTOFILL_ACCNAME_HIDE_KEYBOARD)];
[navView addSubview:closeButton];
currentX += kNavigationButtonWidth;
}
// Navigation view is at the left side for RTL. Add a right separator in
// this case.
if (isRTL) {
[[self class] addImageViewWithImageName:@"autofill_right_sep"
originX:currentX
originY:firstRow
width:kNavigationAreaSeparatorWidth
inView:navView];
}
return navView;
}
- (UIButton*)keyboardNavButtonWithNormalImage:(UIImage*)normalImage
pressedImage:(UIImage*)pressedImage
disabledImage:(UIImage*)disabledImage
target:(id)target
action:(SEL)action
enabled:(BOOL)enabled
originX:(CGFloat)originX
originY:(CGFloat)originY
height:(CGFloat)height {
UIButton* button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame =
CGRectMake(originX, originY, kNavigationButtonWidth, height - originY);
[button setBackgroundImage:normalImage forState:UIControlStateNormal];
[button setBackgroundImage:pressedImage forState:UIControlStateSelected];
[button setBackgroundImage:pressedImage forState:UIControlStateHighlighted];
if (disabledImage)
[button setBackgroundImage:disabledImage forState:UIControlStateDisabled];
CALayer* layer = [button layer];
layer.borderWidth = 0;
layer.borderColor = [[UIColor blackColor] CGColor];
button.enabled = enabled;
[button addTarget:target
action:action
forControlEvents:UIControlEventTouchUpInside];
return button;
}
+ (void)addBackgroundImageInView:(UIView*)view
withImageName:(NSString*)imageName {
UIImage* backgroundImage = StretchableImageNamed(imageName);
UIImageView* backgroundImageView =
[[UIImageView alloc] initWithFrame:view.bounds];
[backgroundImageView setImage:backgroundImage];
[backgroundImageView setAlpha:kBackgroundColorAlpha];
[view addSubview:backgroundImageView];
[view sendSubviewToBack:backgroundImageView];
}
+ (void)addImageViewWithImageName:(NSString*)imageName
originX:(CGFloat)originX
originY:(CGFloat)originY
width:(CGFloat)width
inView:(UIView*)view {
UIImage* image =
StretchableImageFromUIImage([UIImage imageNamed:imageName], 0, 0);
UIImageView* imageView = [[UIImageView alloc] initWithImage:image];
[imageView setFrame:CGRectMake(originX, originY, width,
CGRectGetHeight(view.bounds) - originY)];
[view addSubview:imageView];
}
@end