Chromium Code Reviews| OLD | NEW |
|---|---|
| (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/strings/grit/components_strings.h" | |
| 11 #import "ios/chrome/browser/payments/cells/payments_text_item.h" | |
| 12 #import "ios/chrome/browser/ui/collection_view/cells/MDCCollectionViewCell+Chrom e.h" | |
| 13 #import "ios/chrome/browser/ui/colors/MDCPalette+CrAdditions.h" | |
| 14 #import "ios/chrome/browser/ui/settings/autofill_edit_accessory_view.h" | |
| 15 #import "ios/chrome/browser/ui/settings/cells/autofill_edit_item.h" | |
| 16 #import "ios/chrome/browser/ui/uikit_ui_util.h" | |
| 17 #include "ios/chrome/grit/ios_theme_resources.h" | |
| 18 #import "ios/third_party/material_components_ios/src/components/Typography/src/M aterialTypography.h" | |
| 19 #include "ui/base/l10n/l10n_util.h" | |
| 20 | |
| 21 #if !defined(__has_feature) || !__has_feature(objc_arc) | |
| 22 #error "This file requires ARC support." | |
| 23 #endif | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 const CGFloat kSeparatorEdgeInset = 14; | |
| 28 | |
| 29 AutofillEditCell* AutofillEditCellForTextField(UITextField* textField) { | |
| 30 for (UIView* view = textField; view; view = [view superview]) { | |
| 31 AutofillEditCell* cell = base::mac::ObjCCast<AutofillEditCell>(view); | |
| 32 if (cell) | |
| 33 return cell; | |
| 34 } | |
| 35 | |
| 36 // There has to be a cell associated with this text field. | |
| 37 NOTREACHED(); | |
| 38 return nil; | |
| 39 } | |
| 40 | |
| 41 } // namespace | |
| 42 | |
| 43 @interface PaymentRequestEditViewController ()<AutofillEditAccessoryDelegate> { | |
| 44 // The currently focused cell. May be nil. | |
| 45 __weak AutofillEditCell* _currentEditingCell; | |
| 46 | |
| 47 AutofillEditAccessoryView* _accessoryView; | |
| 48 } | |
| 49 | |
| 50 // Returns the index path for the cell associated with the currently focused | |
| 51 // text field. | |
| 52 - (NSIndexPath*)indexPathForCurrentTextField; | |
| 53 | |
| 54 // Returns the validation error string for |value| for the autofill type | |
| 55 // |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 :)
| |
| 56 // 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.
| |
| 57 - (NSString*)validateValue:(NSString*)value | |
| 58 autofillType:(autofill::ServerFieldType)autofillType | |
| 59 required:(BOOL)required; | |
| 60 | |
| 61 // Adds an error message item in the section |sectionIdentifier| if | |
| 62 // |errorMessage| is non-empty. Otherwise removes such an item if one exists. | |
| 63 - (void)addOrRemoveErrorMessage:(NSString*)errorMessage | |
| 64 inSectionWithIdentifier:(NSInteger)sectionIdentifier; | |
| 65 | |
| 66 // Returns the indexPath for the same row as that of |indexPath| in a section | |
| 67 // with the given offset relative to that of |indexPath|. It may return nil. | |
| 68 - (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset | |
| 69 fromPath:(NSIndexPath*)indexPath; | |
| 70 | |
| 71 // Returns the text field with the given offset relative to the currently | |
| 72 // focused text field. May return nil. | |
| 73 - (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset; | |
| 74 | |
| 75 // 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.
| |
| 76 // 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...
| |
| 77 // field. | |
| 78 - (void)updateAccessoryViewButtonState; | |
|
lpromero
2017/03/16 13:01:06
There should be a plural somewhere. updateAccessor
Moe
2017/03/16 15:44:23
Done.
| |
| 79 | |
| 80 @end | |
| 81 | |
| 82 @implementation PaymentRequestEditViewController | |
| 83 | |
| 84 - (instancetype)init { | |
| 85 self = [super initWithStyle:CollectionViewControllerStyleAppBar]; | |
| 86 if (self) { | |
| 87 _accessoryView = [[AutofillEditAccessoryView alloc] initWithDelegate:self]; | |
| 88 } | |
| 89 return self; | |
| 90 } | |
| 91 | |
| 92 - (void)viewDidAppear:(BOOL)animated { | |
| 93 [super viewDidAppear:animated]; | |
| 94 [[NSNotificationCenter defaultCenter] | |
| 95 addObserver:self | |
| 96 selector:@selector(keyboardDidShow) | |
| 97 name:UIKeyboardDidShowNotification | |
| 98 object:nil]; | |
| 99 } | |
| 100 | |
| 101 - (void)viewWillDisappear:(BOOL)animated { | |
| 102 [super viewWillDisappear:animated]; | |
| 103 [[NSNotificationCenter defaultCenter] | |
| 104 removeObserver:self | |
| 105 name:UIKeyboardDidShowNotification | |
| 106 object:nil]; | |
| 107 } | |
| 108 | |
| 109 #pragma mark - CollectionViewController methods | |
| 110 | |
| 111 - (void)loadModel { | |
| 112 [super loadModel]; | |
| 113 CollectionViewModel* model = self.collectionViewModel; | |
| 114 | |
| 115 // Iterate over the field definitions and add the respective sections and | |
| 116 // items. | |
| 117 for (const auto& field : [self editorFields]) { | |
| 118 [model addSectionWithIdentifier:field.section_id]; | |
| 119 AutofillEditItem* item = | |
| 120 [[AutofillEditItem alloc] initWithType:field.item_type]; | |
| 121 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.
| |
| 122 item.textFieldName = [NSString | |
| 123 stringWithFormat:labelFormat, base::SysUTF16ToNSString(field.label)]; | |
| 124 item.textFieldEnabled = YES; | |
| 125 item.required = field.required; | |
| 126 item.autofillType = field.data_type; | |
| 127 [model addItem:item toSectionWithIdentifier:field.section_id]; | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 - (void)viewDidLoad { | |
| 132 [super viewDidLoad]; | |
| 133 | |
| 134 // Customize collection view settings. | |
| 135 self.styler.cellStyle = MDCCollectionViewCellStyleCard; | |
| 136 self.styler.separatorInset = | |
| 137 UIEdgeInsetsMake(0, kSeparatorEdgeInset, 0, kSeparatorEdgeInset); | |
| 138 } | |
| 139 | |
| 140 #pragma mark - UITextFieldDelegate | |
| 141 | |
| 142 - (void)textFieldDidBeginEditing:(UITextField*)textField { | |
| 143 _currentEditingCell = AutofillEditCellForTextField(textField); | |
| 144 [textField setInputAccessoryView:_accessoryView]; | |
| 145 [self updateAccessoryViewButtonState]; | |
| 146 } | |
| 147 | |
| 148 - (void)textFieldDidEndEditing:(UITextField*)textField { | |
| 149 DCHECK(_currentEditingCell == AutofillEditCellForTextField(textField)); | |
| 150 CollectionViewModel* model = self.collectionViewModel; | |
| 151 | |
| 152 // Validate the text field. If there is a validation error, display an error | |
| 153 // message item in the same section as the field. Otherwise remove the error | |
| 154 // message item in that section. | |
| 155 NSIndexPath* indexPath = [self indexPathForCurrentTextField]; | |
| 156 AutofillEditItem* item = base::mac::ObjCCastStrict<AutofillEditItem>( | |
| 157 [model itemAtIndexPath:indexPath]); | |
| 158 | |
| 159 NSString* errorMessage = [self validateValue:textField.text | |
| 160 autofillType:item.autofillType | |
| 161 required:item.required]; | |
| 162 NSInteger sectionIdentifier = | |
| 163 [model sectionIdentifierForSection:[indexPath section]]; | |
| 164 [self addOrRemoveErrorMessage:errorMessage | |
| 165 inSectionWithIdentifier:sectionIdentifier]; | |
| 166 | |
| 167 [textField setInputAccessoryView:nil]; | |
| 168 _currentEditingCell = nil; | |
| 169 } | |
| 170 | |
| 171 - (BOOL)textFieldShouldReturn:(UITextField*)textField { | |
| 172 DCHECK([_currentEditingCell textField] == textField); | |
| 173 [self nextPressed]; | |
| 174 return NO; | |
| 175 } | |
| 176 | |
| 177 #pragma mark - AutofillEditAccessoryDelegate | |
| 178 | |
| 179 - (void)nextPressed { | |
| 180 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1]; | |
| 181 if (nextCell) | |
| 182 [nextCell.textField becomeFirstResponder]; | |
| 183 } | |
| 184 | |
| 185 - (void)previousPressed { | |
| 186 AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1]; | |
| 187 if (previousCell) | |
| 188 [previousCell.textField becomeFirstResponder]; | |
| 189 } | |
| 190 | |
| 191 - (void)closePressed { | |
| 192 [[_currentEditingCell textField] resignFirstResponder]; | |
| 193 } | |
| 194 | |
| 195 #pragma mark - UICollectionViewDataSource | |
| 196 | |
| 197 - (UICollectionViewCell*)collectionView:(UICollectionView*)collectionView | |
| 198 cellForItemAtIndexPath:(NSIndexPath*)indexPath { | |
| 199 UICollectionViewCell* cell = | |
| 200 [super collectionView:collectionView cellForItemAtIndexPath:indexPath]; | |
| 201 | |
| 202 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.
| |
| 203 AutofillEditCell* autofillEditCell = | |
| 204 base::mac::ObjCCast<AutofillEditCell>(cell); | |
| 205 autofillEditCell.textField.delegate = self; | |
| 206 } | |
| 207 | |
| 208 NSInteger itemType = | |
| 209 [self.collectionViewModel itemTypeForIndexPath:indexPath]; | |
| 210 switch (itemType) { | |
| 211 case ItemTypeErrorMessage: { | |
| 212 PaymentsTextCell* errorMessageCell = | |
| 213 base::mac::ObjCCastStrict<PaymentsTextCell>(cell); | |
| 214 errorMessageCell.textLabel.font = [MDCTypography body1Font]; | |
| 215 errorMessageCell.textLabel.textColor = | |
| 216 [[MDCPalette cr_redPalette] tint600]; | |
| 217 break; | |
| 218 } | |
| 219 default: | |
| 220 break; | |
| 221 } | |
| 222 | |
| 223 return cell; | |
| 224 } | |
| 225 | |
| 226 #pragma mark MDCCollectionViewStylingDelegate | |
| 227 | |
| 228 - (CGFloat)collectionView:(UICollectionView*)collectionView | |
| 229 cellHeightAtIndexPath:(NSIndexPath*)indexPath { | |
| 230 CollectionViewItem* item = | |
| 231 [self.collectionViewModel itemAtIndexPath:indexPath]; | |
| 232 switch (item.type) { | |
| 233 case ItemTypeErrorMessage: | |
| 234 return [MDCCollectionViewCell | |
| 235 cr_preferredHeightForWidth:CGRectGetWidth(collectionView.bounds) | |
| 236 forItem:item]; | |
| 237 default: | |
| 238 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
| |
| 239 return MDCCellDefaultOneLineHeight; | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 - (BOOL)collectionView:(UICollectionView*)collectionView | |
| 244 hidesInkViewAtIndexPath:(NSIndexPath*)indexPath { | |
| 245 NSInteger type = [self.collectionViewModel itemTypeForIndexPath:indexPath]; | |
| 246 switch (type) { | |
| 247 case ItemTypeErrorMessage: | |
| 248 return YES; | |
| 249 default: | |
| 250 return NO; | |
| 251 } | |
| 252 } | |
| 253 | |
| 254 #pragma mark - Protected methods | |
| 255 | |
| 256 - (std::vector<EditorField>)editorFields { | |
| 257 return std::vector<EditorField>(); | |
| 258 } | |
| 259 | |
| 260 - (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.
| |
| 261 DCHECK(_currentEditingCell); | |
| 262 NSIndexPath* indexPath = | |
| 263 [[self collectionView] indexPathForCell:_currentEditingCell]; | |
| 264 DCHECK(indexPath); | |
| 265 return indexPath; | |
| 266 } | |
| 267 | |
| 268 #pragma mark - Helper methods | |
| 269 | |
| 270 - (NSString*)validateValue:(NSString*)value | |
| 271 autofillType:(autofill::ServerFieldType)autofillType | |
| 272 required:(BOOL)required { | |
| 273 if (required && value.length == 0) { | |
| 274 return l10n_util::GetNSString( | |
| 275 IDS_PAYMENTS_FIELD_REQUIRED_VALIDATION_MESSAGE); | |
| 276 } | |
| 277 return @""; | |
| 278 } | |
| 279 | |
| 280 - (void)addOrRemoveErrorMessage:(NSString*)errorMessage | |
| 281 inSectionWithIdentifier:(NSInteger)sectionIdentifier { | |
| 282 CollectionViewModel* model = self.collectionViewModel; | |
| 283 if ([model hasItemForItemType:ItemTypeErrorMessage | |
| 284 sectionIdentifier:sectionIdentifier]) { | |
| 285 [model removeItemWithType:ItemTypeErrorMessage | |
| 286 fromSectionWithIdentifier:sectionIdentifier]; | |
| 287 } | |
| 288 | |
| 289 if (errorMessage.length != 0) { | |
| 290 PaymentsTextItem* errorMessageItem = | |
| 291 [[PaymentsTextItem alloc] initWithType:ItemTypeErrorMessage]; | |
| 292 errorMessageItem.text = errorMessage; | |
| 293 errorMessageItem.image = NativeImage(IDR_IOS_PAYMENTS_WARNING); | |
| 294 [model addItem:errorMessageItem toSectionWithIdentifier:sectionIdentifier]; | |
| 295 } | |
| 296 | |
| 297 // Update the entire section. | |
| 298 NSInteger section = [model sectionForSectionIdentifier:sectionIdentifier]; | |
| 299 [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.
| |
| 300 } | |
| 301 | |
| 302 - (NSIndexPath*)indexPathWithSectionOffset:(NSInteger)offset | |
| 303 fromPath:(NSIndexPath*)indexPath { | |
| 304 DCHECK(indexPath); | |
| 305 DCHECK(offset); | |
| 306 NSInteger nextSection = [indexPath section] + offset; | |
| 307 if (nextSection >= 0 && | |
| 308 nextSection < [[self collectionView] numberOfSections]) { | |
| 309 return [NSIndexPath indexPathForRow:[indexPath row] inSection:nextSection]; | |
| 310 } | |
| 311 return nil; | |
| 312 } | |
| 313 | |
| 314 - (AutofillEditCell*)nextTextFieldWithOffset:(NSInteger)offset { | |
| 315 UICollectionView* collectionView = [self collectionView]; | |
| 316 NSIndexPath* currentCellPath = [self indexPathForCurrentTextField]; | |
| 317 DCHECK(currentCellPath); | |
| 318 NSIndexPath* nextCellPath = | |
| 319 [self indexPathWithSectionOffset:offset fromPath:currentCellPath]; | |
| 320 if (nextCellPath) { | |
| 321 id nextCell = [collectionView cellForItemAtIndexPath:nextCellPath]; | |
| 322 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.
| |
| 323 return base::mac::ObjCCastStrict<AutofillEditCell>( | |
| 324 [collectionView cellForItemAtIndexPath:nextCellPath]); | |
| 325 } | |
| 326 } | |
| 327 return nil; | |
| 328 } | |
| 329 | |
| 330 - (void)updateAccessoryViewButtonState { | |
| 331 AutofillEditCell* previousCell = [self nextTextFieldWithOffset:-1]; | |
| 332 [[_accessoryView previousButton] setEnabled:previousCell != nil]; | |
| 333 | |
| 334 AutofillEditCell* nextCell = [self nextTextFieldWithOffset:1]; | |
| 335 [[_accessoryView nextButton] setEnabled:nextCell != nil]; | |
| 336 } | |
| 337 | |
| 338 #pragma mark - Keyboard handling | |
| 339 | |
| 340 - (void)keyboardDidShow { | |
|
lpromero
2017/03/16 13:01:06
Weird, I thought UICollectionViewController alread
| |
| 341 [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
| |
| 342 animated:YES]; | |
| 343 } | |
| 344 | |
| 345 @end | |
| OLD | NEW |