diff --git a/Sources/BackupFeature/Controllers/BackupPassphraseController.swift b/Sources/BackupFeature/Controllers/BackupPassphraseController.swift index b5bd426b6f9242c42d507f4dd5feedd8abccdf17..97c23d21b497a643a7850f3b9147723053dd1e9e 100644 --- a/Sources/BackupFeature/Controllers/BackupPassphraseController.swift +++ b/Sources/BackupFeature/Controllers/BackupPassphraseController.swift @@ -2,7 +2,6 @@ import UIKit import Shared import Combine import InputField -import ScrollViewController public final class BackupPassphraseController: UIViewController { lazy private var screenView = BackupPassphraseView() @@ -21,7 +20,6 @@ public final class BackupPassphraseController: UIViewController { private let cancelClosure: EmptyClosure private let stringClosure: StringClosure private var cancellables = Set<AnyCancellable>() - private let keyboardListener = KeyboardFrameChangeListener(notificationCenter: .default) public init( _ cancelClosure: @escaping EmptyClosure, @@ -35,66 +33,41 @@ public final class BackupPassphraseController: UIViewController { required init?(coder: NSCoder) { nil } public override func loadView() { - let view = UIView() - view.addSubview(screenView) - - screenView.snp.makeConstraints { make in - make.top.equalToSuperview() - make.left.equalToSuperview() - make.right.equalToSuperview() - make.bottom.equalToSuperview().offset(0) - } - - self.view = view + view = screenView } public override func viewDidLoad() { super.viewDidLoad() - setupKeyboard() setupBindings() - - screenView.continueButton.isEnabled = false - } - - private func setupKeyboard() { - keyboardListener.keyboardFrameWillChange = { [weak self] keyboard in - guard let self = self else { return } - - let inset = self.view.frame.height - self.view.convert(keyboard.frame, from: nil).minY - - self.screenView.snp.updateConstraints { - $0.bottom.equalToSuperview().offset(-inset) - } - - self.view.setNeedsLayout() - - UIView.animate(withDuration: keyboard.animationDuration) { - self.view.layoutIfNeeded() - } - } } private func setupBindings() { - screenView.inputField.returnPublisher - .sink { [unowned self] in screenView.inputField.endEditing(true) } - .store(in: &cancellables) - - screenView.cancelButton - .publisher(for: .touchUpInside) - .sink { [unowned self] in dismiss(animated: true) { self.cancelClosure() }} - .store(in: &cancellables) + screenView + .inputField + .returnPublisher + .sink { [unowned self] in + screenView.inputField.endEditing(true) + }.store(in: &cancellables) - screenView.inputField + screenView + .inputField .textPublisher - .sink { [unowned self] in passphrase = $0.trimmingCharacters(in: .whitespacesAndNewlines) } - .store(in: &cancellables) + .sink { [unowned self] in + passphrase = $0.trimmingCharacters(in: .whitespacesAndNewlines) + }.store(in: &cancellables) + + screenView + .continueButton + .publisher(for: .touchUpInside) + .sink { [unowned self] in + dismiss(animated: true) { self.stringClosure(self.passphrase) } + }.store(in: &cancellables) - screenView.continueButton + screenView + .cancelButton .publisher(for: .touchUpInside) .sink { [unowned self] in - dismiss(animated: true) { - self.stringClosure(self.passphrase) - } + dismiss(animated: true) { self.cancelClosure() } }.store(in: &cancellables) } } diff --git a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift index a49b51bd28fe3a670efeba3c2c9cff8e34e31af6..f16acd561f4ca41c9f2abe0db1101a58df4cfcad 100644 --- a/Sources/BackupFeature/Coordinator/BackupCoordinator.swift +++ b/Sources/BackupFeature/Coordinator/BackupCoordinator.swift @@ -1,6 +1,7 @@ import UIKit import Shared import Presentation +import ScrollViewController public protocol BackupCoordinating { func toDrawer( @@ -16,12 +17,18 @@ public protocol BackupCoordinating { } public struct BackupCoordinator: BackupCoordinating { - var bottomPresenter: Presenting = BottomPresenter() + var fullscreenPresenter: Presenting = FullscreenPresenter() - var passphraseFactory: (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController + var passphraseFactory: ( + @escaping EmptyClosure, + @escaping StringClosure + ) -> UIViewController public init( - passphraseFactory: @escaping (@escaping EmptyClosure, @escaping StringClosure) -> UIViewController + passphraseFactory: @escaping ( + @escaping EmptyClosure, + @escaping StringClosure + ) -> UIViewController ) { self.passphraseFactory = passphraseFactory } @@ -32,7 +39,8 @@ public extension BackupCoordinator { _ screen: UIViewController, from parent: UIViewController ) { - bottomPresenter.present(screen, from: parent) + let target = ScrollViewController.embedding(screen) + fullscreenPresenter.present(target, from: parent) } func toPassphrase( @@ -41,6 +49,21 @@ public extension BackupCoordinator { passphraseClosure: @escaping StringClosure ) { let screen = passphraseFactory(cancelClosure, passphraseClosure) - bottomPresenter.present(screen, from: parent) + let target = ScrollViewController.embedding(screen) + fullscreenPresenter.present(target, from: parent) + } +} + +extension ScrollViewController { + static func embedding(_ viewController: UIViewController) -> ScrollViewController { + let scrollViewController = ScrollViewController() + scrollViewController.addChild(viewController) + scrollViewController.contentView = viewController.view + scrollViewController.wrapperView.handlesTouchesOutsideContent = false + scrollViewController.wrapperView.alignContentToBottom = true + scrollViewController.scrollView.bounces = false + + viewController.didMove(toParent: scrollViewController) + return scrollViewController } } diff --git a/Sources/BackupFeature/Views/BackupPassphraseView.swift b/Sources/BackupFeature/Views/BackupPassphraseView.swift index b23c5dcdb5195a7d0f5ff3ac10757ac143ae683f..f2ff27b4d01652f72abab2482a70db9f031d5e55 100644 --- a/Sources/BackupFeature/Views/BackupPassphraseView.swift +++ b/Sources/BackupFeature/Views/BackupPassphraseView.swift @@ -4,49 +4,62 @@ import InputField final class BackupPassphraseView: UIView { let titleLabel = UILabel() - let subtitleLabel = UILabel() - let inputField = InputField() let stackView = UIStackView() - let continueButton = CapsuleButton() + let inputField = InputField() + let subtitleLabel = UILabel() let cancelButton = CapsuleButton() + let continueButton = CapsuleButton() init() { super.init(frame: .zero) - setup() - } - - required init?(coder: NSCoder) { nil } - - private func setup() { layer.cornerRadius = 40 backgroundColor = Asset.neutralWhite.color layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] - subtitleLabel.numberOfLines = 0 - titleLabel.textColor = Asset.neutralActive.color - subtitleLabel.textColor = Asset.neutralActive.color + setupInput() + setupLabels() + setupButtons() + setupStackView() + } + + required init?(coder: NSCoder) { nil } + private func setupInput() { inputField.setup( style: .regular, - title: "Passphrase", - placeholder: "* * * * * *", - subtitleColor: Asset.neutralDisabled.color + title: Localized.Backup.Passphrase.Input.title, + placeholder: Localized.Backup.Passphrase.Input.placeholder, + rightView: .toggleSecureEntry, + subtitleColor: Asset.neutralDisabled.color, + allowsEmptySpace: false, + autocapitalization: .none, + contentType: .newPassword ) + } - titleLabel.text = "Secure your backup" + private func setupLabels() { titleLabel.textAlignment = .left + titleLabel.text = Localized.Backup.Passphrase.title + titleLabel.textColor = Asset.neutralActive.color titleLabel.font = Fonts.Mulish.bold.font(size: 26.0) - subtitleLabel.text = "Please select a password for your backup. If you lose this password, you will not be able to restore your account. Make sure to keep a record somewhere safe. Your password needs to be at least 8 characters with at least 1 uppercase, 1 lowercase and 1 number characters" + subtitleLabel.numberOfLines = 0 subtitleLabel.textAlignment = .left + subtitleLabel.textColor = Asset.neutralActive.color + subtitleLabel.text = Localized.Backup.Passphrase.subtitle subtitleLabel.font = Fonts.Mulish.regular.font(size: 16.0) + } - continueButton.setStyle(.brandColored) - continueButton.setTitle("Set password and continue", for: .normal) - + private func setupButtons() { cancelButton.setStyle(.seeThrough) - cancelButton.setTitle("Cancel", for: .normal) + cancelButton.setTitle(Localized.Backup.Passphrase.cancel, for: .normal) + + continueButton.isEnabled = false + continueButton.setStyle(.brandColored) + continueButton.setTitle(Localized.Backup.Passphrase.continue, for: .normal) + } + private func setupStackView() { stackView.spacing = 20 stackView.axis = .vertical stackView.addArrangedSubview(titleLabel) @@ -57,11 +70,11 @@ final class BackupPassphraseView: UIView { addSubview(stackView) - stackView.snp.makeConstraints { make in - make.top.equalToSuperview().offset(60) - make.left.equalToSuperview().offset(50) - make.right.equalToSuperview().offset(-50) - make.bottom.equalToSuperview().offset(-70) + stackView.snp.makeConstraints { + $0.top.equalToSuperview().offset(60) + $0.left.equalToSuperview().offset(50) + $0.right.equalToSuperview().offset(-50) + $0.bottom.equalToSuperview().offset(-70) } } } diff --git a/Sources/InputField/InputField.swift b/Sources/InputField/InputField.swift index 4174ff0a78abc8f3f597e3d7298a92f93f2a3fa8..e041db906a504c4a1e6295c84e14a456a18b357c 100644 --- a/Sources/InputField/InputField.swift +++ b/Sources/InputField/InputField.swift @@ -197,7 +197,9 @@ public final class InputField: UIView { } private func hideButtonImage(isSecureEntry: Bool) -> UIImage? { - isSecureEntry ? Asset.eyeClosed.image : Asset.eyeOpen.image + let openImage = Asset.eyeOpen.image.withTintColor(Asset.neutralWeak.color) + let closedImage = Asset.eyeClosed.image.withTintColor(Asset.neutralWeak.color) + return isSecureEntry ? closedImage : openImage } private func setup() { diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift index 975cacada35bc7b5205a6b6761517801ae27c5ea..3177da720c32c5e4209fa4dc0c9c1211d6e65b5e 100644 --- a/Sources/Shared/AutoGenerated/Strings.swift +++ b/Sources/Shared/AutoGenerated/Strings.swift @@ -274,6 +274,22 @@ public enum Localized { /// Backup settings public static let title = Localized.tr("Localizable", "backup.config.title") } + public enum Passphrase { + /// Cancel + public static let cancel = Localized.tr("Localizable", "backup.passphrase.cancel") + /// Set password and continue + public static let `continue` = Localized.tr("Localizable", "backup.passphrase.continue") + /// Please select a password for your backup. If you lose this password, you will not be able to restore your account. Make sure to keep a record somewhere safe. Your password needs to be at least 8 characters with at least 1 uppercase, 1 lowercase and 1 number characters + public static let subtitle = Localized.tr("Localizable", "backup.passphrase.subtitle") + /// Secure your backup + public static let title = Localized.tr("Localizable", "backup.passphrase.title") + public enum Input { + /// * * * * * * + public static let placeholder = Localized.tr("Localizable", "backup.passphrase.input.placeholder") + /// Passphrase + public static let title = Localized.tr("Localizable", "backup.passphrase.input.title") + } + } public enum Setup { /// Setup your #backup service#. public static let title = Localized.tr("Localizable", "backup.setup.title") diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings index 24d260d891069004978304b748f6387888c4e71f..a82b151e1ff1fb0a65ddffb5060c922057679532 100644 --- a/Sources/Shared/Resources/en.lproj/Localizable.strings +++ b/Sources/Shared/Resources/en.lproj/Localizable.strings @@ -656,6 +656,19 @@ "backup.config.infrastructure" = "Backup over"; +"backup.passphrase.title" += "Secure your backup"; +"backup.passphrase.subtitle" += "Please select a password for your backup. If you lose this password, you will not be able to restore your account. Make sure to keep a record somewhere safe. Your password needs to be at least 8 characters with at least 1 uppercase, 1 lowercase and 1 number characters"; +"backup.passphrase.input.title" += "Passphrase"; +"backup.passphrase.input.placeholder" += "* * * * * *"; +"backup.passphrase.continue" += "Set password and continue"; +"backup.passphrase.cancel" += "Cancel"; + "backup.iCloud" = "iCloud"; "backup.dropbox"