[iOS] Make password picker have single selection
* Updates password picker to new mocks to allow selecting only one
credential
* Next button is enabled by default with the first row selected by
default
Bug: 1463882
Change-Id: I625f3a3b8a09655dea92010f55201e156105535e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4951733
Commit-Queue: Rafał Godlewski <rgod@google.com>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Reviewed-by: Ernesto Izquierdo Clua <eic@google.com>
Cr-Commit-Position: refs/heads/main@{#1215110}
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator.mm
index 9fc60d9f..523a49c 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator.mm
@@ -98,10 +98,9 @@
}
- (void)passwordPickerClosed:(PasswordPickerViewController*)controller
- withSelectedCredentials:
- (const std::vector<password_manager::CredentialUIEntry>&)credentials {
- [self.delegate passwordPickerCoordinator:self
- didSelectCredentials:credentials];
+ withSelectedCredential:
+ (const password_manager::CredentialUIEntry&)credential {
+ [self.delegate passwordPickerCoordinator:self didSelectCredential:credential];
}
@end
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator_delegate.h b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator_delegate.h
index 5958ea28..c3fba54 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_coordinator_delegate.h
@@ -20,9 +20,8 @@
// Called when the user confirms the selection by clicking the next button.
- (void)passwordPickerCoordinator:(PasswordPickerCoordinator*)coordinator
- didSelectCredentials:
- (const std::vector<password_manager::CredentialUIEntry>&)
- credentials;
+ didSelectCredential:
+ (const password_manager::CredentialUIEntry&)credential;
@end
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller.mm b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller.mm
index 698424e..b02e46a 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller.mm
@@ -8,7 +8,6 @@
#import "components/password_manager/core/browser/password_ui_utils.h"
#import "components/password_manager/core/browser/ui/credential_ui_entry.h"
#import "ios/chrome/browser/net/crurl.h"
-#import "ios/chrome/browser/shared/ui/symbols/symbols.h"
#import "ios/chrome/browser/shared/ui/table_view/cells/table_view_url_item.h"
#import "ios/chrome/browser/shared/ui/table_view/table_view_favicon_data_source.h"
#import "ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_presentation_delegate.h"
@@ -27,9 +26,6 @@
ItemTypeCredential = kItemTypeEnumZero,
};
-// Size of the accessory view symbol.
-const CGFloat kAccessorySymbolSize = 22;
-
} // namespace
@interface PasswordPickerViewController () {
@@ -59,13 +55,10 @@
style:UIBarButtonItemStylePlain
target:self
action:@selector(nextButtonTapped)];
- nextButton.enabled = NO;
self.navigationItem.rightBarButtonItem = nextButton;
self.navigationItem.rightBarButtonItem.accessibilityIdentifier =
kPasswordPickerNextButtonId;
- self.tableView.allowsMultipleSelection = YES;
-
[self loadModel];
}
@@ -80,20 +73,28 @@
}
}
+- (void)viewDidAppear:(BOOL)animated {
+ [super viewDidAppear:animated];
+
+ // Select first row by default.
+ NSIndexPath* indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
+ [self.tableView selectRowAtIndexPath:indexPath
+ animated:NO
+ scrollPosition:UITableViewScrollPositionNone];
+}
+
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView*)tableView
didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
- [tableView cellForRowAtIndexPath:indexPath].accessoryView =
- [[UIImageView alloc] initWithImage:[self checkmarkCircleIcon]];
- [self setNextButtonStatus];
+ [tableView cellForRowAtIndexPath:indexPath].accessoryType =
+ UITableViewCellAccessoryCheckmark;
}
- (void)tableView:(UITableView*)tableView
didDeselectRowAtIndexPath:(NSIndexPath*)indexPath {
- [tableView cellForRowAtIndexPath:indexPath].accessoryView =
- [[UIImageView alloc] initWithImage:[self circleIcon]];
- [self setNextButtonStatus];
+ [tableView cellForRowAtIndexPath:indexPath].accessoryType =
+ UITableViewCellAccessoryNone;
}
#pragma mark - UITableViewDataSource
@@ -115,9 +116,11 @@
cell.userInteractionEnabled = YES;
cell.textLabel.numberOfLines = 1;
cell.detailTextLabel.numberOfLines = 1;
- cell.accessoryView = [[UIImageView alloc]
- initWithImage:cell.isSelected ? [self checkmarkCircleIcon]
- : [self circleIcon]];
+ if (indexPath.row == tableView.indexPathForSelectedRow.row) {
+ cell.accessoryType = UITableViewCellAccessoryCheckmark;
+ } else {
+ cell.accessoryType = UITableViewCellAccessoryNone;
+ }
TableViewItem* item = [self.tableViewModel itemAtIndexPath:indexPath];
TableViewURLItem* URLItem =
@@ -156,32 +159,15 @@
#pragma mark - Private
-- (UIImage*)checkmarkCircleIcon {
- return DefaultSymbolWithPointSize(kCheckmarkCircleFillSymbol,
- kAccessorySymbolSize);
-}
-
-- (UIImage*)circleIcon {
- return DefaultSymbolWithPointSize(kCircleSymbol, kAccessorySymbolSize);
-}
-
- (void)cancelButtonTapped {
[self.delegate passwordPickerWasDismissed:self];
}
- (void)nextButtonTapped {
- std::vector<password_manager::CredentialUIEntry> selectedCredentials;
- for (NSIndexPath* indexPath in self.tableView.indexPathsForSelectedRows) {
- selectedCredentials.push_back(_credentials[indexPath.row]);
- }
- [self.delegate passwordPickerClosed:self
- withSelectedCredentials:selectedCredentials];
-}
-
-// Enables next button if any row is selected or disables it otherwise.
-- (void)setNextButtonStatus {
- self.navigationItem.rightBarButtonItem.enabled =
- self.tableView.indexPathsForSelectedRows.count > 0;
+ [self.delegate
+ passwordPickerClosed:self
+ withSelectedCredential:_credentials[self.tableView.indexPathForSelectedRow
+ .row]];
}
@end
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_presentation_delegate.h b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_presentation_delegate.h
index e108d08..3f6887b 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_presentation_delegate.h
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_presentation_delegate.h
@@ -17,10 +17,10 @@
// Called when the user clicks cancel button or dismisses the view by swiping.
- (void)passwordPickerWasDismissed:(PasswordPickerViewController*)controller;
-// Called when the user clicks next button with selected credentials.
+// Called when the user clicks next button with selected credential.
- (void)passwordPickerClosed:(PasswordPickerViewController*)controller
- withSelectedCredentials:
- (const std::vector<password_manager::CredentialUIEntry>&)credentials;
+ withSelectedCredential:
+ (const password_manager::CredentialUIEntry&)credential;
@end
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_unittest.mm b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_unittest.mm
index 29d512c..33c9a00 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_unittest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_picker_view_controller_unittest.mm
@@ -44,6 +44,16 @@
static_cast<PasswordPickerViewController*>(controller());
[password_picker_controller setCredentials:credentials];
}
+
+ void CheckCellAccessoryType(UITableViewCellAccessoryType accessoryType,
+ int section,
+ int row) {
+ UITableViewCell* cell =
+ [controller() tableView:controller().tableView
+ cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row
+ inSection:section]];
+ EXPECT_EQ(cell.accessoryType, accessoryType);
+ }
};
TEST_F(PasswordPickerViewControllerTest, TestPasswordPickerLayout) {
@@ -62,3 +72,45 @@
base::SysUTF8ToNSString(item.URL.gurl.host()));
}
}
+
+TEST_F(PasswordPickerViewControllerTest, TestNextButtonEnabledByDefault) {
+ SetCredentials(/*amount=*/3);
+
+ EXPECT_EQ(NumberOfSections(), 1);
+ EXPECT_EQ(NumberOfItemsInSection(0), 3);
+
+ PasswordPickerViewController* passwordPickerController =
+ base::apple::ObjCCastStrict<PasswordPickerViewController>(controller());
+ EXPECT_TRUE(
+ passwordPickerController.navigationItem.rightBarButtonItem.isEnabled);
+}
+
+TEST_F(PasswordPickerViewControllerTest, TestSettingAccessoryType) {
+ SetCredentials(/*amount=*/4);
+
+ EXPECT_EQ(NumberOfSections(), 1);
+ EXPECT_EQ(NumberOfItemsInSection(0), 4);
+
+ // Check that the first row has a checkmark by default.
+ CheckCellAccessoryType(UITableViewCellAccessoryCheckmark, 0, 0);
+ CheckCellAccessoryType(UITableViewCellAccessoryNone, 0, 1);
+ CheckCellAccessoryType(UITableViewCellAccessoryNone, 0, 2);
+ CheckCellAccessoryType(UITableViewCellAccessoryNone, 0, 3);
+
+ // Select last row.
+ PasswordPickerViewController* passwordPickerController =
+ base::apple::ObjCCastStrict<PasswordPickerViewController>(controller());
+ NSIndexPath* indexPath = [NSIndexPath indexPathForRow:3 inSection:0];
+ [passwordPickerController.tableView
+ selectRowAtIndexPath:indexPath
+ animated:NO
+ scrollPosition:UITableViewScrollPositionNone];
+ [passwordPickerController tableView:passwordPickerController.tableView
+ didSelectRowAtIndexPath:indexPath];
+
+ // Check that the last row has a checkmark.
+ CheckCellAccessoryType(UITableViewCellAccessoryNone, 0, 0);
+ CheckCellAccessoryType(UITableViewCellAccessoryNone, 0, 1);
+ CheckCellAccessoryType(UITableViewCellAccessoryNone, 0, 2);
+ CheckCellAccessoryType(UITableViewCellAccessoryCheckmark, 0, 3);
+}
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_coordinator.mm b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_coordinator.mm
index 7d3b6b5..ff71c4fd 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_coordinator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_coordinator.mm
@@ -131,9 +131,9 @@
GetForBrowserState(browserState)];
// With more than 1 credential an additional UI will be presented to select
- // which ones should be shared.
+ // which one should be shared.
if (_credentials.size() == 1) {
- self.mediator.selectedCredentials = _credentials;
+ self.mediator.selectedCredential = _credentials[0];
}
}
@@ -197,10 +197,9 @@
}
- (void)passwordPickerCoordinator:(PasswordPickerCoordinator*)coordinator
- didSelectCredentials:
- (const std::vector<password_manager::CredentialUIEntry>&)
- credentials {
- self.mediator.selectedCredentials = credentials;
+ didSelectCredential:
+ (const password_manager::CredentialUIEntry&)credential {
+ self.mediator.selectedCredential = credential;
[self startFamilyPickerCoordinator];
}
@@ -249,7 +248,7 @@
}
- (void)startPasswordSharing {
- [self.mediator sendSelectedPasswordsToSelectedRecipients];
+ [self.mediator sendSelectedCredentialToSelectedRecipients];
}
#pragma mark - Private
@@ -287,10 +286,8 @@
- (void)startSharingStatusCoordinator {
[self.sharingStatusCoordinator stop];
- // TODO(crbug.com/1463882): Remove multiple credential selection, before it's
- // done use the first one.
password_manager::CredentialUIEntry credential =
- self.mediator.selectedCredentials[0];
+ self.mediator.selectedCredential;
self.sharingStatusCoordinator = [[SharingStatusCoordinator alloc]
initWithBaseViewController:self.navigationController
browser:self.browser
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm
index 5e63216..71f0856 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_egtest.mm
@@ -372,19 +372,16 @@
selectElementWithMatcher:grey_accessibilityID(kPasswordShareButtonId)]
performAction:grey_tap()];
- // Check that the next button is not visible without any rows selected.
- [[EarlGrey selectElementWithMatcher:grey_accessibilityID(
- kPasswordPickerNextButtonId)]
- assertWithMatcher:grey_not(grey_enabled())];
-
- // Select first row and click "Next".
- [[EarlGrey
- selectElementWithMatcher:grey_allOf(grey_accessibilityID(@"username1"),
- grey_sufficientlyVisible(), nil)]
- performAction:grey_tap()];
+ // Check that the next button is enabled by default.
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(
kPasswordPickerNextButtonId)]
assertWithMatcher:grey_enabled()];
+
+ // Select second row and click "Next".
+ [[EarlGrey
+ selectElementWithMatcher:grey_allOf(grey_accessibilityID(@"username2"),
+ grey_sufficientlyVisible(), nil)]
+ performAction:grey_tap()];
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(
kPasswordPickerNextButtonId)]
performAction:grey_tap()];
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.h b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.h
index cb89b46..e897c063b 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.h
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.h
@@ -45,14 +45,14 @@
- (instancetype)init NS_UNAVAILABLE;
-// Fetches corresponding password forms for all `selectedCredentials` and
-// invokes SendPasswords method of PasswordSenderService with all forms for each
-// of the `selectedRecipients`.
-- (void)sendSelectedPasswordsToSelectedRecipients;
+// Fetches corresponding password forms for `selectedCredential` and invokes
+// SendPasswords method of PasswordSenderService with all forms for each of the
+// `selectedRecipients`.
+- (void)sendSelectedCredentialToSelectedRecipients;
-// Credentials selected by the user to be shared.
-@property(nonatomic, assign) std::vector<password_manager::CredentialUIEntry>
- selectedCredentials;
+// Credential selected by the user to be shared.
+@property(nonatomic, assign)
+ password_manager::CredentialUIEntry selectedCredential;
// Recipients selected by the user to receive the shared passwords.
@property(nonatomic, strong)
diff --git a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.mm b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.mm
index 455f868..5b23639 100644
--- a/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.mm
+++ b/ios/chrome/browser/ui/settings/password/password_sharing/password_sharing_mediator.mm
@@ -82,15 +82,10 @@
return self;
}
-- (void)sendSelectedPasswordsToSelectedRecipients {
- std::vector<password_manager::PasswordForm> passwords;
- for (const password_manager::CredentialUIEntry& credential :
- self.selectedCredentials) {
- std::vector<password_manager::PasswordForm> credential_forms =
- _savedPasswordsPresenter->GetCorrespondingPasswordForms(credential);
- passwords.insert(passwords.end(), credential_forms.begin(),
- credential_forms.end());
- }
+- (void)sendSelectedCredentialToSelectedRecipients {
+ std::vector<password_manager::PasswordForm> passwords =
+ _savedPasswordsPresenter->GetCorrespondingPasswordForms(
+ self.selectedCredential);
for (RecipientInfoForIOSDisplay* recipient in self.selectedRecipients) {
_passwordSenderService->SendPasswords(
passwords, {.user_id = base::SysNSStringToUTF8(recipient.userID),