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

Side by Side Diff: ios/chrome/browser/payments/payment_request_edit_view_controller.mm

Issue 2744823003: [Payment Request] Generic edit form (Closed)
Patch Set: rebase 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 unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2017 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "ios/chrome/browser/payments/payment_request_edit_view_controller.h"
6
7 #include "base/logging.h"
8 #import "base/mac/foundation_util.h"
9 #include "base/strings/sys_string_conversions.h"
10 #include "components/autofill/core/browser/field_types.h"
11 #include "components/strings/grit/components_strings.h"
12 #import "ios/chrome/browser/payments/cells/payments_text_item.h"
13 #import "ios/chrome/browser/payments/payment_request_edit_view_controller+intern al.h"
14 #import "ios/chrome/browser/payments/payment_request_edit_view_controller_action s.h"
15 #import "ios/chrome/browser/payments/payment_request_editor_field.h"
16 #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrom e.h"
17 #import "ios/chrome/browser/ui/collection_view/cells/collection_view_footer_item .h"
18 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h"
19 #import "ios/chrome/browser/ui/settings/autofill_edit_accessory_view.h"
20 #import "ios/chrome/browser/ui/settings/cells/autofill_edit_item.h"
21 #import "ios/chrome/browser/ui/uikit_ui_util.h"
22 #include "ios/chrome/grit/ios_theme_resources.h"
23 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h"
24 #include "ui/base/l10n/l10n_util.h"
25
26 #if !defined(__has_feature) || !__has_feature(objc_arc)
27 #error "This file requires ARC support."
28 #endif
29
30 namespace {
31
32 NSString* const kPaymentRequestEditCollectionViewID =
33 @"kPaymentRequestEditCollectionViewID";
34
35 const CGFloat kSeparatorEdgeInset = 14;
36
37 const CGFloat kFooterCellHorizontalPadding = 16;
38
39 // Returns the AutofillEditCell that is the parent view of the |textField|.
40 AutofillEditCell* AutofillEditCellForTextField(UITextField* textField) {
41 for (UIView* view = textField; view; view = [view superview]) {
42 AutofillEditCell* cell = base::mac::ObjCCast<AutofillEditCell>(view);
43 if (cell)
44 return cell;
45 }
46
47 // There has to be a cell associated with this text field.
48 NOTREACHED();
49 return nil;
50 }
51
52 typedef NS_ENUM(NSInteger, SectionIdentifier) {
53 SectionIdentifierFooter = kSectionIdentifierEnumZero,
54 SectionIdentifierFirstTextField,
55 };
56
57 typedef NS_ENUM(NSInteger, ItemType) {
58 ItemTypeFooter = kItemTypeEnumZero,
59 ItemTypeTextField, // This is a repeated item type.
60 ItemTypeErrorMessage, // This is a repeated item type.
61 };
62
63 } // namespace
64
65 @interface PaymentRequestEditViewController ()<
66 AutofillEditAccessoryDelegate,
67 PaymentRequestEditViewControllerActions,
68 PaymentRequestEditViewControllerValidator,
69 UITextFieldDelegate> {
70 NSArray<EditorField*>* _fields;
71
72 // The currently focused cell. May be nil.
73 __weak AutofillEditCell* _currentEditingCell;
74
75 AutofillEditAccessoryView* _accessoryView;
76 }
77
78 // Returns the indexPath for the same row as that of |indexPath| in a section
79 // with the given offset relative to that of |indexPath|. May return nil.
80 - (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset
81 fromPath:(NSIndexPath*)indexPath;
82
83 // Returns the text field with the given offset relative to the currently
84 // focused text field. May return nil.
85 - (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset;
86
87 // Enables or disables the accessory view's previous and next buttons depending
88 // on whether there is a text field before and after the currently focused text
89 // field.
90 - (void)updateAccessoryViewButtonsStates;
91
92 @end
93
94 @implementation PaymentRequestEditViewController
95
96 @synthesize editorDelegate = _editorDelegate;
97 @synthesize validatorDelegate = _validatorDelegate;
98
99 - (instancetype)initWithEditorFields:(NSArray<EditorField*>*)fields {
100 self = [super initWithStyle:CollectionViewControllerStyleAppBar];
101 if (self) {
102 _fields = fields;
103
104 // Set self as the validator delegate.
105 _validatorDelegate = self;
106
107 // Set up leading (cancel) button.
108 UIBarButtonItem* cancelButton = [[UIBarButtonItem alloc]
109 initWithTitle:l10n_util::GetNSString(IDS_CANCEL)
110 style:UIBarButtonItemStylePlain
111 target:nil
112 action:@selector(onReturn)];
113 [cancelButton setTitleTextAttributes:@{
114 NSForegroundColorAttributeName : [UIColor lightGrayColor]
115 }
116 forState:UIControlStateDisabled];
117 [cancelButton
118 setAccessibilityLabel:l10n_util::GetNSString(IDS_ACCNAME_CANCEL)];
119 [self navigationItem].leftBarButtonItem = cancelButton;
120
121 // Set up trailing (done) button.
122 UIBarButtonItem* doneButton =
123 [[UIBarButtonItem alloc] initWithTitle:l10n_util::GetNSString(IDS_DONE)
124 style:UIBarButtonItemStylePlain
125 target:nil
126 action:@selector(onDone)];
127 [doneButton setTitleTextAttributes:@{
128 NSForegroundColorAttributeName : [UIColor lightGrayColor]
129 }
130 forState:UIControlStateDisabled];
131 [doneButton setAccessibilityLabel:l10n_util::GetNSString(IDS_ACCNAME_DONE)];
132 [self navigationItem].rightBarButtonItem = doneButton;
133
134 _accessoryView = [[AutofillEditAccessoryView alloc] initWithDelegate:self];
135 }
136 return self;
137 }
138
139 #pragma mark - PaymentRequestEditViewControllerActions methods
140
141 - (void)onReturn {
142 [_editorDelegate paymentRequestEditViewControllerDidReturn:self];
143 }
144
145 - (void)onDone {
146 if ([self validateForm]) {
147 [_editorDelegate paymentRequestEditViewController:self
148 didFinishEditingFields:_fields];
149 }
150 }
151
152 - (void)viewDidAppear:(BOOL)animated {
153 [super viewDidAppear:animated];
154 [[NSNotificationCenter defaultCenter]
155 addObserver:self
156 selector:@selector(keyboardDidShow)
157 name:UIKeyboardDidShowNotification
158 object:nil];
159 }
160
161 - (void)viewWillDisappear:(BOOL)animated {
162 [super viewWillDisappear:animated];
163 [[NSNotificationCenter defaultCenter]
164 removeObserver:self
165 name:UIKeyboardDidShowNotification
166 object:nil];
167 }
168
169 #pragma mark - CollectionViewController methods
170
171 - (void)loadModel {
172 [super loadModel];
173 CollectionViewModel* model = self.collectionViewModel;
174
175 // Iterate over the fields and add the respective sections and items.
176 int sectionIdentifier = static_cast<int>(SectionIdentifierFirstTextField);
177 for (EditorField* field in _fields) {
178 [model addSectionWithIdentifier:sectionIdentifier];
179 AutofillEditItem* item =
180 [[AutofillEditItem alloc] initWithType:ItemTypeTextField];
181 item.textFieldName = field.label;
182 item.textFieldEnabled = YES;
183 item.textFieldValue = field.value;
184 item.required = field.isRequired;
185 item.autofillType =
186 static_cast<autofill::ServerFieldType>(field.autofillType);
187 [model addItem:item
188 toSectionWithIdentifier:static_cast<NSInteger>(sectionIdentifier)];
189 field.item = item;
190 field.sectionIdentifier = static_cast<NSInteger>(sectionIdentifier);
191 ++sectionIdentifier;
192 }
193
194 [self loadFooterItems];
195 }
196
197 - (void)viewDidLoad {
198 [super viewDidLoad];
199
200 self.collectionView.accessibilityIdentifier =
201 kPaymentRequestEditCollectionViewID;
202
203 // Customize collection view settings.
204 self.styler.cellStyle = MDCCollectionViewCellStyleCard;
205 self.styler.separatorInset =
206 UIEdgeInsetsMake(0, kSeparatorEdgeInset, 0, kSeparatorEdgeInset);
207 }
208
209 #pragma mark - UITextFieldDelegate
210
211 - (void)textFieldDidBeginEditing:(UITextField*)textField {
212 _currentEditingCell = AutofillEditCellForTextField(textField);
213 [textField setInputAccessoryView:_accessoryView];
214 [self updateAccessoryViewButtonsStates];
215 }
216
217 - (void)textFieldDidEndEditing:(UITextField*)textField {
218 DCHECK(_currentEditingCell == AutofillEditCellForTextField(textField));
219
220 // Validate the text field.
221 CollectionViewModel* model = self.collectionViewModel;
222
223 NSIndexPath* indexPath = [self indexPathForCurrentTextField];
224 AutofillEditItem* item = base::mac::ObjCCastStrict<AutofillEditItem>(
225 [model itemAtIndexPath:indexPath]);
226
227 NSString* errorMessage =
228 [_validatorDelegate paymentRequestEditViewController:self
229 validateValue:textField.text
230 autofillType:item.autofillType
231 required:item.required];
232 NSInteger sectionIdentifier =
233 [model sectionIdentifierForSection:[indexPath section]];
234 [self addOrRemoveErrorMessage:errorMessage
235 inSectionWithIdentifier:sectionIdentifier];
236
237 [textField setInputAccessoryView:nil];
238 _currentEditingCell = nil;
239 }
240
241 - (BOOL)textFieldShouldReturn:(UITextField*)textField {
242 DCHECK([_currentEditingCell textField] == textField);
243 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1];
244 if (nextCell)
245 [self nextPressed];
246 else
247 [self closePressed];
248
249 return NO;
250 }
251
252 #pragma mark - AutofillEditAccessoryDelegate
253
254 - (void)nextPressed {
255 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1];
256 if (nextCell)
257 [nextCell.textField becomeFirstResponder];
258 }
259
260 - (void)previousPressed {
261 AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1];
262 if (previousCell)
263 [previousCell.textField becomeFirstResponder];
264 }
265
266 - (void)closePressed {
267 [[_currentEditingCell textField] resignFirstResponder];
268 }
269
270 #pragma mark - UICollectionViewDataSource
271
272 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView
273 cellForItemAtIndexPath:(NSIndexPath*)indexPath {
274 UICollectionViewCell* cell =
275 [super collectionView:collectionView cellForItemAtIndexPath:indexPath];
276
277 CollectionViewItem* item =
278 [self.collectionViewModel itemAtIndexPath:indexPath];
279 switch (item.type) {
280 case ItemTypeTextField: {
281 AutofillEditCell* autofillEditCell =
282 base::mac::ObjCCast<AutofillEditCell>(cell);
283 autofillEditCell.textField.delegate = self;
284 autofillEditCell.textField.clearButtonMode = UITextFieldViewModeNever;
285 autofillEditCell.textLabel.font = [MDCTypography body2Font];
286 autofillEditCell.textLabel.textColor = [[MDCPalette greyPalette] tint900];
287 autofillEditCell.textField.font = [MDCTypography body1Font];
288 autofillEditCell.textField.textColor =
289 [[MDCPalette cr_bluePalette] tint600];
290 break;
291 }
292 case ItemTypeErrorMessage: {
293 PaymentsTextCell* errorMessageCell =
294 base::mac::ObjCCastStrict<PaymentsTextCell>(cell);
295 errorMessageCell.textLabel.font = [MDCTypography body1Font];
296 errorMessageCell.textLabel.textColor =
297 [[MDCPalette cr_redPalette] tint600];
298 break;
299 }
300 case ItemTypeFooter: {
301 CollectionViewFooterCell* footerCell =
302 base::mac::ObjCCastStrict<CollectionViewFooterCell>(cell);
303 footerCell.textLabel.font = [MDCTypography body2Font];
304 footerCell.textLabel.textColor = [[MDCPalette greyPalette] tint600];
305 footerCell.textLabel.shadowColor = nil; // No shadow.
306 footerCell.horizontalPadding = kFooterCellHorizontalPadding;
307 break;
308 }
309 default:
310 break;
311 }
312
313 return cell;
314 }
315
316 #pragma mark MDCCollectionViewStylingDelegate
317
318 - (CGFloat)collectionView:(UICollectionView*)collectionView
319 cellHeightAtIndexPath:(NSIndexPath*)indexPath {
320 CollectionViewItem* item =
321 [self.collectionViewModel itemAtIndexPath:indexPath];
322 switch (item.type) {
323 case ItemTypeTextField:
324 case ItemTypeErrorMessage:
325 case ItemTypeFooter:
326 return [MDCCollectionViewCell
327 cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds)
328 forItem:item];
329 default:
330 NOTREACHED();
331 return MDCCellDefaultOneLineHeight;
332 }
333 }
334
335 - (BOOL)collectionView:(UICollectionView*)collectionView
336 hidesInkViewAtIndexPath:(NSIndexPath*)indexPath {
337 NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath];
338 switch (type) {
339 case ItemTypeErrorMessage:
340 case ItemTypeFooter:
341 return YES;
342 default:
343 return NO;
344 }
345 }
346
347 - (BOOL)collectionView:(UICollectionView*)collectionView
348 shouldHideItemBackgroundAtIndexPath:(NSIndexPath*)indexPath {
349 NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath];
350 switch (type) {
351 case ItemTypeFooter:
352 return YES;
353 default:
354 return NO;
355 }
356 }
357
358 #pragma mark - PaymentRequestEditViewControllerValidator
359
360 - (NSString*)paymentRequestEditViewController:
361 (PaymentRequestEditViewController*)controller
362 validateValue:(NSString*)value
363 autofillType:(NSInteger)autofillType
364 required:(BOOL)required {
365 if (required && value.length == 0) {
366 return l10n_util::GetNSString(
367 IDS_PAYMENTS_FIELD_REQUIRED_VALIDATION_MESSAGE);
368 }
369 return @"";
370 }
371
372 #pragma mark - Helper methods
373
374 - (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset
375 fromPath:(NSIndexPath*)indexPath {
376 DCHECK(indexPath);
377 DCHECK(offset);
378 NSInteger nextSection = [indexPath section] + offset;
379 if (nextSection >= 0 &&
380 nextSection < [[self collectionView] numberOfSections]) {
381 return [NSIndexPath indexPathForRow:[indexPath row] inSection:nextSection];
382 }
383 return nil;
384 }
385
386 - (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset {
387 UICollectionView* collectionView = [self collectionView];
388 NSIndexPath* currentCellPath = [self indexPathForCurrentTextField];
389 DCHECK(currentCellPath);
390 NSIndexPath* nextCellPath =
391 [self indexPathWithSectionOffset:offset fromPath:currentCellPath];
392 if (nextCellPath) {
393 id nextCell = [collectionView cellForItemAtIndexPath:nextCellPath];
394 if ([nextCell isKindOfClass:[AutofillEditCell class]]) {
395 return base::mac::ObjCCastStrict<AutofillEditCell>(
396 [collectionView cellForItemAtIndexPath:nextCellPath]);
397 }
398 }
399 return nil;
400 }
401
402 - (void)updateAccessoryViewButtonsStates {
403 AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1];
404 [[_accessoryView previousButton] setEnabled:previousCell != nil];
405
406 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1];
407 [[_accessoryView nextButton] setEnabled:nextCell != nil];
408 }
409
410 #pragma mark - Keyboard handling
411
412 - (void)keyboardDidShow {
413 [self.collectionView
414 scrollToItemAtIndexPath:[self.collectionView
415 indexPathForCell:_currentEditingCell]
416 atScrollPosition:UICollectionViewScrollPositionCenteredVertically
417 animated:YES];
418 }
419
420 @end
421
422 @implementation PaymentRequestEditViewController (Internal)
423
424 - (BOOL)validateForm {
425 for (EditorField* field in _fields) {
426 AutofillEditItem* item = field.item;
427
428 NSString* errorMessage =
429 [_validatorDelegate paymentRequestEditViewController:self
430 validateValue:item.textFieldValue
431 autofillType:field.autofillType
432 required:field.isRequired];
433 [self addOrRemoveErrorMessage:errorMessage
434 inSectionWithIdentifier:field.sectionIdentifier];
435 if (errorMessage.length != 0) {
436 return NO;
437 }
438
439 field.value = item.textFieldValue;
440 }
441 return YES;
442 }
443
444 - (void)loadFooterItems {
445 CollectionViewModel* model = self.collectionViewModel;
446
447 [model addSectionWithIdentifier:SectionIdentifierFooter];
448 CollectionViewFooterItem* footerItem =
449 [[CollectionViewFooterItem alloc] initWithType:ItemTypeFooter];
450 footerItem.text = l10n_util::GetNSString(IDS_PAYMENTS_REQUIRED_FIELD_MESSAGE);
451 [model addItem:footerItem toSectionWithIdentifier:SectionIdentifierFooter];
452 }
453
454 - (NSIndexPath*)indexPathForCurrentTextField {
455 DCHECK(_currentEditingCell);
456 NSIndexPath* indexPath =
457 [[self collectionView] indexPathForCell:_currentEditingCell];
458 DCHECK(indexPath);
459 return indexPath;
460 }
461
462 - (void)addOrRemoveErrorMessage:(NSString*)errorMessage
463 inSectionWithIdentifier:(NSInteger)sectionIdentifier {
464 CollectionViewModel* model = self.collectionViewModel;
465 if ([model hasItemForItemType:ItemTypeErrorMessage
466 sectionIdentifier:sectionIdentifier]) {
467 NSIndexPath* indexPath = [model indexPathForItemType:ItemTypeErrorMessage
468 sectionIdentifier:sectionIdentifier];
469 if (errorMessage.length == 0) {
470 // Remove the item at the index path.
471 [model removeItemWithType:ItemTypeErrorMessage
472 fromSectionWithIdentifier:sectionIdentifier];
473 [self.collectionView deleteItemsAtIndexPaths:@[ indexPath ]];
474 } else {
475 // Reload the item at the index path.
476 PaymentsTextItem* item = base::mac::ObjCCastStrict<PaymentsTextItem>(
477 [model itemAtIndexPath:indexPath]);
478 item.text = errorMessage;
479 [self.collectionView reloadItemsAtIndexPaths:@[ indexPath ]];
480 }
481 } else if (errorMessage.length != 0) {
482 // Insert an item at the index path.
483 PaymentsTextItem* errorMessageItem =
484 [[PaymentsTextItem alloc] initWithType:ItemTypeErrorMessage];
485 errorMessageItem.text = errorMessage;
486 errorMessageItem.image = NativeImage(IDR_IOS_PAYMENTS_WARNING);
487 [model addItem:errorMessageItem toSectionWithIdentifier:sectionIdentifier];
488 NSIndexPath* indexPath = [model indexPathForItemType:ItemTypeErrorMessage
489 sectionIdentifier:sectionIdentifier];
490 [self.collectionView insertItemsAtIndexPaths:@[ indexPath ]];
491 }
492 }
493
494 @end
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698