Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(57)

Unified Diff: ios/chrome/browser/payments/payment_request_edit_view_controller.mm

Issue 2744823003: [Payment Request] Generic edit form (Closed)
Patch Set: Addressed comments Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/payments/payment_request_edit_view_controller.mm
diff --git a/ios/chrome/browser/payments/payment_request_edit_view_controller.mm b/ios/chrome/browser/payments/payment_request_edit_view_controller.mm
new file mode 100644
index 0000000000000000000000000000000000000000..29387ffe98c9dfc2950d186a83e6fa6ea6193f12
--- /dev/null
+++ b/ios/chrome/browser/payments/payment_request_edit_view_controller.mm
@@ -0,0 +1,359 @@
+// 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.
+
+#import "ios/chrome/browser/payments/payment_request_edit_view_controller.h"
+
+#include "base/logging.h"
+#import "base/mac/foundation_util.h"
+#include "base/strings/sys_string_conversions.h"
+#include "components/strings/grit/components_strings.h"
+#import "ios/chrome/browser/payments/cells/payments_text_item.h"
+#import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrome.h"
+#import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
+#import "ios/chrome/browser/ui/settings/autofill_edit_accessory_view.h"
+#import "ios/chrome/browser/ui/settings/cells/autofill_edit_item.h"
+#import "ios/chrome/browser/ui/uikit_ui_util.h"
+#include "ios/chrome/grit/ios_theme_resources.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
+
+namespace {
+
+const CGFloat kSeparatorEdgeInset = 14;
+
+AutofillEditCell* AutofillEditCellForTextField(UITextField* textField) {
+ for (UIView* view = textField; view; view = [view superview]) {
+ AutofillEditCell* cell = base::mac::ObjCCast<AutofillEditCell>(view);
+ if (cell)
+ return cell;
+ }
+
+ // There has to be a cell associated with this text field.
+ NOTREACHED();
+ return nil;
+}
+
+// Item type for the error message item. This value is chosen to avoid
+// overlapping with the values starting from kItemTypeEnumZero. This is a
+// repeated item type.
+const NSInteger ItemTypeErrorMessage = kItemTypeEnumZero + 100;
+
+} // namespace
+
+@interface PaymentRequestEditViewController ()<AutofillEditAccessoryDelegate> {
+ // The currently focused cell. May be nil.
+ __weak AutofillEditCell* _currentEditingCell;
+
+ AutofillEditAccessoryView* _accessoryView;
+}
+
+// Returns the index path for the cell associated with the currently focused
+// text field.
+- (NSIndexPath*)indexPathForCurrentTextField;
+
+// Returns the validation error string for |value| for the autofill type
+// |autofillType|. |required| indicates whether this is a required field or not.
+// If there are no validation errors, an empty string is returned.
+- (NSString*)validateValue:(NSString*)value
+ autofillType:(autofill::ServerFieldType)autofillType
+ required:(BOOL)required;
+
+// Adds an error message item in the section |sectionIdentifier| if
+// |errorMessage| is non-empty. Otherwise removes such an item if one exists.
+- (void)addOrRemoveErrorMessage:(NSString*)errorMessage
+ inSectionWithIdentifier:(NSInteger)sectionIdentifier;
+
+// Returns the indexPath for the same row as that of |indexPath| in a section
+// with the given offset relative to that of |indexPath|. It may return nil.
+- (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset
+ fromPath:(NSIndexPath*)indexPath;
+
+// Returns the text field with the given offset relative to the currently
+// focused text field. May return nil.
+- (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset;
+
+// Enables or disables the accessory view's previous and next buttons depending
+// on whether there is a text field before and after the currently focused text
+// field.
+- (void)updateAccessoryViewButtonsStates;
+
+@end
+
+@implementation PaymentRequestEditViewController
+
+- (instancetype)init {
+ self = [super initWithStyle:CollectionViewControllerStyleAppBar];
+ if (self) {
+ _accessoryView = [[AutofillEditAccessoryView alloc] initWithDelegate:self];
+ }
+ return self;
+}
+
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardDidShow)
+ name:UIKeyboardDidShowNotification
+ object:nil];
+}
+
+- (void)viewWillDisappear:(BOOL)animated {
+ [super viewWillDisappear:animated];
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:UIKeyboardDidShowNotification
+ object:nil];
+}
+
+#pragma mark - CollectionViewController methods
+
+- (void)loadModel {
+ [super loadModel];
+ CollectionViewModel* model = self.collectionViewModel;
+
+ // Iterate over the field definitions and add the respective sections and
+ // items.
+ for (const auto& field : [self editorFields]) {
+ [model addSectionWithIdentifier:field.section_id];
+ AutofillEditItem* item =
+ [[AutofillEditItem alloc] initWithType:field.item_type];
+ item.textFieldName = base::SysUTF16ToNSString(field.label);
+ item.textFieldEnabled = YES;
+ item.required = field.required;
+ item.autofillType = field.data_type;
+ [model addItem:item toSectionWithIdentifier:field.section_id];
+ }
+}
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+
+ // Customize collection view settings.
+ self.styler.cellStyle = MDCCollectionViewCellStyleCard;
+ self.styler.separatorInset =
+ UIEdgeInsetsMake(0, kSeparatorEdgeInset, 0, kSeparatorEdgeInset);
+}
+
+#pragma mark - UITextFieldDelegate
+
+- (void)textFieldDidBeginEditing:(UITextField*)textField {
+ _currentEditingCell = AutofillEditCellForTextField(textField);
+ [textField setInputAccessoryView:_accessoryView];
+ [self updateAccessoryViewButtonsStates];
+}
+
+- (void)textFieldDidEndEditing:(UITextField*)textField {
+ DCHECK(_currentEditingCell == AutofillEditCellForTextField(textField));
+ CollectionViewModel* model = self.collectionViewModel;
+
+ // Validate the text field. If there is a validation error, display an error
+ // message item in the same section as the field. Otherwise remove the error
+ // message item in that section.
+ NSIndexPath* indexPath = [self indexPathForCurrentTextField];
+ AutofillEditItem* item = base::mac::ObjCCastStrict<AutofillEditItem>(
+ [model itemAtIndexPath:indexPath]);
+
+ NSString* errorMessage = [self validateValue:textField.text
+ autofillType:item.autofillType
+ required:item.required];
+ NSInteger sectionIdentifier =
+ [model sectionIdentifierForSection:[indexPath section]];
+ [self addOrRemoveErrorMessage:errorMessage
+ inSectionWithIdentifier:sectionIdentifier];
+
+ [textField setInputAccessoryView:nil];
+ _currentEditingCell = nil;
+}
+
+- (BOOL)textFieldShouldReturn:(UITextField*)textField {
+ DCHECK([_currentEditingCell textField] == textField);
+ [self nextPressed];
+ return NO;
+}
+
+#pragma mark - AutofillEditAccessoryDelegate
+
+- (void)nextPressed {
+ AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1];
+ if (nextCell)
+ [nextCell.textField becomeFirstResponder];
+}
+
+- (void)previousPressed {
+ AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1];
+ if (previousCell)
+ [previousCell.textField becomeFirstResponder];
+}
+
+- (void)closePressed {
+ [[_currentEditingCell textField] resignFirstResponder];
+}
+
+#pragma mark - UICollectionViewDataSource
+
+- (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
+ cellForItemAtIndexPath:(NSIndexPath*)indexPath {
+ UICollectionViewCell* cell =
+ [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
+
+ if ([cell isKindOfClass:[AutofillEditCell class]]) {
+ AutofillEditCell* autofillEditCell =
+ base::mac::ObjCCast<AutofillEditCell>(cell);
+ autofillEditCell.textField.delegate = self;
+ }
+
+ CollectionViewItem* item =
+ [self.collectionViewModel itemAtIndexPath:indexPath];
+ switch (item.type) {
+ case ItemTypeErrorMessage: {
+ PaymentsTextCell* errorMessageCell =
+ base::mac::ObjCCastStrict<PaymentsTextCell>(cell);
+ errorMessageCell.textLabel.font = [MDCTypography body1Font];
+ errorMessageCell.textLabel.textColor =
+ [[MDCPalette cr_redPalette] tint600];
+ break;
+ }
+ default:
+ break;
+ }
+
+ return cell;
+}
+
+#pragma mark MDCCollectionViewStylingDelegate
+
+- (CGFloat)collectionView:(UICollectionView*)collectionView
+ cellHeightAtIndexPath:(NSIndexPath*)indexPath {
+ CollectionViewItem* item =
+ [self.collectionViewModel itemAtIndexPath:indexPath];
+
+ if ([item isKindOfClass:[AutofillEditItem class]] ||
+ item.type == ItemTypeErrorMessage) {
+ return [MDCCollectionViewCell
+ cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
+ forItem:item];
+ }
+
+ NOTREACHED();
+ return MDCCellDefaultOneLineHeight;
+}
+
+- (BOOL)collectionView:(UICollectionView*)collectionView
+ hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
+ NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath];
+ switch (type) {
+ case ItemTypeErrorMessage:
+ return YES;
+ default:
+ return NO;
+ }
+}
+
+#pragma mark - Protected methods
+
+- (std::vector<EditorField>)editorFields {
+ return std::vector<EditorField>();
+}
+
+#pragma mark - Helper methods
+
+- (NSIndexPath*)indexPathForCurrentTextField {
+ DCHECK(_currentEditingCell);
+ NSIndexPath* indexPath =
+ [[self collectionView] indexPathForCell:_currentEditingCell];
+ DCHECK(indexPath);
+ return indexPath;
+}
+
+- (NSString*)validateValue:(NSString*)value
+ autofillType:(autofill::ServerFieldType)autofillType
+ required:(BOOL)required {
+ if (required && value.length == 0) {
+ return l10n_util::GetNSString(
+ IDS_PAYMENTS_FIELD_REQUIRED_VALIDATION_MESSAGE);
+ }
+ return @"";
+}
+
+- (void)addOrRemoveErrorMessage:(NSString*)errorMessage
+ inSectionWithIdentifier:(NSInteger)sectionIdentifier {
+ CollectionViewModel* model = self.collectionViewModel;
+ if ([model hasItemForItemType:ItemTypeErrorMessage
+ sectionIdentifier:sectionIdentifier]) {
+ NSIndexPath* indexPath = [model indexPathForItemType:ItemTypeErrorMessage
+ sectionIdentifier:sectionIdentifier];
+ if (errorMessage.length == 0) {
+ // Remove the item at the index path.
+ [model removeItemWithType:ItemTypeErrorMessage
+ fromSectionWithIdentifier:sectionIdentifier];
+ [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]];
+ } else {
+ // Reload the item at the index path.
+ PaymentsTextItem* item = base::mac::ObjCCastStrict<PaymentsTextItem>(
+ [model itemAtIndexPath:indexPath]);
+ item.text = errorMessage;
+ [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
+ }
+ } else if (errorMessage.length != 0) {
+ // Insert an item at the index path.
+ PaymentsTextItem* errorMessageItem =
+ [[PaymentsTextItem alloc] initWithType:ItemTypeErrorMessage];
+ errorMessageItem.text = errorMessage;
+ errorMessageItem.image = NativeImage(IDR_IOS_PAYMENTS_WARNING);
+ [model addItem:errorMessageItem toSectionWithIdentifier:sectionIdentifier];
+ NSIndexPath* indexPath = [model indexPathForItemType:ItemTypeErrorMessage
+ sectionIdentifier:sectionIdentifier];
+ [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
+ }
+}
+
+- (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset
+ fromPath:(NSIndexPath*)indexPath {
+ DCHECK(indexPath);
+ DCHECK(offset);
+ NSInteger nextSection = [indexPath section] + offset;
+ if (nextSection >= 0 &&
+ nextSection < [[self collectionView] numberOfSections]) {
+ return [NSIndexPath indexPathForRow:[indexPath row] inSection:nextSection];
+ }
+ return nil;
+}
+
+- (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset {
+ UICollectionView* collectionView = [self collectionView];
+ NSIndexPath* currentCellPath = [self indexPathForCurrentTextField];
+ DCHECK(currentCellPath);
+ NSIndexPath* nextCellPath =
+ [self indexPathWithSectionOffset:offset fromPath:currentCellPath];
+ if (nextCellPath) {
+ id nextCell = [collectionView cellForItemAtIndexPath:nextCellPath];
+ if ([nextCell isKindOfClass:[AutofillEditCell class]]) {
+ return base::mac::ObjCCastStrict<AutofillEditCell>(
+ [collectionView cellForItemAtIndexPath:nextCellPath]);
+ }
+ }
+ return nil;
+}
+
+- (void)updateAccessoryViewButtonsStates {
+ AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1];
+ [[_accessoryView previousButton] setEnabled:previousCell != nil];
+
+ AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1];
+ [[_accessoryView nextButton] setEnabled:nextCell != nil];
+}
+
+#pragma mark - Keyboard handling
+
+- (void)keyboardDidShow {
+ [self.collectionView scrollRectToVisible:[_currentEditingCell frame]
+ animated:YES];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698