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

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

Issue 2744823003: [Payment Request] Generic edit form (Closed)
Patch Set: Generic edit form + showCase 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..de779afaeda234f7f88f7e6bd76a4a3f2b971090
--- /dev/null
+++ b/ios/chrome/browser/payments/payment_request_edit_view_controller.mm
@@ -0,0 +1,345 @@
+// 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;
+}
+
+} // 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 wether this is a required field or not.
lpromero 2017/03/16 13:01:07 s/wether/whether. I just found out "wether" exists
Moe 2017/03/16 15:44:23 Done! definitely whether and not wether here :)
+// If there are no validation errors an empty string is returned.
lpromero 2017/03/16 13:01:07 Comma after "errors".
Moe 2017/03/16 15:44:23 Done.
+- (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
lpromero 2017/03/16 13:01:06 s/Disables/disables
Moe 2017/03/16 15:44:22 Done.
+// on wehther there is a text field before and after the currently focused text
lpromero 2017/03/16 13:01:06 whether
Moe 2017/03/16 15:44:23 Done. Sorry. dyslexia...
+// field.
+- (void)updateAccessoryViewButtonState;
lpromero 2017/03/16 13:01:06 There should be a plural somewhere. updateAccessor
Moe 2017/03/16 15:44:23 Done.
+
+@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];
+ NSString* labelFormat = field.required ? @"%@*" : @"%@";
lpromero 2017/03/16 13:01:07 Since AutofillEditItem knows about the "required"
Moe 2017/03/16 15:44:23 Done.
+ item.textFieldName = [NSString
+ stringWithFormat:labelFormat, 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 updateAccessoryViewButtonState];
+}
+
+- (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 isMemberOfClass:[AutofillEditCell class]]) {
lpromero 2017/03/16 13:01:07 Optional nit: is isKindOfClass more appropriate? W
Moe 2017/03/16 15:44:23 Done.
+ AutofillEditCell* autofillEditCell =
+ base::mac::ObjCCast<AutofillEditCell>(cell);
+ autofillEditCell.textField.delegate = self;
+ }
+
+ NSInteger itemType =
+ [self.collectionViewModel itemTypeForIndexPath:indexPath];
+ switch (itemType) {
+ 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];
+ switch (item.type) {
+ case ItemTypeErrorMessage:
+ return [MDCCollectionViewCell
+ cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
+ forItem:item];
+ default:
+ NOTREACHED();
lpromero 2017/03/16 13:01:06 How come we don't reach this section? The text fie
Moe 2017/03/16 15:44:22 That's the subclasses who decide what the item typ
+ 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>();
+}
+
+- (NSIndexPath*)indexPathForCurrentTextField {
lpromero 2017/03/16 13:01:07 This one is not protected, it is private.
Moe 2017/03/16 15:44:23 Done.
+ DCHECK(_currentEditingCell);
+ NSIndexPath* indexPath =
+ [[self collectionView] indexPathForCell:_currentEditingCell];
+ DCHECK(indexPath);
+ return indexPath;
+}
+
+#pragma mark - Helper methods
+
+- (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]) {
+ [model removeItemWithType:ItemTypeErrorMessage
+ fromSectionWithIdentifier:sectionIdentifier];
+ }
+
+ if (errorMessage.length != 0) {
+ PaymentsTextItem* errorMessageItem =
+ [[PaymentsTextItem alloc] initWithType:ItemTypeErrorMessage];
+ errorMessageItem.text = errorMessage;
+ errorMessageItem.image = NativeImage(IDR_IOS_PAYMENTS_WARNING);
+ [model addItem:errorMessageItem toSectionWithIdentifier:sectionIdentifier];
+ }
+
+ // Update the entire section.
+ NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier];
+ [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:section]];
lpromero 2017/03/16 13:01:07 Does that mean that the error message doesn't anim
Moe 2017/03/16 15:44:23 Done.
+}
+
+- (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 isMemberOfClass:[AutofillEditCell class]]) {
lpromero 2017/03/16 13:01:06 Idem, would isKindOfClass be better?
Moe 2017/03/16 15:44:23 Done.
+ return base::mac::ObjCCastStrict<AutofillEditCell>(
+ [collectionView cellForItemAtIndexPath:nextCellPath]);
+ }
+ }
+ return nil;
+}
+
+- (void)updateAccessoryViewButtonState {
+ 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 {
lpromero 2017/03/16 13:01:06 Weird, I thought UICollectionViewController alread
+ [self.collectionView scrollRectToVisible:[_currentEditingCell frame]
lpromero 2017/03/16 13:01:06 No action needed: I wonder if using [self.collecti
Moe 2017/03/16 15:44:23 I copied this from autofill_edit_collection_view_c
lpromero 2017/03/16 16:08:08 Ok. For the up/down button, it doesn't apply here,
Moe 2017/03/21 04:22:14 true about the up/down buttons. If I'm understandi
+ animated:YES];
+}
+
+@end

Powered by Google App Engine
This is Rietveld 408576698