Просмотр исходного кода

Merge pull request #605 from deltachat/welcomeScreen

Welcome screen
cyBerta 5 лет назад
Родитель
Сommit
ede65c0942

+ 5 - 1
deltachat-ios.xcodeproj/project.pbxproj

@@ -132,6 +132,7 @@
 		AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */; };
 		AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */; };
 		AE728F15229D5C390047565B /* PhotoPickerAlertAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */; };
+		AE76E5EE242BF2EA003CF461 /* WelcomeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE76E5ED242BF2EA003CF461 /* WelcomeViewController.swift */; };
 		AE77838D23E32ED20093EABD /* ContactDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE77838C23E32ED20093EABD /* ContactDetailViewModel.swift */; };
 		AE77838F23E4276D0093EABD /* ContactCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE77838E23E4276D0093EABD /* ContactCellViewModel.swift */; };
 		AE8519EA2272FDCA00ED86F0 /* DeviceContactsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */; };
@@ -364,6 +365,7 @@
 		AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailHeader.swift; sourceTree = "<group>"; };
 		AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupViewController.swift; sourceTree = "<group>"; };
 		AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerAlertAction.swift; sourceTree = "<group>"; };
+		AE76E5ED242BF2EA003CF461 /* WelcomeViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WelcomeViewController.swift; sourceTree = "<group>"; };
 		AE77838C23E32ED20093EABD /* ContactDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewModel.swift; sourceTree = "<group>"; };
 		AE77838E23E4276D0093EABD /* ContactCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCellViewModel.swift; sourceTree = "<group>"; };
 		AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContactsHandler.swift; sourceTree = "<group>"; };
@@ -754,6 +756,7 @@
 				AEE6EC472283045D00EDC689 /* EditSettingsController.swift */,
 				AE851ACF227DF50900ED86F0 /* GroupChatDetailViewController.swift */,
 				AEE6EC3E2282C59C00EDC689 /* GroupMembersViewController.swift */,
+				AE19887423EB264000B4CD5F /* HelpViewController.swift */,
 				AEE6EC402282DF5700EDC689 /* MailboxViewController.swift */,
 				785BE16721E247F1003BE98C /* MessageInfoViewController.swift */,
 				7AE0A5481FC42F65005ECB4B /* NewChatViewController.swift */,
@@ -765,7 +768,7 @@
 				30149D9222F21129003C12B5 /* QrViewController.swift */,
 				B21005DA23383664004C70C5 /* SettingsClassicViewController.swift */,
 				78E45E3921D3CFBC00D4B15E /* SettingsController.swift */,
-				AE19887423EB264000B4CD5F /* HelpViewController.swift */,
+				AE76E5ED242BF2EA003CF461 /* WelcomeViewController.swift */,
 			);
 			path = Controller;
 			sourceTree = "<group>";
@@ -1201,6 +1204,7 @@
 				302B84CE2397F6CD001C261F /* URL+Extension.swift in Sources */,
 				305961CC2346125100C80F33 /* UIView+Extensions.swift in Sources */,
 				7A9FB1441FB061E2001FEA36 /* AppDelegate.swift in Sources */,
+				AE76E5EE242BF2EA003CF461 /* WelcomeViewController.swift in Sources */,
 				305961F52346125100C80F33 /* TypingIndicatorCell.swift in Sources */,
 				AEE56D7D2253ADB4007DC082 /* HudHandler.swift in Sources */,
 				305961FF2346125100C80F33 /* AvatarView.swift in Sources */,

+ 23 - 0
deltachat-ios/Assets.xcassets/dc_logo.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "filename" : "dc_icon_1x.png",
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "filename" : "dc_icon_2x.png",
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "filename" : "dc_icon_3x.png",
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "author" : "xcode",
+    "version" : 1
+  }
+}

BIN
deltachat-ios/Assets.xcassets/dc_logo.imageset/dc_icon_1x.png


BIN
deltachat-ios/Assets.xcassets/dc_logo.imageset/dc_icon_2x.png


BIN
deltachat-ios/Assets.xcassets/dc_logo.imageset/dc_icon_3x.png


+ 2 - 3
deltachat-ios/Controller/AccountSetupController.swift

