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

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

Powered by Google App Engine
This is Rietveld 408576698