blob: 9700f9ef7f9c0f63105710ceb2c6f66f90ffaf08 [file] [log] [blame]
// Copyright 2016 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/ui/payments/payment_request_view_controller.h"
#include "base/mac/foundation_util.h"
#include "components/strings/grit/components_strings.h"
#import "ios/chrome/browser/ui/autofill/cells/status_item.h"
#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_detail_item.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item+collection_view_controller.h"
#import "ios/chrome/browser/ui/collection_view/cells/collection_view_item.h"
#import "ios/chrome/browser/ui/collection_view/collection_view_model.h"
#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
#import "ios/chrome/browser/ui/payments/cells/page_info_item.h"
#import "ios/chrome/browser/ui/payments/cells/payments_text_item.h"
#import "ios/chrome/browser/ui/payments/cells/price_item.h"
#import "ios/chrome/browser/ui/payments/payment_request_view_controller_actions.h"
#include "ios/chrome/browser/ui/rtl_geometry.h"
#include "ios/chrome/browser/ui/uikit_ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/third_party/material_components_ios/src/components/Buttons/src/MaterialButtons.h"
#import "ios/third_party/material_components_ios/src/components/Typography/src/MaterialTypography.h"
#include "ui/base/l10n/l10n_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
NSString* const kPaymentRequestCollectionViewID =
@"kPaymentRequestCollectionViewID";
namespace {
const CGFloat kFooterCellHorizontalPadding = 16;
const CGFloat kButtonEdgeInset = 9;
const CGFloat kSeparatorEdgeInset = 14;
typedef NS_ENUM(NSInteger, SectionIdentifier) {
SectionIdentifierSummary = kSectionIdentifierEnumZero,
SectionIdentifierShipping,
SectionIdentifierPayment,
SectionIdentifierContactInfo,
SectionIdentifierFooter,
};
typedef NS_ENUM(NSInteger, ItemType) {
ItemTypeSummaryPageInfo = kItemTypeEnumZero,
ItemTypeSpinner,
ItemTypeSummaryTotal,
ItemTypeShippingHeader,
ItemTypeShippingAddress,
ItemTypeShippingOption,
ItemTypePaymentHeader,
ItemTypePaymentMethod,
ItemTypeContactInfoHeader,
ItemTypeContactInfo,
ItemTypeFooterText,
};
} // namespace
@interface PaymentRequestViewController ()<
CollectionViewFooterLinkDelegate,
PaymentRequestViewControllerActions> {
UIBarButtonItem* _cancelButton;
MDCButton* _payButton;
}
@end
@implementation PaymentRequestViewController
@synthesize pageFavicon = _pageFavicon;
@synthesize pageTitle = _pageTitle;
@synthesize pageHost = _pageHost;
@synthesize connectionSecure = _connectionSecure;
@synthesize pending = _pending;
@synthesize cancellable = _cancellable;
@synthesize delegate = _delegate;
@synthesize dataSource = _dataSource;
- (instancetype)init {
UICollectionViewLayout* layout = [[MDCCollectionViewFlowLayout alloc] init];
if ((self = [super initWithLayout:layout
style:CollectionViewControllerStyleAppBar])) {
[self setTitle:l10n_util::GetNSString(IDS_PAYMENTS_TITLE)];
// Set up leading (cancel) button.
_cancelButton = [[UIBarButtonItem alloc]
initWithTitle:l10n_util::GetNSString(IDS_CANCEL)
style:UIBarButtonItemStylePlain
target:nil
action:@selector(onCancel)];
[_cancelButton setTitleTextAttributes:@{
NSForegroundColorAttributeName : [UIColor lightGrayColor]
}
forState:UIControlStateDisabled];
[_cancelButton
setAccessibilityLabel:l10n_util::GetNSString(IDS_ACCNAME_CANCEL)];
[self navigationItem].leftBarButtonItem = _cancelButton;
// Set up trailing (pay) button.
_payButton = [[MDCButton alloc] init];
[_payButton setTitle:l10n_util::GetNSString(IDS_PAYMENTS_PAY_BUTTON)
forState:UIControlStateNormal];
[_payButton setBackgroundColor:[[MDCPalette cr_bluePalette] tint500]];
[_payButton setTitleColor:[UIColor whiteColor]
forState:UIControlStateNormal];
[_payButton setInkColor:[UIColor colorWithWhite:1 alpha:0.2]];
[_payButton addTarget:self
action:@selector(onConfirm)
forControlEvents:UIControlEventTouchUpInside];
[_payButton sizeToFit];
[_payButton setAutoresizingMask:UIViewAutoresizingFlexibleTrailingMargin() |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin];
// The navigation bar will set the rightBarButtonItem's height to the full
// height of the bar. We don't want that for the button so we use a UIView
// here to contain the button instead and the button is vertically centered
// inside the full bar height.
UIView* buttonView = [[UIView alloc] initWithFrame:CGRectZero];
[buttonView addSubview:_payButton];
// Navigation bar button items are aligned with the trailing edge of the
// screen. Make the enclosing view larger here. The pay button will be
// aligned with the leading edge of the enclosing view leaving an inset on
// the trailing edge.
CGRect buttonViewBounds = buttonView.bounds;
buttonViewBounds.size.width =
[_payButton frame].size.width + kButtonEdgeInset;
buttonView.bounds = buttonViewBounds;
UIBarButtonItem* payButtonItem =
[[UIBarButtonItem alloc] initWithCustomView:buttonView];
[self navigationItem].rightBarButtonItem = payButtonItem;
}
return self;
}
- (void)onCancel {
[_delegate paymentRequestViewControllerDidCancel:self];
}
- (void)onConfirm {
[_delegate paymentRequestViewControllerDidConfirm:self];
}
#pragma mark - Setters
- (void)setDataSource:(id<PaymentRequestViewControllerDataSource>)dataSource {
_dataSource = dataSource;
[_payButton setEnabled:[_dataSource canPay]];
}
- (void)setCancellable:(BOOL)cancellable {
_cancellable = cancellable;
[_cancelButton setEnabled:_cancellable];
self.view.userInteractionEnabled = cancellable;
}
#pragma mark - CollectionViewController methods
- (void)loadModel {
[super loadModel];
CollectionViewModel* model = self.collectionViewModel;
// Summary section.
[model addSectionWithIdentifier:SectionIdentifierSummary];
PageInfoItem* pageInfo =
[[PageInfoItem alloc] initWithType:ItemTypeSummaryPageInfo];
pageInfo.pageFavicon = _pageFavicon;
pageInfo.pageTitle = _pageTitle;
pageInfo.pageHost = _pageHost;
pageInfo.connectionSecure = _connectionSecure;
[model addItem:pageInfo toSectionWithIdentifier:SectionIdentifierSummary];
if (_pending) {
[_payButton setEnabled:NO];
StatusItem* statusItem = [[StatusItem alloc] initWithType:ItemTypeSpinner];
statusItem.text = l10n_util::GetNSString(IDS_PAYMENTS_PROCESSING_MESSAGE);
[model addItem:statusItem toSectionWithIdentifier:SectionIdentifierSummary];
return;
}
[self addPaymentSummaryItem];
// Shipping section.
if ([_dataSource requestShipping]) {
[model addSectionWithIdentifier:SectionIdentifierShipping];
PaymentsTextItem* shippingSectionHeaderItem =
[_dataSource shippingSectionHeaderItem];
[shippingSectionHeaderItem setTextColor:[[MDCPalette greyPalette] tint500]];
[shippingSectionHeaderItem setType:ItemTypeShippingHeader];
[model setHeader:shippingSectionHeaderItem
forSectionWithIdentifier:SectionIdentifierShipping];
[self populateShippingSection];
}
// Payment method section.
[model addSectionWithIdentifier:SectionIdentifierPayment];
[self populatePaymentMethodSection];
// Contact Info section.
if ([_dataSource requestContactInfo]) {
[model addSectionWithIdentifier:SectionIdentifierContactInfo];
[self populateContactInfoSection];
}
// Footer Text section.
[model addSectionWithIdentifier:SectionIdentifierFooter];
CollectionViewFooterItem* footerItem = [_dataSource footerItem];
[footerItem setType:ItemTypeFooterText];
footerItem.linkDelegate = self;
[model addItem:footerItem toSectionWithIdentifier:SectionIdentifierFooter];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.collectionView.accessibilityIdentifier = kPaymentRequestCollectionViewID;
// Customize collection view settings.
self.styler.cellStyle = MDCCollectionViewCellStyleCard;
self.styler.separatorInset =
UIEdgeInsetsMake(0, kSeparatorEdgeInset, 0, kSeparatorEdgeInset);
}
- (void)updatePaymentSummaryItem {
CollectionViewModel* model = self.collectionViewModel;
[model removeItemWithType:ItemTypeSummaryTotal
fromSectionWithIdentifier:SectionIdentifierSummary];
[self addPaymentSummaryItem];
// Reload the item.
NSIndexPath* indexPath =
[model indexPathForItemType:ItemTypeSummaryTotal
sectionIdentifier:SectionIdentifierSummary];
[self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
}
- (void)updateShippingSection {
CollectionViewModel* model = self.collectionViewModel;
[model removeItemWithType:ItemTypeShippingAddress
fromSectionWithIdentifier:SectionIdentifierShipping];
if ([model hasItemForItemType:ItemTypeShippingOption
sectionIdentifier:SectionIdentifierShipping]) {
[model removeItemWithType:ItemTypeShippingOption
fromSectionWithIdentifier:SectionIdentifierShipping];
}
[self populateShippingSection];
// Reload the section.
NSInteger sectionIndex =
[model sectionForSectionIdentifier:SectionIdentifierShipping];
[self.collectionView
reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
// Update the pay button.
[_payButton setEnabled:[_dataSource canPay]];
}
- (void)updatePaymentMethodSection {
CollectionViewModel* model = self.collectionViewModel;
[model removeItemWithType:ItemTypePaymentMethod
fromSectionWithIdentifier:SectionIdentifierPayment];
[self populatePaymentMethodSection];
// Reload the section.
NSInteger sectionIndex =
[model sectionForSectionIdentifier:SectionIdentifierPayment];
[self.collectionView
reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
// Update the pay button.
[_payButton setEnabled:[_dataSource canPay]];
}
- (void)updateContactInfoSection {
CollectionViewModel* model = self.collectionViewModel;
[model removeItemWithType:ItemTypeContactInfo
fromSectionWithIdentifier:SectionIdentifierContactInfo];
[self populateContactInfoSection];
// Reload the section.
NSInteger sectionIndex =
[model sectionForSectionIdentifier:SectionIdentifierContactInfo];
[self.collectionView
reloadSections:[NSIndexSet indexSetWithIndex:sectionIndex]];
// Update the pay button.
[_payButton setEnabled:[_dataSource canPay]];
}
#pragma mark - CollectionViewFooterLinkDelegate
- (void)cell:(CollectionViewFooterCell*)cell didTapLinkURL:(GURL)url {
[_delegate paymentRequestViewControllerDidSelectSettings:self];
}
#pragma mark - UICollectionViewDataSource
- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
cellForItemAtIndexPath:(nonnull NSIndexPath*)indexPath {
UICollectionViewCell* cell =
[super collectionView:collectionView cellForItemAtIndexPath:indexPath];
NSInteger itemType =
[self.collectionViewModel itemTypeForIndexPath:indexPath];
switch (itemType) {
case ItemTypeShippingAddress:
case ItemTypePaymentMethod:
case ItemTypeShippingOption:
case ItemTypeContactInfo: {
if ([cell isKindOfClass:[CollectionViewDetailCell class]]) {
CollectionViewDetailCell* detailCell =
base::mac::ObjCCastStrict<CollectionViewDetailCell>(cell);
detailCell.detailTextLabel.font = [MDCTypography body2Font];
detailCell.detailTextLabel.textColor =
[[MDCPalette cr_bluePalette] tint700];
}
break;
}
case ItemTypeFooterText: {
CollectionViewFooterCell* footerCell =
base::mac::ObjCCastStrict<CollectionViewFooterCell>(cell);
footerCell.textLabel.font = [MDCTypography body2Font];
footerCell.textLabel.textColor = [[MDCPalette greyPalette] tint600];
footerCell.textLabel.shadowColor = nil; // No shadow.
footerCell.horizontalPadding = kFooterCellHorizontalPadding;
break;
}
default:
break;
}
return cell;
}
#pragma mark - UICollectionViewDelegate
- (void)collectionView:(UICollectionView*)collectionView
didSelectItemAtIndexPath:(NSIndexPath*)indexPath {
[super collectionView:collectionView didSelectItemAtIndexPath:indexPath];
NSInteger itemType =
[self.collectionViewModel itemTypeForIndexPath:indexPath];
switch (itemType) {
case ItemTypeSummaryTotal:
[_delegate
paymentRequestViewControllerDidSelectPaymentSummaryItem:self];
break;
case ItemTypeShippingAddress:
[_delegate paymentRequestViewControllerDidSelectShippingAddressItem:self];
break;
case ItemTypeShippingOption:
[_delegate paymentRequestViewControllerDidSelectShippingOptionItem:self];
break;
case ItemTypePaymentMethod:
[_delegate paymentRequestViewControllerDidSelectPaymentMethodItem:self];
break;
case ItemTypeContactInfo:
[_delegate paymentRequestViewControllerDidSelectContactInfoItem:self];
break;
case ItemTypeFooterText:
// Selecting the footer item should not trigger an action, unless the
// link was clicked, which will call didTapLinkURL:.
break;
case ItemTypeSummaryPageInfo:
// Selecting the page info item should not trigger an action.
break;
default:
NOTREACHED();
break;
}
}
#pragma mark - MDCCollectionViewStylingDelegate
- (CGFloat)collectionView:(UICollectionView*)collectionView
cellHeightAtIndexPath:(NSIndexPath*)indexPath {
CollectionViewItem* item =
[self.collectionViewModel itemAtIndexPath:indexPath];
UIEdgeInsets inset = [self collectionView:collectionView
layout:collectionView.collectionViewLayout
insetForSectionAtIndex:indexPath.section];
return [MDCCollectionViewCell
cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds) -
inset.left - inset.right
forItem:item];
}
- (BOOL)collectionView:(UICollectionView*)collectionView
hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath];
// If there are no payment items to display, there is no effect from touching
// the total so there should not be an ink ripple. The footer and the page
// info items should also not have a ripple.
if ((type == ItemTypeSummaryTotal && ![_dataSource hasPaymentItems]) ||
type == ItemTypeFooterText || type == ItemTypeSummaryPageInfo) {
return YES;
} else {
return NO;
}
}
- (BOOL)collectionView:(UICollectionView*)collectionView
shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath {
// No background on the footer text item.
NSInteger sectionIdentifier =
[self.collectionViewModel sectionIdentifierForSection:indexPath.section];
return sectionIdentifier == SectionIdentifierFooter ? YES : NO;
}
#pragma mark - Helper methods
- (void)addPaymentSummaryItem {
CollectionViewItem* item = [_dataSource paymentSummaryItem];
[item setType:ItemTypeSummaryTotal];
if ([_dataSource hasPaymentItems])
item.accessibilityTraits |= UIAccessibilityTraitButton;
[self.collectionViewModel addItem:item
toSectionWithIdentifier:SectionIdentifierSummary];
}
- (void)populateShippingSection {
CollectionViewModel* model = self.collectionViewModel;
CollectionViewItem* shippingAddressItem = [_dataSource shippingAddressItem];
[shippingAddressItem setType:ItemTypeShippingAddress];
shippingAddressItem.accessibilityTraits |= UIAccessibilityTraitButton;
[model addItem:shippingAddressItem
toSectionWithIdentifier:SectionIdentifierShipping];
if ([_dataSource canShip]) {
CollectionViewItem* shippingOptionItem = [_dataSource shippingOptionItem];
[shippingOptionItem setType:ItemTypeShippingOption];
shippingOptionItem.accessibilityTraits |= UIAccessibilityTraitButton;
[model addItem:shippingOptionItem
toSectionWithIdentifier:SectionIdentifierShipping];
}
}
- (void)populatePaymentMethodSection {
CollectionViewModel* model = self.collectionViewModel;
PaymentsTextItem* paymentMethodSectionHeaderItem =
[_dataSource paymentMethodSectionHeaderItem];
if (paymentMethodSectionHeaderItem) {
[paymentMethodSectionHeaderItem setType:ItemTypePaymentHeader];
[paymentMethodSectionHeaderItem
setTextColor:[[MDCPalette greyPalette] tint500]];
[model setHeader:paymentMethodSectionHeaderItem
forSectionWithIdentifier:SectionIdentifierPayment];
}
CollectionViewItem* paymentMethodItem = [_dataSource paymentMethodItem];
[paymentMethodItem setType:ItemTypePaymentMethod];
paymentMethodItem.accessibilityTraits |= UIAccessibilityTraitButton;
[model addItem:paymentMethodItem
toSectionWithIdentifier:SectionIdentifierPayment];
}
- (void)populateContactInfoSection {
CollectionViewModel* model = self.collectionViewModel;
PaymentsTextItem* contactInfoSectionHeaderItem =
[_dataSource contactInfoSectionHeaderItem];
if (contactInfoSectionHeaderItem) {
[contactInfoSectionHeaderItem setType:ItemTypeContactInfoHeader];
[contactInfoSectionHeaderItem
setTextColor:[[MDCPalette greyPalette] tint500]];
[model setHeader:contactInfoSectionHeaderItem
forSectionWithIdentifier:SectionIdentifierContactInfo];
}
CollectionViewItem* contactInfoItem = [_dataSource contactInfoItem];
[contactInfoItem setType:ItemTypeContactInfo];
contactInfoItem.accessibilityTraits |= UIAccessibilityTraitButton;
[model addItem:contactInfoItem
toSectionWithIdentifier:SectionIdentifierContactInfo];
}
@end