Commit 082af3c7 authored by Ioana Pandele's avatar Ioana Pandele Committed by Commit Bot

Add progress indicator for password export

This is an alert with the title "Preparing passwords..." displayed after the reauthentication
step is finished, and while passwords are being serialized and written to the temporary file.
The alert offers the user the option to cancel the exporting operation.

If the user cancels, the exporter will wait for the async tasks to finish, perform necessary
clean-up and then become available again.

Bug: 789122
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I8dd6b9a094e055eef15f5580e3d887e5cf979408
Reviewed-on: https://chromium-review.googlesource.com/897607Reviewed-by: default avatarSylvain Defresne <sdefresne@chromium.org>
Commit-Queue: Ioana Pandele <ioanap@chromium.org>
Cr-Commit-Position: refs/heads/master@{#534046}
parent 3180cc44
......@@ -1072,6 +1072,9 @@ Handoff must also be enabled in the General section of Settings, and your device
<message name="IDS_IOS_EXPORT_PASSWORDS_ALERT_MESSAGE" desc="The message of the alert displayed as a warning to the user who tapped on the button to export passwords. [iOS only]">
Your passwords will be visible to anyone who can see the exported file.
</message>
<message name="IDS_IOS_EXPORT_PASSWORDS_PREPARING_ALERT_TITLE" desc="The title of the alert displayed to indicate that the passwords are being prepared for export. [iOS only]">
Preparing Passwords...
</message>
<message name="IDS_IOS_EXPORT_PASSWORDS_FAILED_ALERT_TITLE" desc="Title of the alert informing the user that exporting passwords has failed. [iOS only]">
Can't Export Passwords
</message>
......
......@@ -20,6 +20,12 @@ enum class WriteToURLStatus {
UNKNOWN_ERROR,
};
enum class ExportState {
IDLE,
ONGOING,
CANCELLING,
};
@protocol ReauthenticationProtocol;
@protocol FileWriterProtocol<NSObject>
......@@ -48,6 +54,10 @@ enum class WriteToURLStatus {
// in order to export passwords.
- (void)showSetPasscodeDialog;
// Displays an alert which informs the user that the passwords are being
// prepared to be exported and gives them the option of cancelling the export.
- (void)showPreparingPasswordsAlert;
// Displays an alert detailing an error that has occured during export.
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)errorReason;
......@@ -60,6 +70,9 @@ enum class WriteToURLStatus {
NSArray* returnedItems,
NSError* activityError))completionHandler;
// Enables or disables the export button based on the export state.
- (void)updateExportPasswordsButton;
@end
/** Class handling all the operations necessary to export passwords.*/
......@@ -80,8 +93,11 @@ enum class WriteToURLStatus {
- (void)startExportFlow:
(std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords;
// Whether an export operation is already in progress.
@property(nonatomic, assign, readonly) BOOL isExporting;
// Called when the user cancels the export operation.
- (void)cancelExport;
// State of the export operation.
@property(nonatomic, readonly, assign) ExportState exportState;
@end
......
......@@ -104,16 +104,15 @@ enum class ReauthenticationStatus {
@property(nonatomic, assign) BOOL serializingFinished;
// String containing serialized password forms.
@property(nonatomic, copy) NSString* serializedPasswords;
// Whether an export operation is ongoing. This is a readwrite property
// corresponding to the public readonly property.
@property(nonatomic, assign) BOOL isExporting;
// The exporter state.
@property(nonatomic, assign) ExportState exportState;
@end
@implementation PasswordExporter
// Public synthesized properties
@synthesize isExporting = _isExporting;
@synthesize exportState = _exportState;
// Private synthesized properties
@synthesize reauthenticationStatus = _reauthenticationStatus;
......@@ -142,8 +141,11 @@ enum class ReauthenticationStatus {
- (void)startExportFlow:
(std::vector<std::unique_ptr<autofill::PasswordForm>>)passwords {
DCHECK(!passwords.empty());
DCHECK(self.exportState == ExportState::IDLE);
if ([_weakReauthenticationModule canAttemptReauth]) {
self.isExporting = YES;
self.exportState = ExportState::ONGOING;
[_weakDelegate updateExportPasswordsButton];
[self serializePasswords:std::move(passwords)];
[self startReauthentication];
} else {
......@@ -151,6 +153,10 @@ enum class ReauthenticationStatus {
}
}
- (void)cancelExport {
self.exportState = ExportState::CANCELLING;
}
#pragma mark - Private methods
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)errorReason {
......@@ -183,6 +189,7 @@ enum class ReauthenticationStatus {
return;
if (success) {
strongSelf.reauthenticationStatus = ReauthenticationStatus::SUCCESSFUL;
[strongSelf showPreparingPasswordsAlert];
} else {
strongSelf.reauthenticationStatus = ReauthenticationStatus::FAILED;
}
......@@ -196,6 +203,10 @@ enum class ReauthenticationStatus {
handler:onReauthenticationFinished];
}
- (void)showPreparingPasswordsAlert {
[_weakDelegate showPreparingPasswordsAlert];
}
- (void)tryExporting {
if (!self.serializingFinished)
return;
......@@ -217,10 +228,15 @@ enum class ReauthenticationStatus {
self.serializingFinished = NO;
self.serializedPasswords = nil;
self.reauthenticationStatus = ReauthenticationStatus::PENDING;
self.isExporting = NO;
self.exportState = ExportState::IDLE;
[_weakDelegate updateExportPasswordsButton];
}
- (void)writePasswordsToFile {
if (self.exportState == ExportState::CANCELLING) {
[self resetExportState];
return;
}
NSURL* tempPasswordsFileURL =
[[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]
URLByAppendingPathComponent:_tempPasswordsFileName
......@@ -232,6 +248,10 @@ enum class ReauthenticationStatus {
if (!strongSelf) {
return;
}
if (strongSelf.exportState == ExportState::CANCELLING) {
[strongSelf resetExportState];
return;
}
switch (status) {
case WriteToURLStatus::SUCCESS:
[strongSelf showActivityView];
......@@ -284,7 +304,12 @@ enum class ReauthenticationStatus {
[[NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]
URLByAppendingPathComponent:_tempPasswordsFileName
isDirectory:NO];
if (self.exportState == ExportState::CANCELLING) {
// Initiate cleanup. Once the file is deleted, the export state will be
// reset;
[self deleteTemporaryFile:passwordsTempFileURL];
return;
}
__weak PasswordExporter* weakSelf = self;
[_weakDelegate
showActivityViewWithActivityItems:@[ passwordsTempFileURL ]
......
......@@ -168,6 +168,9 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
// Boolean containing whether the export button and functionality are enabled
// or not.
BOOL exportEnabled_;
// Alert informing the user that passwords are being prepared for
// export.
UIAlertController* preparingPasswordsAlert_;
}
// Kick off async request to get logins from password store.
......@@ -194,9 +197,12 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
browserState_ = browserState;
reauthenticationModule_ = [[ReauthenticationModule alloc]
initWithSuccessfulReauthTimeAccessor:self];
passwordExporter_ = [[PasswordExporter alloc]
initWithReauthenticationModule:reauthenticationModule_
delegate:self];
if (base::FeatureList::IsEnabled(
password_manager::features::kPasswordExport)) {
passwordExporter_ = [[PasswordExporter alloc]
initWithReauthenticationModule:reauthenticationModule_
delegate:self];
}
self.title = l10n_util::GetNSString(IDS_IOS_SAVE_PASSWORDS);
self.collectionViewAccessibilityIdentifier =
@"SavePasswordsCollectionViewController";
......@@ -279,7 +285,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
exportPasswordsItem_ = [self exportPasswordsItem];
[model addItem:exportPasswordsItem_
toSectionWithIdentifier:SectionIdentifierExportPasswordsButton];
[self updateExportPasswordsItem];
[self updateExportPasswordsButton];
}
}
......@@ -484,22 +490,24 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[self reloadData];
}
- (void)updateExportPasswordsItem {
if (savedForms_.empty()) {
- (void)updateExportPasswordsButton {
if (!exportPasswordsItem_)
return;
if (!savedForms_.empty() &&
self.passwordExporter.exportState == ExportState::IDLE) {
exportPasswordsItem_.textColor = [[MDCPalette greyPalette] tint900];
exportPasswordsItem_.accessibilityTraits = UIAccessibilityTraitButton;
[self reconfigureCellsForItems:@[ exportPasswordsItem_ ]];
exportEnabled_ = YES;
} else {
exportPasswordsItem_.textColor = [[MDCPalette greyPalette] tint500];
exportPasswordsItem_.accessibilityTraits = UIAccessibilityTraitNotEnabled;
[self reconfigureCellsForItems:@[ exportPasswordsItem_ ]];
exportEnabled_ = NO;
} else {
exportEnabled_ = YES;
}
}
- (void)startPasswordsExportFlow {
// TODO(crbug.com/789122): Consider disabling the button while another export
// operation is in progress.
if (self.passwordExporter.isExporting)
return;
UIAlertController* exportConfirmation = [UIAlertController
alertControllerWithTitle:nil
message:l10n_util::GetNSString(
......@@ -524,6 +532,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[strongSelf.passwordExporter
startExportFlow:CopyOf(strongSelf->savedForms_)];
}];
[exportConfirmation addAction:exportAction];
[self presentViewController:exportConfirmation animated:YES completion:nil];
......@@ -696,7 +705,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[strongSelf updateEditButton];
if (base::FeatureList::IsEnabled(
password_manager::features::kPasswordExport)) {
[strongSelf updateExportPasswordsItem];
[strongSelf updateExportPasswordsButton];
}
}];
}
......@@ -775,6 +784,26 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
[self presentViewController:alertController animated:YES completion:nil];
}
- (void)showPreparingPasswordsAlert {
preparingPasswordsAlert_ = [UIAlertController
alertControllerWithTitle:
l10n_util::GetNSString(IDS_IOS_EXPORT_PASSWORDS_PREPARING_ALERT_TITLE)
message:nil
preferredStyle:UIAlertControllerStyleAlert];
__weak SavePasswordsCollectionViewController* weakSelf = self;
UIAlertAction* cancelAction =
[UIAlertAction actionWithTitle:l10n_util::GetNSString(
IDS_IOS_EXPORT_PASSWORDS_CANCEL_BUTTON)
style:UIAlertActionStyleCancel
handler:^(UIAlertAction*) {
[weakSelf.passwordExporter cancelExport];
}];
[preparingPasswordsAlert_ addAction:cancelAction];
[self presentViewController:preparingPasswordsAlert_
animated:YES
completion:nil];
}
- (void)showExportErrorAlertWithLocalizedReason:(NSString*)localizedReason {
UIAlertController* alertController = [UIAlertController
alertControllerWithTitle:l10n_util::GetNSString(
......@@ -786,7 +815,7 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
[self presentViewController:alertController];
}
- (void)showActivityViewWithActivityItems:(NSArray*)activityItems
......@@ -826,9 +855,23 @@ void SavePasswordsConsumer::OnGetPasswordStoreResults(
activityViewController.popoverPresentationController
.permittedArrowDirections =
UIPopoverArrowDirectionDown | UIPopoverArrowDirectionDown;
[self presentViewController:activityViewController
animated:YES
completion:nil];
[self presentViewController:activityViewController];
}
- (void)presentViewController:(UIViewController*)viewController {
if (self.presentedViewController == preparingPasswordsAlert_ &&
!preparingPasswordsAlert_.beingDismissed) {
__weak SavePasswordsCollectionViewController* weakSelf = self;
[self dismissViewControllerAnimated:YES
completion:^() {
[weakSelf presentViewController:viewController
animated:YES
completion:nil];
}];
} else {
[self presentViewController:viewController animated:YES completion:nil];
}
}
#pragma mark Helper methods
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment