Skip to content

Commit

Permalink
[Payment Request] Picker view + showcase integration + egtests
Browse files Browse the repository at this point in the history
http://imgur.com/a/S1tcB

BUG=602666

Review-Url: https://codereview.chromium.org/2778343002
Cr-Commit-Position: refs/heads/master@{#461989}
  • Loading branch information
mahmadi authored and Commit bot committed Apr 5, 2017
1 parent b4ad68a commit 14a3ede
Show file tree
Hide file tree
Showing 11 changed files with 759 additions and 1 deletion.
4 changes: 4 additions & 0 deletions ios/chrome/browser/payments/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ source_set("payments_ui") {
"payment_request_edit_view_controller_actions.h",
"payment_request_editor_field.h",
"payment_request_editor_field.mm",
"payment_request_picker_row.h",
"payment_request_picker_row.mm",
"payment_request_picker_view_controller.h",
"payment_request_picker_view_controller.mm",
]
deps = [
"//base",
Expand Down
22 changes: 22 additions & 0 deletions ios/chrome/browser/payments/payment_request_picker_row.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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.

#ifndef IOS_CHROME_BROWSER_PAYMENTS_PAYMENT_REQUEST_PICKER_ROW_H_
#define IOS_CHROME_BROWSER_PAYMENTS_PAYMENT_REQUEST_PICKER_ROW_H_

#import <Foundation/Foundation.h>

// The model object for a row in the payment request picker.
@interface PickerRow : NSObject

// The label for the row. This is displayed in the UI.
@property(nonatomic, copy) NSString* label;
// The value for the row. This is not displayed in the UI and is optional.
@property(nonatomic, copy) NSString* value;

- (instancetype)initWithLabel:(NSString*)label value:(NSString*)value;

@end

#endif // IOS_CHROME_BROWSER_PAYMENTS_PAYMENT_REQUEST_PICKER_ROW_H_
29 changes: 29 additions & 0 deletions ios/chrome/browser/payments/payment_request_picker_row.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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_picker_row.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

@implementation PickerRow

@synthesize label = _label;
@synthesize value = _value;

- (instancetype)initWithLabel:(NSString*)label value:(NSString*)value {
self = [super init];
if (self) {
_label = label;
_value = value;
}
return self;
}

- (NSString*)description {
return [NSString stringWithFormat:@"Label: %@, Value: %@", _label, _value];
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// 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.

#ifndef IOS_CHROME_BROWSER_PAYMENT_REQUEST_PICKER_VIEW_CONTROLLER_H_
#define IOS_CHROME_BROWSER_PAYMENT_REQUEST_PICKER_VIEW_CONTROLLER_H_

#import <UIKit/UIKit.h>

extern NSString* const kPaymentRequestPickerRowAccessibilityID;
extern NSString* const kPaymentRequestPickerSearchBarAccessibilityID;

@class PaymentRequestPickerViewController;
@class PickerRow;

// Delegate protocol for PaymentRequestPickerViewController.
@protocol PaymentRequestPickerViewControllerDelegate<NSObject>

// Notifies the delegate that the user has selected a row.
- (void)paymentRequestPickerViewController:
(PaymentRequestPickerViewController*)controller
didSelectRow:(PickerRow*)row;

@end

// TableViewController that displays a searchable list of rows featuring a
// selected row as well as an index list.
@interface PaymentRequestPickerViewController : UITableViewController

// The delegate to be notified when the user selects a row.
@property(nonatomic, weak) id<PaymentRequestPickerViewControllerDelegate>
delegate;

// Initializes the tableView with a list of rows and an optional selected row.
- (instancetype)initWithRows:(NSArray<PickerRow*>*)rows
selected:(PickerRow*)row NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder*)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithNibName:(NSString*)nibNameOrNil
bundle:(NSBundle*)nibBundleOrNil NS_UNAVAILABLE;
- (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE;
@end

#endif // IOS_CHROME_BROWSER_PAYMENT_REQUEST_PICKER_VIEW_CONTROLLER_H_
251 changes: 251 additions & 0 deletions ios/chrome/browser/payments/payment_request_picker_view_controller.mm
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
// 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_picker_view_controller.h"

#import "base/logging.h"
#import "base/mac/foundation_util.h"
#import "ios/chrome/browser/payments/payment_request_picker_row.h"

#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif

NSString* const kPaymentRequestPickerRowAccessibilityID =
@"kPaymentRequestPickerRowAccessibilityID";
NSString* const kPaymentRequestPickerSearchBarAccessibilityID =
@"kPaymentRequestPickerSearchBarAccessibilityID";

namespace {
NSString* const kPaymentRequestPickerViewControllerAccessibilityID =
@"kPaymentRequestPickerViewControllerAccessibilityID";
} // namespace

@interface PaymentRequestPickerViewController ()<UISearchResultsUpdating>

// Search controller that contains search bar.
@property(nonatomic, strong) UISearchController* searchController;

// Full data set displayed when tableView is not filtered.
@property(nonatomic, strong) NSArray<PickerRow*>* allRows;

// Displayed rows in the tableView.
@property(nonatomic, strong) NSArray<PickerRow*>* displayedRows;

// Selected row.
@property(nonatomic, strong) PickerRow* selectedRow;

@property(nonatomic, strong)
NSDictionary<NSString*, NSArray<PickerRow*>*>* sectionTitleToSectionRowsMap;

@end

@implementation PaymentRequestPickerViewController
@synthesize searchController = _searchController;
@synthesize allRows = _allRows;
@synthesize displayedRows = _displayedRows;
@synthesize selectedRow = _selectedRow;
@synthesize sectionTitleToSectionRowsMap = _sectionTitleToSectionRowsMap;
@synthesize delegate = _delegate;

- (instancetype)initWithRows:(NSArray<PickerRow*>*)rows
selected:(PickerRow*)selectedRow {
self = [super initWithStyle:UITableViewStylePlain];
if (self) {
self.allRows = [rows sortedArrayUsingComparator:^NSComparisonResult(
PickerRow* row1, PickerRow* row2) {
return [row1.label localizedCaseInsensitiveCompare:row2.label];
}];
self.selectedRow = selectedRow;
// Default to displaying all the rows.
self.displayedRows = self.allRows;
}
return self;
}

- (void)setDisplayedRows:(NSArray<PickerRow*>*)displayedRows {
_displayedRows = displayedRows;

// Update the mapping from section titles to rows in that section, for
// currently displayed rows.
[self updateSectionTitleToSectionRowsMap];
}

#pragma mark - UIViewController

- (void)viewDidLoad {
[super viewDidLoad];

self.tableView.rowHeight = 48.0f; // The same as MDCCellDefaultOneLineHeight.
self.tableView.accessibilityIdentifier =
kPaymentRequestPickerViewControllerAccessibilityID;

self.searchController =
[[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.searchBar.accessibilityIdentifier =
kPaymentRequestPickerSearchBarAccessibilityID;
self.tableView.tableHeaderView = self.searchController.searchBar;

// Presentation of searchController will walk up the view controller hierarchy
// until it finds the root view controller or one that defines a presentation
// context. Make this class the presentation context so that the search
// controller does not present on top of the navigation controller.
self.definesPresentationContext = YES;
}

#pragma mark - UITableViewDataSource

- (NSArray<NSString*>*)sectionIndexTitlesForTableView:(UITableView*)tableView {
return [self sectionTitles];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView*)tableView {
return [[self sectionTitles] count];
}

- (NSString*)tableView:(UITableView*)tableView
titleForHeaderInSection:(NSInteger)section {
return [[self sectionTitles] objectAtIndex:section];
}

- (NSInteger)tableView:(UITableView*)tableView
numberOfRowsInSection:(NSInteger)section {
return [[self rowsInSection:section] count];
}

- (UITableViewCell*)tableView:(UITableView*)tableView
cellForRowAtIndexPath:(NSIndexPath*)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:@"cell"];
cell.isAccessibilityElement = YES;
cell.accessibilityIdentifier = kPaymentRequestPickerRowAccessibilityID;
}
PickerRow* row =
[[self rowsInSection:indexPath.section] objectAtIndex:indexPath.row];
cell.textLabel.text = row.label;
cell.accessoryType = (row == self.selectedRow)
? UITableViewCellAccessoryCheckmark
: UITableViewCellAccessoryNone;
if (row == self.selectedRow)
cell.accessibilityTraits |= UIAccessibilityTraitSelected;
else
cell.accessibilityTraits &= ~UIAccessibilityTraitSelected;

return cell;
}

#pragma mark - UITableViewDelegate

- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
if (self.selectedRow) {
NSIndexPath* oldSelectedIndexPath = [self indexPathForRow:self.selectedRow];
self.selectedRow = nil;
// Reload the previously selected row if it is displaying.
if (oldSelectedIndexPath) {
[self.tableView reloadRowsAtIndexPaths:@[ oldSelectedIndexPath ]
withRowAnimation:UITableViewRowAnimationFade];
}
}

self.selectedRow =
[[self rowsInSection:indexPath.section] objectAtIndex:indexPath.row];
// Reload the newly selected row.
[self.tableView reloadRowsAtIndexPaths:@[ indexPath ]
withRowAnimation:UITableViewRowAnimationFade];

[_delegate paymentRequestPickerViewController:self
didSelectRow:self.selectedRow];
}

#pragma mark - UISearchResultsUpdating

- (void)updateSearchResultsForSearchController:
(UISearchController*)searchController {
NSString* searchText = searchController.searchBar.text;

// Filter |allRows| for |searchText| and reload the tableView. If |searchText|
// is empty, tableView will be loaded with |allRows|.
if (searchText.length != 0) {
// The search is case-insensitive and ignores diacritics.
NSPredicate* predicate =
[NSPredicate predicateWithFormat:@"label CONTAINS[cd] %@", searchText];
self.displayedRows = [self.allRows filteredArrayUsingPredicate:predicate];
} else {
self.displayedRows = self.allRows;
}

[self.tableView reloadData];
}

#pragma mark - Private

// Creates a mapping from section titles to rows in that section, for currently
// displaying rows, and updates |sectionTitleToSectionRowsMap|.
- (void)updateSectionTitleToSectionRowsMap {
NSMutableDictionary<NSString*, NSArray<PickerRow*>*>*
sectionTitleToSectionRowsMap = [[NSMutableDictionary alloc] init];

for (PickerRow* row in self.displayedRows) {
NSString* sectionTitle = [self sectionTitleForRow:row];
NSMutableArray<PickerRow*>* sectionRows =
base::mac::ObjCCastStrict<NSMutableArray<PickerRow*>>(
sectionTitleToSectionRowsMap[sectionTitle]);
if (!sectionRows)
sectionRows = [[NSMutableArray alloc] init];
[sectionRows addObject:row];
[sectionTitleToSectionRowsMap setObject:sectionRows forKey:sectionTitle];
}

self.sectionTitleToSectionRowsMap = sectionTitleToSectionRowsMap;
}

// Returns the indexPath for |row| by calculating its section and its index
// within the section. Returns nil if the row is not currently displaying.
- (NSIndexPath*)indexPathForRow:(PickerRow*)row {
NSString* sectionTitle = [self sectionTitleForRow:row];

NSInteger section = [[self sectionTitles] indexOfObject:sectionTitle];
if (section == NSNotFound)
return nil;

NSInteger indexInSection =
[self.sectionTitleToSectionRowsMap[sectionTitle] indexOfObject:row];
if (indexInSection == NSNotFound)
return nil;

return [NSIndexPath indexPathForRow:indexInSection inSection:section];
}

// Returns the titles for the displayed sections in the tableView.
- (NSArray<NSString*>*)sectionTitles {
return [[self.sectionTitleToSectionRowsMap allKeys]
sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}

// Returns the displayed rows in the given section.
- (NSArray<PickerRow*>*)rowsInSection:(NSInteger)section {
NSArray<NSString*>* sectionTitles = [self sectionTitles];
DCHECK(section >= 0 && section < static_cast<NSInteger>(sectionTitles.count));

NSString* sectionTitle = [sectionTitles objectAtIndex:section];

return self.sectionTitleToSectionRowsMap[sectionTitle];
}

// Returns the title for the section the given row gets added to. The section
// title for a row is the capitalized first letter of the label for that row.
- (NSString*)sectionTitleForRow:(PickerRow*)row {
return [[row.label substringToIndex:1] uppercaseString];
}

- (NSString*)description {
return kPaymentRequestPickerViewControllerAccessibilityID;
}

@end
2 changes: 1 addition & 1 deletion ios/showcase/common/protocol_alerter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ - (NSString*)argumentDescriptionAtIndex:(NSInteger)index {
// Return a string describing an argument at |index| that's known to be an
// objective-C object.
- (NSString*)objectDescriptionAtIndex:(NSInteger)index {
id object;
__unsafe_unretained id object;

[self getArgument:&object atIndex:index];
if (!object)
Expand Down
5 changes: 5 additions & 0 deletions ios/showcase/core/showcase_model.mm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ @implementation ShowcaseModel
showcase::kClassForInstantiationKey : @"SCPaymentsEditorCoordinator",
showcase::kUseCaseKey : @"Generic payment request editor",
},
@{
showcase::kClassForDisplayKey : @"PaymentRequestPickerViewController",
showcase::kClassForInstantiationKey : @"SCPaymentsPickerCoordinator",
showcase::kUseCaseKey : @"Payment request picker view",
},
@{
showcase::kClassForDisplayKey : @"SettingsViewController",
showcase::kClassForInstantiationKey : @"SCSettingsCoordinator",
Expand Down
3 changes: 3 additions & 0 deletions ios/showcase/payments/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ source_set("payments") {
sources = [
"sc_payments_editor_coordinator.h",
"sc_payments_editor_coordinator.mm",
"sc_payments_picker_coordinator.h",
"sc_payments_picker_coordinator.mm",
]
deps = [
"//base",
Expand All @@ -26,6 +28,7 @@ source_set("eg_tests") {
testonly = true
sources = [
"sc_payments_editor_egtest.mm",
"sc_payments_picker_egtest.mm",
]
deps = [
"//base",
Expand Down
Loading

0 comments on commit 14a3ede

Please sign in to comment.