From 0338b976d40cb6ff065d230d881f0cfcd54fb673 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 12 Jul 2021 13:53:59 +1000 Subject: [PATCH] Implement UI for delete entire account option --- .../Translations/en.lproj/Localizable.strings | 3 + Session/Settings/NukeDataModal.swift | 189 ++++++++++++------ 2 files changed, 132 insertions(+), 60 deletions(-) diff --git a/Session/Meta/Translations/en.lproj/Localizable.strings b/Session/Meta/Translations/en.lproj/Localizable.strings index 4cff5baea..5e7354cb3 100644 --- a/Session/Meta/Translations/en.lproj/Localizable.strings +++ b/Session/Meta/Translations/en.lproj/Localizable.strings @@ -494,6 +494,9 @@ "modal_seed_explanation" = "This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device."; "modal_clear_all_data_title" = "Clear All Data"; "modal_clear_all_data_explanation" = "This will permanently delete your messages, sessions, and contacts."; +"modal_clear_all_data_explanation_2" = "Would you like to clear only this device, or delete your entire account?"; +"modal_clear_all_data_device_only_button_title" = "Device Only"; +"modal_clear_all_data_entire_account_button_title" = "Entire Account"; "vc_qr_code_title" = "QR Code"; "vc_qr_code_view_my_qr_code_tab_title" = "View My QR Code"; "vc_qr_code_view_scan_qr_code_tab_title" = "Scan QR Code"; diff --git a/Session/Settings/NukeDataModal.swift b/Session/Settings/NukeDataModal.swift index 0e74d8690..2b5be8c01 100644 --- a/Session/Settings/NukeDataModal.swift +++ b/Session/Settings/NukeDataModal.swift @@ -1,73 +1,142 @@ +import SessionSnodeKit @objc(LKNukeDataModal) final class NukeDataModal : Modal { + // MARK: Components + private lazy var titleLabel: UILabel = { + let result = UILabel() + result.textColor = Colors.text + result.font = .boldSystemFont(ofSize: Values.mediumFontSize) + result.text = NSLocalizedString("modal_clear_all_data_title", comment: "") + result.numberOfLines = 0 + result.lineBreakMode = .byWordWrapping + result.textAlignment = .center + return result + }() + + private lazy var explanationLabel: UILabel = { + let result = UILabel() + result.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) + result.font = .systemFont(ofSize: Values.smallFontSize) + result.text = NSLocalizedString("modal_clear_all_data_explanation", comment: "") + result.numberOfLines = 0 + result.textAlignment = .center + result.lineBreakMode = .byWordWrapping + return result + }() + + private lazy var clearDataButton: UIButton = { + let result = UIButton() + result.set(.height, to: Values.mediumButtonHeight) + result.layer.cornerRadius = Modal.buttonCornerRadius + if isDarkMode { + result.backgroundColor = Colors.destructive + } + result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) + result.setTitleColor(isLightMode ? Colors.destructive : Colors.text, for: UIControl.State.normal) + result.setTitle(NSLocalizedString("TXT_DELETE_TITLE", comment: ""), for: UIControl.State.normal) + result.addTarget(self, action: #selector(clearAllData), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var buttonStackView1: UIStackView = { + let result = UIStackView(arrangedSubviews: [ cancelButton, clearDataButton ]) + result.axis = .horizontal + result.spacing = Values.mediumSpacing + result.distribution = .fillEqually + return result + }() + + private lazy var deviceOnlyButton: UIButton = { + let result = UIButton() + result.set(.height, to: Values.mediumButtonHeight) + result.layer.cornerRadius = Modal.buttonCornerRadius + result.backgroundColor = Colors.buttonBackground + result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) + result.setTitleColor(Colors.text, for: UIControl.State.normal) + result.setTitle(NSLocalizedString("modal_clear_all_data_device_only_button_title", comment: ""), for: UIControl.State.normal) + result.addTarget(self, action: #selector(clearDeviceOnly), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var entireAccountButton: UIButton = { + let result = UIButton() + result.set(.height, to: Values.mediumButtonHeight) + result.layer.cornerRadius = Modal.buttonCornerRadius + if isDarkMode { + result.backgroundColor = Colors.destructive + } + result.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) + result.setTitleColor(isLightMode ? Colors.destructive : Colors.text, for: UIControl.State.normal) + result.setTitle(NSLocalizedString("modal_clear_all_data_entire_account_button_title", comment: ""), for: UIControl.State.normal) + result.addTarget(self, action: #selector(clearEntireAccount), for: UIControl.Event.touchUpInside) + return result + }() + + private lazy var buttonStackView2: UIStackView = { + let result = UIStackView(arrangedSubviews: [ deviceOnlyButton, entireAccountButton ]) + result.axis = .horizontal + result.spacing = Values.mediumSpacing + result.distribution = .fillEqually + result.alpha = 0 + return result + }() + + private lazy var buttonStackViewContainer: UIView = { + let result = UIView() + result.addSubview(buttonStackView2) + buttonStackView2.pin(to: result) + result.addSubview(buttonStackView1) + buttonStackView1.pin(to: result) + return result + }() + + private lazy var mainStackView: UIStackView = { + let result = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, buttonStackViewContainer ]) + result.axis = .vertical + result.spacing = Values.largeSpacing + return result + }() + // MARK: Lifecycle override func populateContentView() { - // Set up title label - let titleLabel = UILabel() - titleLabel.textColor = Colors.text - titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize) - titleLabel.text = NSLocalizedString("modal_clear_all_data_title", comment: "") - titleLabel.numberOfLines = 0 - titleLabel.lineBreakMode = .byWordWrapping - titleLabel.textAlignment = .center - // Set up explanation label - let explanationLabel = UILabel() - explanationLabel.textColor = Colors.text.withAlphaComponent(Values.mediumOpacity) - explanationLabel.font = .systemFont(ofSize: Values.smallFontSize) - explanationLabel.text = NSLocalizedString("modal_clear_all_data_explanation", comment: "") - explanationLabel.numberOfLines = 0 - explanationLabel.textAlignment = .center - explanationLabel.lineBreakMode = .byWordWrapping - // Set up nuke data button - let nukeDataButton = UIButton() - nukeDataButton.set(.height, to: Values.mediumButtonHeight) - nukeDataButton.layer.cornerRadius = Modal.buttonCornerRadius - if isDarkMode { - nukeDataButton.backgroundColor = Colors.destructive - } - nukeDataButton.titleLabel!.font = .systemFont(ofSize: Values.smallFontSize) - nukeDataButton.setTitleColor(isLightMode ? Colors.destructive : Colors.text, for: UIControl.State.normal) - nukeDataButton.setTitle(NSLocalizedString("TXT_DELETE_TITLE", comment: ""), for: UIControl.State.normal) - nukeDataButton.addTarget(self, action: #selector(nuke), for: UIControl.Event.touchUpInside) - // Set up button stack view - let buttonStackView = UIStackView(arrangedSubviews: [ cancelButton, nukeDataButton ]) - buttonStackView.axis = .horizontal - buttonStackView.spacing = Values.mediumSpacing - buttonStackView.distribution = .fillEqually - // Set up stack view - let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, buttonStackView ]) - stackView.axis = .vertical - stackView.spacing = Values.largeSpacing - contentView.addSubview(stackView) - stackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) - stackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) - contentView.pin(.trailing, to: .trailing, of: stackView, withInset: Values.largeSpacing) - contentView.pin(.bottom, to: .bottom, of: stackView, withInset: Values.largeSpacing) + contentView.addSubview(mainStackView) + mainStackView.pin(.leading, to: .leading, of: contentView, withInset: Values.largeSpacing) + mainStackView.pin(.top, to: .top, of: contentView, withInset: Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: mainStackView, withInset: Values.largeSpacing) + contentView.pin(.bottom, to: .bottom, of: mainStackView, withInset: Values.largeSpacing) } // MARK: Interaction - @objc private func nuke() { - func proceed() { - let appDelegate = UIApplication.shared.delegate as! AppDelegate - ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in - appDelegate.forceSyncConfigurationNowIfNeeded().ensure(on: DispatchQueue.main) { - self?.dismiss(animated: true, completion: nil) // Dismiss the loader - UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later - NotificationCenter.default.post(name: .dataNukeRequested, object: nil) - }.retainUntilComplete() - } + @objc private func clearAllData() { + UIView.animate(withDuration: 0.25) { + self.buttonStackView1.alpha = 0 + self.buttonStackView2.alpha = 1 } - if KeyPairUtilities.hasV2KeyPair() { - proceed() - } else { - presentingViewController?.dismiss(animated: true, completion: nil) - let message = "We’ve upgraded the way Session IDs are generated, so you will be unable to restore your current Session ID." - let alert = UIAlertController(title: "Are You Sure?", message: message, preferredStyle: .alert) - alert.addAction(UIAlertAction(title: "Yes", style: .destructive) { _ in proceed() }) - alert.addAction(UIAlertAction(title: "Cancel", style: .default, handler: nil)) - presentingViewController?.present(alert, animated: true, completion: nil) + UIView.transition(with: explanationLabel, duration: 0.25, options: .transitionCrossDissolve, animations: { + self.explanationLabel.text = NSLocalizedString("modal_clear_all_data_explanation_2", comment: "") + }, completion: nil) + } + + @objc private func clearDeviceOnly() { + let appDelegate = UIApplication.shared.delegate as! AppDelegate + ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in + appDelegate.forceSyncConfigurationNowIfNeeded().ensure(on: DispatchQueue.main) { + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later + NotificationCenter.default.post(name: .dataNukeRequested, object: nil) + }.retainUntilComplete() + } + } + + @objc private func clearEntireAccount() { + ModalActivityIndicatorViewController.present(fromViewController: self, canCancel: false) { [weak self] _ in + SnodeAPI.clearAllData().ensure(on: DispatchQueue.main) { + self?.dismiss(animated: true, completion: nil) // Dismiss the loader + UserDefaults.removeAll() // Not done in the nuke data implementation as unlinking requires this to happen later + NotificationCenter.default.post(name: .dataNukeRequested, object: nil) + }.retainUntilComplete() } } }