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

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

Powered by Google App Engine
This is Rietveld 408576698