@@ -825,15 +825,14 @@ class AccountSetupController: UITableViewController {
             appDelegate.open()
             appDelegate.start()
 
-            appDelegate.appCoordinator.presentLoginController()
+            appDelegate.appCoordinator.presentWelcomeController(animated: true)
         }))
         alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel))
         present(alert, animated: true, completion: nil)
     }
 
     private func handleLoginSuccess() {
-        // used when login hud successfully went trough
-        dismiss(animated: true, completion: nil)
+        // used when login hud successfully went through
         let appDelegate = UIApplication.shared.delegate as! AppDelegate
         appDelegate.registerForPushNotifications()
         initSelectionCells();

+ 278 - 0
deltachat-ios/Controller/WelcomeViewController.swift

@@ -0,0 +1,278 @@
+import UIKit
+
+class WelcomeViewController: UIViewController {
+
+    weak var coordinator: WelcomeCoordinator?
+
+    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.translatesAutoresizingMaskIntoConstraints = false
+        return view
+    }()
+
+    // will be shown while transitioning to tabBarController
+    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
+    }()
+
+    // 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)
+    }
+
+    // MARK: - setup
+    private func setupSubviews() {
+
+        view.addSubview(activityIndicator)
+        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+        activityIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
+        activityIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
+
+        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
+
+        frameGuide.widthAnchor.constraint(equalTo: contentGuide.widthAnchor).isActive = true
+    }
+
+    func setTransitionState(_ transitioning: Bool) {
+        if transitioning {
+            activityIndicator.startAnimating()
+        } else {
+            activityIndicator.stopAnimating()
+        }
+        activityIndicator.isHidden = !transitioning
+        scrollView.isHidden = transitioning
+    }
+}
+
+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 = "log in to your server".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 = 10
+        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 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, loginButton /*, qrCodeButton, importBackupButton */].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
+        }
+    }
+
+    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?()
+     }
+}

+ 67 - 4
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -4,6 +4,7 @@ import Photos
 import MobileCoreServices
 
 class AppCoordinator: NSObject, Coordinator {
+
     private let window: UIWindow
     private let dcContext: DcContext
     private let qrTab = 0
@@ -22,11 +23,22 @@ class AppCoordinator: NSObject, Coordinator {
         return tabBarController
     }()
 
+    private lazy var welcomeController: WelcomeViewController = {
+        let welcomeController = WelcomeViewController()
+        welcomeController.coordinator = self
+        return welcomeController
+    }()
+
     private lazy var loginController: UIViewController = {
         let accountSetupController = AccountSetupController(dcContext: dcContext, editView: false)
         let nav = UINavigationController(rootViewController: accountSetupController)
         let coordinator = AccountSetupCoordinator(dcContext: dcContext, navigationController: nav)
-        coordinator.onLoginSuccess = presentTabBarController
+        coordinator.onLoginSuccess = {
+            [unowned self] in
+            self.loginController.dismiss(animated: true) {
+                self.presentTabBarController()
+            }
+        }
         childCoordinators.append(coordinator)
         accountSetupController.coordinator = coordinator
         return nav
@@ -76,7 +88,7 @@ class AppCoordinator: NSObject, Coordinator {
         if dcContext.isConfigured() {
             presentTabBarController()
         } else {
-            presentLoginController()
+            showWelomeController()
         }
     }
 
@@ -118,8 +130,20 @@ class AppCoordinator: NSObject, Coordinator {
         }
     }
 
-    func presentLoginController() {
-        window.rootViewController = loginController
+    func presentWelcomeController(animated: Bool) {
+        if animated {
+            welcomeController.setTransitionState(true)
+            showWelomeController()
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                self.welcomeController.setTransitionState(false)
+            }
+        } else {
+            showWelomeController()
+        }
+    }
+
+    private func showWelomeController() {
+        window.rootViewController = welcomeController
         window.makeKeyAndVisible()
     }
 
@@ -130,6 +154,40 @@ class AppCoordinator: NSObject, Coordinator {
     }
 }
 
+extension AppCoordinator: WelcomeCoordinator {
+    func showLogin() {
+        // add cancel button item to accountSetupController
+        if let nav = loginController as? UINavigationController, let loginController = nav.topViewController as? AccountSetupController {
+            loginController.navigationItem.leftBarButtonItem = UIBarButtonItem(
+                title: String.localized("cancel"),
+                style: .done,
+                target: self, action: #selector(cancelButtonPressed(_:))
+            )
+            loginController.coordinator?.onLoginSuccess = handleLoginSuccess
+        }
+        loginController.modalPresentationStyle = .fullScreen
+        welcomeController.present(loginController, animated: true, completion: nil)
+    }
+
+    func showQR() {
+        return
+    }
+
+    private func handleLoginSuccess() {
+        welcomeController.setTransitionState(true) // this will hide welcomeController's content
+        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
+            self.loginController.dismiss(animated: true) {
+                self.presentTabBarController()
+                self.welcomeController.setTransitionState(false)
+            }
+        }
+    }
+
+    @objc private func cancelButtonPressed(_ sender: UIBarButtonItem) {
+        loginController.dismiss(animated: true, completion: nil)
+    }
+}
+
 // since mailbox and chatView -tab both use ChatViewController we want to be able to assign different functionality via coordinators -> therefore we override unneeded functions such as showChatDetail -> maybe find better solution in longterm
 class MailboxCoordinator: ChatViewCoordinator {
 
@@ -804,3 +862,8 @@ protocol EditContactCoordinatorProtocol: class {
     func navigateBack()
     func showChat(chatId: Int)
 }
+
+protocol WelcomeCoordinator: class {
+    func showLogin()
+    func showQR()
+}