123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455 |
- import UIKit
- import DcCore
- class WelcomeViewController: UIViewController, ProgressAlertHandler {
- weak var coordinator: WelcomeCoordinator?
- private let dcContext: DcContext
- private var scannedQrCode: String?
- var configureProgressObserver: Any?
- var onProgressSuccess: VoidFunction?
- private lazy var scrollView: UIScrollView = {
- let scrollView = UIScrollView()
- scrollView.showsVerticalScrollIndicator = false
- return scrollView
- }()
- private lazy var welcomeView: WelcomeContentView = {
- let view = WelcomeContentView()
- view.onLogin = {
- [unowned self] in
- self.coordinator?.showLogin()
- }
- view.onScanQRCode = {
- [unowned self] in
- self.showQRReader()
- }
- view.translatesAutoresizingMaskIntoConstraints = false
- return view
- }()
- private lazy var qrCordeReader: QrCodeReaderController = {
- let controller = QrCodeReaderController()
- controller.delegate = self
- return controller
- }()
- private lazy var qrCodeReaderNav: UINavigationController = {
- let nav = UINavigationController(rootViewController: qrCordeReader)
- nav.modalPresentationStyle = .fullScreen
- return nav
- }()
- lazy var progressAlert: UIAlertController = {
- let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
- alert.addAction(UIAlertAction(
- title: String.localized("cancel"),
- style: .cancel,
- handler: { _ in
- self.dcContext.stopOngoingProcess()
- }))
- return alert
- }()
- init(dcContext: DcContext) {
- self.dcContext = dcContext
- super.init(nibName: nil, bundle: nil)
- onProgressSuccess = {[unowned self] in
- self.coordinator?.handleQRAccountCreationSuccess()
- }
- }
-
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- // MARK: - lifecycle
- override func viewDidLoad() {
- super.viewDidLoad()
- setupSubviews()
- }
- override func viewDidLayoutSubviews() {
- super.viewDidLayoutSubviews()
- welcomeView.minContainerHeight = view.frame.height
- }
- override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
- super.viewWillTransition(to: size, with: coordinator)
- welcomeView.minContainerHeight = size.height
- scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: true)
- }
- override func viewDidDisappear(_ animated: Bool) {
- let nc = NotificationCenter.default
- if let configureProgressObserver = self.configureProgressObserver {
- nc.removeObserver(configureProgressObserver)
- }
- }
- // MARK: - setup
- private func setupSubviews() {
- view.addSubview(scrollView)
- scrollView.translatesAutoresizingMaskIntoConstraints = false
- scrollView.addSubview(welcomeView)
- let frameGuide = scrollView.frameLayoutGuide
- let contentGuide = scrollView.contentLayoutGuide
- frameGuide.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0).isActive = true
- frameGuide.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true
- frameGuide.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0).isActive = true
- frameGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
- contentGuide.leadingAnchor.constraint(equalTo: welcomeView.leadingAnchor).isActive = true
- contentGuide.topAnchor.constraint(equalTo: welcomeView.topAnchor).isActive = true
- contentGuide.trailingAnchor.constraint(equalTo: welcomeView.trailingAnchor).isActive = true
- contentGuide.bottomAnchor.constraint(equalTo: welcomeView.bottomAnchor).isActive = true
- // this enables vertical scrolling
- frameGuide.widthAnchor.constraint(equalTo: contentGuide.widthAnchor).isActive = true
- }
- /// if active the welcomeViewController will show nothing but a centered activity indicator
- func activateSpinner(_ active: Bool) {
- welcomeView.showSpinner(active)
- }
- // MARK: - actions
- private func showQRReader(completion onComplete: VoidFunction? = nil) {
- present(qrCodeReaderNav, animated: true) {
- onComplete?()
- }
- }
- private func createAccountFromQRCode() {
- guard let code = scannedQrCode else {
- return
- }
- let success = dcContext.configureAccountFromQR(qrCode: code)
- scannedQrCode = nil
- if success {
- if let loginCompletion = self.onProgressSuccess {
- addProgressAlertListener(onSuccess: loginCompletion)
- showProgressAlert(title: String.localized("qraccount_use_on_new_install"))
- }
- dcContext.configure()
- } else {
- accountCreationErrorAlert()
- }
- }
- private func accountCreationErrorAlert() {
- func handleRepeat() {
- showQRReader(completion: { [unowned self] in
- self.activateSpinner(false)
- })
- }
- let title = String.localized("qraccount_creation_failed")
- let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
- let okAction = UIAlertAction(
- title: String.localized("ok"),
- style: .default,
- handler: { [unowned self] _ in
- self.activateSpinner(false)
- }
- )
- let repeatAction = UIAlertAction(
- title: String.localized("global_menu_edit_redo_desktop"),
- style: .default,
- handler: { _ in
- handleRepeat()
- }
- )
- alert.addAction(okAction)
- alert.addAction(repeatAction)
- present(alert, animated: true)
- }
- }
- extension WelcomeViewController: QrCodeReaderDelegate {
- func handleQrCode(_ code: String) {
- let lot = dcContext.checkQR(qrCode: code)
- if let domain = lot.text1, lot.state == DC_QR_ACCOUNT {
- self.scannedQrCode = code
- confirmAccountCreationAlert(accountDomain: domain)
- } else {
- qrErrorAlert()
- }
- }
- private func confirmAccountCreationAlert(accountDomain domain: String) {
- let title = String.localizedStringWithFormat(NSLocalizedString("qraccount_ask_create_and_login", comment: ""), domain)
- let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
- let okAction = UIAlertAction(
- title: String.localized("ok"),
- style: .default,
- handler: { [unowned self] _ in
- self.activateSpinner(true)
- self.qrCodeReaderNav.dismiss(animated: true) {
- self.createAccountFromQRCode()
- }
- }
- )
- let qrCancelAction = UIAlertAction(
- title: String.localized("cancel"),
- style: .cancel,
- handler: { [unowned self] _ in
- self.qrCodeReaderNav.dismiss(animated: true) {
- self.scannedQrCode = nil
- }
- }
- )
- alert.addAction(okAction)
- alert.addAction(qrCancelAction)
- qrCodeReaderNav.present(alert, animated: true)
- }
- private func qrErrorAlert() {
- let title = String.localized("qraccount_qr_code_cannot_be_used")
- let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
- let okAction = UIAlertAction(
- title: String.localized("ok"),
- style: .default,
- handler: { [unowned self] _ in
- self.qrCordeReader.startSession()
- }
- )
- let qrCancelAction = UIAlertAction(
- title: String.localized("cancel"),
- style: .cancel,
- handler: { [unowned self] _ in
- self.qrCodeReaderNav.dismiss(animated: true) {
- self.scannedQrCode = nil
- }
- }
- )
- alert.addAction(okAction)
- alert.addAction(qrCancelAction)
- qrCodeReaderNav.present(alert, animated: true, completion: nil)
- }
- }
- // MARK: - WelcomeContentView
- class WelcomeContentView: UIView {
- var onLogin: VoidFunction?
- var onScanQRCode: VoidFunction?
- var onImportBackup: VoidFunction?
- var minContainerHeight: CGFloat = 0 {
- didSet {
- containerMinHeightConstraint.constant = minContainerHeight
- logoHeightConstraint.constant = calculateLogoHeight()
- }
- }
- private lazy var containerMinHeightConstraint: NSLayoutConstraint = {
- return container.heightAnchor.constraint(greaterThanOrEqualToConstant: 0)
- }()
- private lazy var logoHeightConstraint: NSLayoutConstraint = {
- return logoView.heightAnchor.constraint(equalToConstant: 0)
- }()
- private var container = UIView()
- private var logoView: UIImageView = {
- let image = #imageLiteral(resourceName: "dc_logo")
- let view = UIImageView(image: image)
- return view
- }()
- private lazy var titleLabel: UILabel = {
- let label = UILabel()
- label.text = String.localized("welcome_desktop")
- label.textColor = DcColors.grayTextColor
- label.textAlignment = .center
- label.numberOfLines = 0
- label.font = UIFont.systemFont(ofSize: 24, weight: .bold)
- return label
- }()
- private lazy var subtitleLabel: UILabel = {
- let label = UILabel()
- label.text = String.localized("welcome_intro1_message")
- label.font = UIFont.systemFont(ofSize: 22, weight: .regular)
- label.textColor = DcColors.grayTextColor
- label.numberOfLines = 0
- label.textAlignment = .center
- return label
- }()
- private lazy var loginButton: UIButton = {
- let button = UIButton(type: .roundedRect)
- let title = String.localized("login_header").uppercased()
- button.setTitle(title, for: .normal)
- button.titleLabel?.font = UIFont.systemFont(ofSize: 18, weight: .regular)
- button.setTitleColor(.white, for: .normal)
- button.backgroundColor = DcColors.primary
- let insets = button.contentEdgeInsets
- button.contentEdgeInsets = UIEdgeInsets(top: 8, left: 15, bottom: 8, right: 15)
- button.layer.cornerRadius = 5
- button.clipsToBounds = true
- button.addTarget(self, action: #selector(loginButtonPressed(_:)), for: .touchUpInside)
- return button
- }()
- private lazy var buttonStack: UIStackView = {
- let stack = UIStackView(arrangedSubviews: [loginButton, qrCodeButton /*, importBackupButton */])
- stack.axis = .vertical
- stack.spacing = 15
- return stack
- }()
- private lazy var qrCodeButton: UIButton = {
- let button = UIButton()
- let title = String.localized("qrscan_title")
- button.setTitleColor(UIColor.systemBlue, for: .normal)
- button.setTitle(title, for: .normal)
- button.addTarget(self, action: #selector(qrCodeButtonPressed(_:)), for: .touchUpInside)
- return button
- }()
- private lazy var importBackupButton: UIButton = {
- let button = UIButton()
- let title = String.localized("import_backup_title")
- button.setTitleColor(UIColor.systemBlue, for: .normal)
- button.setTitle(title, for: .normal)
- button.addTarget(self, action: #selector(importBackupButtonPressed(_:)), for: .touchUpInside)
- return button
- }()
- private var activityIndicator: UIActivityIndicatorView = {
- let view: UIActivityIndicatorView
- if #available(iOS 13, *) {
- view = UIActivityIndicatorView(style: .large)
- } else {
- view = UIActivityIndicatorView(style: .whiteLarge)
- view.color = UIColor.gray
- }
- view.isHidden = true
- return view
- }()
- private let defaultSpacing: CGFloat = 20
- init() {
- super.init(frame: .zero)
- setupSubviews()
- backgroundColor = DcColors.defaultBackgroundColor
- }
- required init?(coder: NSCoder) {
- fatalError("init(coder:) has not been implemented")
- }
- // MARK: - setup
- private func setupSubviews() {
- addSubview(container)
- container.translatesAutoresizingMaskIntoConstraints = false
- container.topAnchor.constraint(equalTo: topAnchor).isActive = true
- container.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
- container.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 0.75).isActive = true
- container.centerXAnchor.constraint(equalTo: centerXAnchor, constant: 0).isActive = true
- containerMinHeightConstraint.isActive = true
- _ = [logoView, titleLabel, subtitleLabel].map {
- addSubview($0)
- $0.translatesAutoresizingMaskIntoConstraints = false
- }
- let bottomLayoutGuide = UILayoutGuide()
- container.addLayoutGuide(bottomLayoutGuide)
- bottomLayoutGuide.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
- bottomLayoutGuide.heightAnchor.constraint(equalTo: container.heightAnchor, multiplier: 0.55).isActive = true
- subtitleLabel.topAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
- subtitleLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
- subtitleLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
- subtitleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
- titleLabel.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
- titleLabel.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
- titleLabel.bottomAnchor.constraint(equalTo: subtitleLabel.topAnchor, constant: -defaultSpacing).isActive = true
- titleLabel.setContentHuggingPriority(.defaultHigh, for: .vertical)
- logoView.bottomAnchor.constraint(equalTo: titleLabel.topAnchor, constant: -defaultSpacing).isActive = true
- logoView.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
- logoHeightConstraint.constant = calculateLogoHeight()
- logoHeightConstraint.isActive = true
- logoView.widthAnchor.constraint(equalTo: logoView.heightAnchor).isActive = true
- let logoTopAnchor = logoView.topAnchor.constraint(equalTo: container.topAnchor, constant: 20) // this will allow the container to grow in height
- logoTopAnchor.priority = .defaultLow
- logoTopAnchor.isActive = true
- let buttonContainerGuide = UILayoutGuide()
- container.addLayoutGuide(buttonContainerGuide)
- buttonContainerGuide.topAnchor.constraint(equalTo: subtitleLabel.bottomAnchor).isActive = true
- buttonContainerGuide.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
- loginButton.setContentHuggingPriority(.defaultHigh, for: .vertical)
- container.addSubview(buttonStack)
- buttonStack.translatesAutoresizingMaskIntoConstraints = false
- buttonStack.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
- buttonStack.centerYAnchor.constraint(equalTo: buttonContainerGuide.centerYAnchor).isActive = true
- let buttonStackTopAnchor = buttonStack.topAnchor.constraint(equalTo: buttonContainerGuide.topAnchor, constant: defaultSpacing)
- // this will allow the container to grow in height
- let buttonStackBottomAnchor = buttonStack.bottomAnchor.constraint(equalTo: buttonContainerGuide.bottomAnchor, constant: -50)
- _ = [buttonStackTopAnchor, buttonStackBottomAnchor].map {
- $0.priority = .defaultLow
- $0.isActive = true
- }
- addSubview(activityIndicator)
- activityIndicator.translatesAutoresizingMaskIntoConstraints = false
- activityIndicator.centerYAnchor.constraint(equalTo: buttonContainerGuide.centerYAnchor).isActive = true
- activityIndicator.centerXAnchor.constraint(equalTo: container.centerXAnchor).isActive = true
- }
- private func calculateLogoHeight() -> CGFloat {
- let titleHeight = titleLabel.intrinsicContentSize.height
- let subtitleHeight = subtitleLabel.intrinsicContentSize.height
- let intrinsicHeight = subtitleHeight + titleHeight
- let maxHeight: CGFloat = 100
- return intrinsicHeight > maxHeight ? maxHeight : intrinsicHeight
- }
- // MARK: - actions
- @objc private func loginButtonPressed(_ sender: UIButton) {
- onLogin?()
- }
- @objc private func qrCodeButtonPressed(_ sender: UIButton) {
- onScanQRCode?()
- }
- @objc private func importBackupButtonPressed(_ sender: UIButton) {
- onImportBackup?()
- }
- func showSpinner(_ show: Bool) {
- if show {
- activityIndicator.startAnimating()
- } else {
- activityIndicator.stopAnimating()
- }
- activityIndicator.isHidden = !show
- buttonStack.isHidden = show
- }
- }
|