/* Copyright 2020-2024 New Vector Ltd. Copyright (c) 2021 BWI GmbH SPDX-License-Identifier: AGPL-3.0-only Please see LICENSE in the repository root for full details. */ import UIKit protocol SecureBackupSetupIntroViewControllerDelegate: AnyObject { func secureBackupSetupIntroViewControllerDidTapUseKey(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) func secureBackupSetupIntroViewControllerDidTapUsePassphrase(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) func secureBackupSetupIntroViewControllerDidCancel(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController, showSkipAlert: Bool) func secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(_ secureBackupSetupIntroViewController: SecureBackupSetupIntroViewController) } @objcMembers final class SecureBackupSetupIntroViewController: UIViewController { // MARK: - Properties // MARK: Outlets @IBOutlet private weak var informationLabel: UILabel! @IBOutlet private weak var topSeparatorView: UIView! @IBOutlet private weak var secureKeyCell: SecureBackupSetupIntroCell! @IBOutlet private weak var securePassphraseCell: SecureBackupSetupIntroCell! // MARK: Private private var viewModel: SecureBackupSetupIntroViewModelType! private var cancellable: Bool! private var theme: Theme! private var activityIndicatorPresenter: ActivityIndicatorPresenter! private var errorPresenter: MXKErrorPresentation! // MARK: Public weak var delegate: SecureBackupSetupIntroViewControllerDelegate? // MARK: - Setup class func instantiate(with viewModel: SecureBackupSetupIntroViewModelType, cancellable: Bool) -> SecureBackupSetupIntroViewController { let viewController = StoryboardScene.SecureBackupSetupIntroViewController.initialScene.instantiate() viewController.viewModel = viewModel viewController.cancellable = cancellable viewController.theme = ThemeService.shared().theme return viewController } // MARK: - Life cycle override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. self.vc_removeBackTitle() self.setupViews() self.activityIndicatorPresenter = ActivityIndicatorPresenter() self.errorPresenter = MXKErrorAlertPresentation() self.registerThemeServiceDidChangeThemeNotification() self.update(theme: self.theme) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.checkKeyBackup() } override var preferredStatusBarStyle: UIStatusBarStyle { return self.theme.statusBarStyle } // MARK: - Private private func setupViews() { if self.cancellable { let cancelBarButtonItem = MXKBarButtonItem(title: VectorL10n.cancel, style: .plain) { [weak self] in guard let self = self else { return } self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: true) } self.navigationItem.rightBarButtonItem = cancelBarButtonItem } self.title = BWIL10n.secureKeyBackupSetupIntroTitle self.informationLabel.text = BWIL10n.secureKeyBackupSetupIntroInfo self.secureKeyCell.fill(title: VectorL10n.secureKeyBackupSetupIntroUseSecurityKeyTitle, information: VectorL10n.secureKeyBackupSetupIntroUseSecurityKeyInfo, image: Asset.Images.secretsSetupKey.image) self.secureKeyCell.action = { [weak self] in guard let self = self else { return } self.delegate?.secureBackupSetupIntroViewControllerDidTapUseKey(self) } self.securePassphraseCell.fill(title: BWIL10n.secureKeyBackupSetupIntroUseSecurityPassphraseTitle, information: BWIL10n.secureKeyBackupSetupIntroUseSecurityPassphraseInfo, image: Asset.Images.secretsSetupPassphrase.image) self.securePassphraseCell.action = { [weak self] in guard let self = self else { return } self.delegate?.secureBackupSetupIntroViewControllerDidTapUsePassphrase(self) } setupBackupMethods() } private func setupBackupMethods() { let secureBackupSetupMethods = self.viewModel.homeserverEncryptionConfiguration.secureBackupSetupMethods // Hide setup methods that are not listed if !secureBackupSetupMethods.contains(.key) { self.secureKeyCell.isHidden = true } if !secureBackupSetupMethods.contains(.passphrase) { self.securePassphraseCell.isHidden = true } } private func renderLoading() { self.activityIndicatorPresenter.presentActivityIndicator(on: self.view, animated: true) } private func renderLoaded() { self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) } private func render(error: Error) { self.activityIndicatorPresenter.removeCurrentActivityIndicator(animated: true) self.errorPresenter.presentError(from: self, forError: error, animated: true, handler: nil) } private func update(theme: Theme) { self.theme = theme self.view.backgroundColor = theme.headerBackgroundColor if let navigationBar = self.navigationController?.navigationBar { theme.applyStyle(onNavigationBar: navigationBar) } self.informationLabel.textColor = theme.textPrimaryColor self.topSeparatorView.backgroundColor = theme.lineBreakColor self.secureKeyCell.update(theme: theme) self.securePassphraseCell.update(theme: theme) } private func registerThemeServiceDidChangeThemeNotification() { NotificationCenter.default.addObserver(self, selector: #selector(themeDidChange), name: .themeServiceDidChangeTheme, object: nil) } @objc private func themeDidChange() { self.update(theme: ThemeService.shared().theme) } // TODO: To remove private func checkKeyBackup() { guard self.viewModel.checkKeyBackup else { return } guard let keyBackup = self.viewModel.keyBackup else { return } // If a backup already exists and we do not have the private key, // we need to get this private key first. Ask the user to make a key backup restore to catch it if keyBackup.keyBackupVersion != nil && keyBackup.hasPrivateKeyInCryptoStore == false { // don't show dialog if forced, already asked before in mandatory verification recover if BWIBuildSettings.shared.forceResetBackupIfLost { let alertController = UIAlertController(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorTitle, message: VectorL10n.secureKeyBackupSetupExistingBackupErrorInfo, preferredStyle: .alert) let connectAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorUnlockIt, style: .default) { (_) in self.delegate?.secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(self) } let resetAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorDeleteIt, style: .destructive) { (_) in self.deleteKeybackup() } } else { let alertContoller = UIAlertController(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorTitle, message: VectorL10n.secureKeyBackupSetupExistingBackupErrorInfo, preferredStyle: .alert) let connectAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorUnlockIt, style: .default) { (_) in self.delegate?.secureBackupSetupIntroViewControllerDidTapConnectToKeyBackup(self) } let resetAction = UIAlertAction(title: VectorL10n.secureKeyBackupSetupExistingBackupErrorDeleteIt, style: .destructive) { (_) in self.deleteKeybackup() } let cancelAction = UIAlertAction(title: VectorL10n.cancel, style: .cancel) { (_) in self.delegate?.secureBackupSetupIntroViewControllerDidCancel(self, showSkipAlert: false) } alertContoller.addAction(connectAction) alertContoller.addAction(resetAction) alertContoller.addAction(cancelAction) self.present(alertContoller, animated: true) } } } // TODO: Move to view model private func deleteKeybackup() { guard let keyBackup = self.viewModel.keyBackup, let keybackupVersion = keyBackup.keyBackupVersion?.version else { return } self.renderLoading() keyBackup.deleteVersion(keybackupVersion, success: { [weak self] in guard let self = self else { return } self.renderLoaded() self.checkKeyBackup() }, failure: { [weak self] (error) in guard let self = self else { return } self.render(error: error) }) } }