Add a SignOutFlowPresenter. (#6854)

Used in AllChats, UserSessions and Settings.
(TabBarCoordinator is unnecessary as signout will be removed from there).
This commit is contained in:
Doug
2022-10-13 14:36:30 +01:00
committed by GitHub
parent 8fa639f96e
commit 7023196ec3
5 changed files with 225 additions and 235 deletions
+19 -128
View File
@@ -189,8 +189,7 @@ typedef void (^blockSettingsViewController_onReadyToDestroy)(void);
@interface SettingsViewController () <UITextFieldDelegate, MXKCountryPickerViewControllerDelegate, MXKLanguagePickerViewControllerDelegate, DeactivateAccountViewControllerDelegate,
NotificationSettingsCoordinatorBridgePresenterDelegate,
SecureBackupSetupCoordinatorBridgePresenterDelegate,
SignOutAlertPresenterDelegate,
SignOutFlowPresenterDelegate,
SingleImagePickerPresenterDelegate,
SettingsDiscoveryTableViewSectionDelegate, SettingsDiscoveryViewModelCoordinatorDelegate,
SettingsIdentityServerCoordinatorBridgePresenterDelegate,
@@ -267,7 +266,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
@property (nonatomic, strong) NotificationSettingsCoordinatorBridgePresenter *notificationSettingsBridgePresenter;
@property (nonatomic, strong) SignOutAlertPresenter *signOutAlertPresenter;
@property (nonatomic, strong) SignOutFlowPresenter *signOutFlowPresenter;
@property (nonatomic, weak) UIButton *signOutButton;
@property (nonatomic, strong) SingleImagePickerPresenter *imagePickerPresenter;
@@ -275,12 +274,8 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
@property (nonatomic, strong) SettingsDiscoveryTableViewSection *settingsDiscoveryTableViewSection;
@property (nonatomic, strong) SettingsDiscoveryThreePidDetailsCoordinatorBridgePresenter *discoveryThreePidDetailsPresenter;
@property (nonatomic, strong) SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter;
@property (nonatomic, strong) TableViewSections *tableViewSections;
@property (nonatomic, strong) CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter;
@property (nonatomic, strong) ReauthenticationCoordinatorBridgePresenter *reauthenticationCoordinatorBridgePresenter;
@property (nonatomic, strong) UserInteractiveAuthenticationService *userInteractiveAuthenticationService;
@@ -701,9 +696,6 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
}];
[self userInterfaceThemeDidChange];
self.signOutAlertPresenter = [SignOutAlertPresenter new];
self.signOutAlertPresenter.delegate = self;
_tableViewSections = [TableViewSections new];
_tableViewSections.delegate = self;
[self updateSections];
@@ -770,8 +762,7 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
[super destroy];
}
_secureBackupSetupCoordinatorBridgePresenter = nil;
identityServerSettingsCoordinatorBridgePresenter = nil;
}
@@ -2964,13 +2955,11 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
{
self.signOutButton = (UIButton*)sender;
MXKeyBackup *keyBackup = self.mainSession.crypto.backup;
SignOutFlowPresenter *flowPresenter = [[SignOutFlowPresenter alloc] initWithSession:self.mainSession presentingViewController:self];
flowPresenter.delegate = self;
[self.signOutAlertPresenter presentFor:keyBackup.state
areThereKeysToBackup:keyBackup.hasKeysToBackup
from:self
sourceView:self.signOutButton
animated:YES];
[flowPresenter startWithSourceView:self.signOutButton];
self.signOutFlowPresenter = flowPresenter;
}
- (void)onRemove3PID:(NSIndexPath*)indexPath
@@ -4181,123 +4170,25 @@ ChangePasswordCoordinatorBridgePresenterDelegate>
self.notificationSettingsBridgePresenter = nil;
}
#pragma mark - SignOutFlowPresenterDelegate
#pragma mark - SecureBackupSetupCoordinatorBridgePresenter
- (void)showSecureBackupSetupFromSignOutFlow
- (void)signOutFlowPresenterDidStartLoading:(SignOutFlowPresenter *)presenter
{
if (self.canSetupSecureBackup)
{
[self setupSecureBackup2];
}
else
{
// Set up cross-signing first
[self setupCrossSigningWithTitle:[VectorL10n secureKeyBackupSetupIntroTitle]
message:[VectorL10n securitySettingsUserPasswordDescription]
success:^{
[self setupSecureBackup2];
} failure:^(NSError *error) {
}];
}
}
- (void)setupSecureBackup2
{
SecureBackupSetupCoordinatorBridgePresenter *secureBackupSetupCoordinatorBridgePresenter = [[SecureBackupSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession allowOverwrite:YES];
secureBackupSetupCoordinatorBridgePresenter.delegate = self;
[secureBackupSetupCoordinatorBridgePresenter presentFrom:self animated:YES];
self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter;
}
- (BOOL)canSetupSecureBackup
{
return [self.mainSession vc_canSetupSecureBackup];
}
#pragma mark - SecureBackupSetupCoordinatorBridgePresenterDelegate
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.secureBackupSetupCoordinatorBridgePresenter = nil;
}
- (void)secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel:(SecureBackupSetupCoordinatorBridgePresenter *)coordinatorBridgePresenter
{
[self.secureBackupSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:nil];
self.secureBackupSetupCoordinatorBridgePresenter = nil;
}
#pragma mark - SignOutAlertPresenterDelegate
- (void)signOutAlertPresenterDidTapBackupAction:(SignOutAlertPresenter * _Nonnull)presenter
{
[self showSecureBackupSetupFromSignOutFlow];
}
- (void)signOutAlertPresenterDidTapSignOutAction:(SignOutAlertPresenter * _Nonnull)presenter
{
// Prevent user to perform user interaction in settings when sign out
// TODO: Prevent user interaction in all application (navigation controller and split view controller included)
[self startActivityIndicator];
self.view.userInteractionEnabled = NO;
self.signOutButton.enabled = NO;
[self startActivityIndicator];
MXWeakify(self);
[[AppDelegate theDelegate] logoutWithConfirmation:NO completion:^(BOOL isLoggedOut) {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
self.view.userInteractionEnabled = YES;
self.signOutButton.enabled = YES;
}];
}
- (void)setupCrossSigningWithTitle:(NSString*)title
message:(NSString*)message
success:(void (^)(void))success
failure:(void (^)(NSError *error))failure
- (void)signOutFlowPresenterDidStopLoading:(SignOutFlowPresenter *)presenter
{
[self startActivityIndicator];
self.view.userInteractionEnabled = NO;
MXWeakify(self);
void (^animationCompletion)(void) = ^void () {
MXStrongifyAndReturnIfNil(self);
[self stopActivityIndicator];
self.view.userInteractionEnabled = YES;
[self.crossSigningSetupCoordinatorBridgePresenter dismissWithAnimated:YES completion:^{}];
self.crossSigningSetupCoordinatorBridgePresenter = nil;
};
CrossSigningSetupCoordinatorBridgePresenter *crossSigningSetupCoordinatorBridgePresenter = [[CrossSigningSetupCoordinatorBridgePresenter alloc] initWithSession:self.mainSession];
[crossSigningSetupCoordinatorBridgePresenter presentWith:title
message:message
from:self
animated:YES
success:^{
animationCompletion();
success();
} cancel:^{
animationCompletion();
failure(nil);
} failure:^(NSError * _Nonnull error) {
animationCompletion();
[[AppDelegate theDelegate] showErrorAsAlert:error];
failure(error);
}];
self.crossSigningSetupCoordinatorBridgePresenter = crossSigningSetupCoordinatorBridgePresenter;
[self stopActivityIndicator];
self.view.userInteractionEnabled = YES;
self.signOutButton.enabled = YES;
}
- (void)signOutFlowPresenter:(SignOutFlowPresenter *)presenter didFailWith:(NSError *)error
{
[[AppDelegate theDelegate] showErrorAsAlert:error];
}
#pragma mark - SingleImagePickerPresenterDelegate
@@ -0,0 +1,162 @@
//
// Copyright 2022 New Vector Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
@objc protocol SignOutFlowPresenterDelegate {
/// The presenter is starting an operation that might take while and the UI should indicate this.
func signOutFlowPresenterDidStartLoading(_ presenter: SignOutFlowPresenter)
/// The presenter has finished an operation and the UI should indicate this if necessary.
func signOutFlowPresenterDidStopLoading(_ presenter: SignOutFlowPresenter)
/// The presenter encountered an error and has stopped.
func signOutFlowPresenter(_ presenter: SignOutFlowPresenter, didFailWith error: Error)
}
/// This class provides a reusable component to present the sign out flow
/// for the current session, including the initial prompt, and any follow-up
/// key-backup setup that is necessary for the user.
@objcMembers class SignOutFlowPresenter: NSObject {
private let session: MXSession
private let presentingViewController: UIViewController
private var signOutAlertPresenter = SignOutAlertPresenter()
weak var delegate: SignOutFlowPresenterDelegate?
init(session: MXSession, presentingViewController: UIViewController) {
self.session = session
self.presentingViewController = presentingViewController
super.init()
signOutAlertPresenter.delegate = self
}
/// Starts the flow without a specific source view. On iPad any popups
/// will show from the presenting view controller itself.
func start() {
start(sourceView: presentingViewController.view)
}
/// Starts the flow, presenting any popups on iPad from the specified view.
func start(sourceView: UIView?) {
guard let keyBackup = session.crypto?.backup else { return }
signOutAlertPresenter.present(for: keyBackup.state,
areThereKeysToBackup: keyBackup.hasKeysToBackup,
from: presentingViewController,
sourceView: sourceView ?? presentingViewController.view,
animated: true)
}
// MARK: - SecureBackupSetupCoordinatorBridgePresenter
private var secureBackupSetupCoordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter?
private var crossSigningSetupCoordinatorBridgePresenter: CrossSigningSetupCoordinatorBridgePresenter?
private func showSecureBackupSetupFromSignOutFlow() {
if canSetupSecureBackup {
setupSecureBackup()
} else {
// Set up cross-signing first
setupCrossSigning(title: VectorL10n.secureKeyBackupSetupIntroTitle,
message: VectorL10n.securitySettingsUserPasswordDescription) { [weak self] result in
guard let self = self else { return }
switch result {
case .success(let isCompleted):
if isCompleted {
self.setupSecureBackup()
}
case .failure(let error):
self.delegate?.signOutFlowPresenter(self, didFailWith: error)
}
}
}
}
private var canSetupSecureBackup: Bool {
return session.vc_canSetupSecureBackup()
}
private func setupSecureBackup() {
let secureBackupSetupCoordinatorBridgePresenter = SecureBackupSetupCoordinatorBridgePresenter(session: session, allowOverwrite: true)
secureBackupSetupCoordinatorBridgePresenter.delegate = self
secureBackupSetupCoordinatorBridgePresenter.present(from: presentingViewController, animated: true)
self.secureBackupSetupCoordinatorBridgePresenter = secureBackupSetupCoordinatorBridgePresenter
}
private func setupCrossSigning(title: String, message: String, completion: @escaping (Result<Bool, Error>) -> Void) {
delegate?.signOutFlowPresenterDidStartLoading(self)
let dismissAnimation = { [weak self] in
guard let self = self else { return }
self.delegate?.signOutFlowPresenterDidStopLoading(self)
self.crossSigningSetupCoordinatorBridgePresenter?.dismiss(animated: true, completion: {
self.crossSigningSetupCoordinatorBridgePresenter = nil
})
}
let crossSigningSetupCoordinatorBridgePresenter = CrossSigningSetupCoordinatorBridgePresenter(session: session)
crossSigningSetupCoordinatorBridgePresenter.present(with: title, message: message, from: presentingViewController, animated: true) {
dismissAnimation()
completion(.success(true))
} cancel: {
dismissAnimation()
completion(.success(false))
} failure: { error in
dismissAnimation()
completion(.failure(error))
}
self.crossSigningSetupCoordinatorBridgePresenter = crossSigningSetupCoordinatorBridgePresenter
}
}
// MARK: - SignOutAlertPresenterDelegate
extension SignOutFlowPresenter: SignOutAlertPresenterDelegate {
func signOutAlertPresenterDidTapSignOutAction(_ presenter: SignOutAlertPresenter) {
// Allow presenting screen to black user interaction when signing out
// TODO: Prevent user interaction in all application (navigation controller and split view controller included)
delegate?.signOutFlowPresenterDidStartLoading(self)
AppDelegate.theDelegate().logout(withConfirmation: false) { [weak self] isLoggedOut in
guard let self = self else { return }
self.delegate?.signOutFlowPresenterDidStopLoading(self)
}
}
func signOutAlertPresenterDidTapBackupAction(_ presenter: SignOutAlertPresenter) {
showSecureBackupSetupFromSignOutFlow()
}
}
// MARK: - SecureBackupSetupCoordinatorBridgePresenterDelegate
extension SignOutFlowPresenter: SecureBackupSetupCoordinatorBridgePresenterDelegate {
func secureBackupSetupCoordinatorBridgePresenterDelegateDidCancel(_ coordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter) {
coordinatorBridgePresenter.dismiss(animated: true) {
self.secureBackupSetupCoordinatorBridgePresenter = nil
}
}
func secureBackupSetupCoordinatorBridgePresenterDelegateDidComplete(_ coordinatorBridgePresenter: SecureBackupSetupCoordinatorBridgePresenter) {
coordinatorBridgePresenter.dismiss(animated: true) {
self.secureBackupSetupCoordinatorBridgePresenter = nil
}
}
}