Browse Source

Merge pull request #611 from deltachat/qr_login_welcome

Qr login welcome
cyBerta 5 years ago
parent
commit
579e8e6260

+ 4 - 0
deltachat-ios.xcodeproj/project.pbxproj

@@ -157,6 +157,7 @@
 		AEE6EC3F2282C59C00EDC689 /* GroupMembersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE6EC3E2282C59C00EDC689 /* GroupMembersViewController.swift */; };
 		AEE6EC412282DF5700EDC689 /* MailboxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE6EC402282DF5700EDC689 /* MailboxViewController.swift */; };
 		AEE6EC482283045D00EDC689 /* EditSettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE6EC472283045D00EDC689 /* EditSettingsController.swift */; };
+		AEE700252438E0E500D6992E /* ProgressAlertHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE700242438E0E500D6992E /* ProgressAlertHandler.swift */; };
 		AEFBE22F23FEF23D0045327A /* ProviderInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFBE22E23FEF23D0045327A /* ProviderInfoCell.swift */; };
 		AEFBE23123FF09B20045327A /* TypeAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEFBE23023FF09B20045327A /* TypeAlias.swift */; };
 		B20462E42440A4A600367A57 /* SettingsAutodelOverviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B20462E32440A4A600367A57 /* SettingsAutodelOverviewController.swift */; };
@@ -395,6 +396,7 @@
 		AEE6EC3E2282C59C00EDC689 /* GroupMembersViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMembersViewController.swift; sourceTree = "<group>"; };
 		AEE6EC402282DF5700EDC689 /* MailboxViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MailboxViewController.swift; sourceTree = "<group>"; };
 		AEE6EC472283045D00EDC689 /* EditSettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditSettingsController.swift; sourceTree = "<group>"; };
+		AEE700242438E0E500D6992E /* ProgressAlertHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressAlertHandler.swift; sourceTree = "<group>"; };
 		AEFBE22E23FEF23D0045327A /* ProviderInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderInfoCell.swift; sourceTree = "<group>"; };
 		AEFBE23023FF09B20045327A /* TypeAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeAlias.swift; sourceTree = "<group>"; };
 		B20462E02440805C00367A57 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -851,6 +853,7 @@
 				AEC67A1B241CE9E4007DDBE1 /* AppStateRestorer.swift */,
 				AEE56D7C2253ADB4007DC082 /* HudHandler.swift */,
 				AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */,
+				AEE700242438E0E500D6992E /* ProgressAlertHandler.swift */,
 			);
 			path = Handler;
 			sourceTree = "<group>";
@@ -1196,6 +1199,7 @@
 				305961E62346125100C80F33 /* LocationMessageSnapshotOptions.swift in Sources */,
 				AEE6EC3F2282C59C00EDC689 /* GroupMembersViewController.swift in Sources */,
 				B26B3BC7236DC3DC008ED35A /* SwitchCell.swift in Sources */,
+				AEE700252438E0E500D6992E /* ProgressAlertHandler.swift in Sources */,
 				78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */,
 				AE8519EA2272FDCA00ED86F0 /* DeviceContactsHandler.swift in Sources */,
 				3059620B2346125100C80F33 /* LocationMessageSizeCalculator.swift in Sources */,

+ 16 - 45
deltachat-ios/Controller/AccountSetupController.swift

@@ -1,14 +1,16 @@
 import SafariServices
 import UIKit
 
-class AccountSetupController: UITableViewController {
+class AccountSetupController: UITableViewController, ProgressAlertHandler {
 
     weak var coordinator: AccountSetupCoordinator?
 
     private let dcContext: DcContext
     private var skipOauth = false
     private var backupProgressObserver: Any?
-    private var configureProgressObserver: Any?
+    var configureProgressObserver: Any?
+    var onProgressSuccess: VoidFunction? // not needed here
+
     private var oauth2Observer: Any?
 
     private let tagEmailCell = 0
@@ -70,7 +72,7 @@ class AccountSetupController: UITableViewController {
 
     // MARK: - the progress dialog
 
-    private lazy var configProgressAlert: UIAlertController = {
+    lazy var progressAlert: UIAlertController = {
         let alert = UIAlertController(title: "", message: "", preferredStyle: .alert)
         alert.addAction(UIAlertAction(
             title: String.localized("cancel"),
@@ -81,37 +83,6 @@ class AccountSetupController: UITableViewController {
         return alert
     }()
 
-    private func showProgressHud(title: String) {
-        configProgressAlert.actions[0].isEnabled = true
-        configProgressAlert.title = title
-        configProgressAlert.message = String.localized("one_moment")
-        present(configProgressAlert, animated: true, completion: nil)
-    }
-
-    private func updateProgressHud(error message: String?) {
-        DispatchQueue.main.async(execute: {
-            self.configProgressAlert.dismiss(animated: false)
-            let errorAlert = UIAlertController(title: String.localized("error"), message: message, preferredStyle: .alert)
-            errorAlert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
-            self.present(errorAlert, animated: true, completion: nil)
-        })
-    }
-
-    private func updateProgressHudSuccess() {
-        updateProgressHudValue(value: 1000)
-        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
-            self.configProgressAlert.dismiss(animated: true) {
-                self.handleLoginSuccess()
-            }
-        })
-    }
-
-    private func updateProgressHudValue(value: Int?) {
-        if let value = value {
-            configProgressAlert.message = String.localized("one_moment") + " " + String(value/10) + "%"
-        }
-    }
-
     // MARK: - cells
 
     private lazy var emailCell: TextFieldCell = {
@@ -570,7 +541,7 @@ class AccountSetupController: UITableViewController {
         login(emailAddress: emailAddress, password: password)
     }
 
-    func updateProviderInfo() {
+    private func updateProviderInfo() {
             provider = dcContext.getProviderFromEmail(addr: emailCell.getText() ?? "")
         if let hint = provider?.beforeLoginHint,
             let status = provider?.status,
@@ -585,14 +556,14 @@ class AccountSetupController: UITableViewController {
         }
     }
 
-    func showProviderInfo() {
+    private func showProviderInfo() {
         basicSectionCells = [emailCell, passwordCell, providerInfoCell]
         let providerInfoCellIndexPath = IndexPath(row: 2, section: 0)
         tableView.insertRows(at: [providerInfoCellIndexPath], with: .fade)
         providerInfoShowing = true
     }
 
-    func hideProviderInfo() {
+    private func hideProviderInfo() {
         providerInfoCell.updateInfo(hint: nil, hintType: .none)
         basicSectionCells = [emailCell, passwordCell]
         let providerInfoCellIndexPath = IndexPath(row: 2, section: 0)
@@ -612,7 +583,7 @@ class AccountSetupController: UITableViewController {
 
         print("oAuth-Flag when loggin in: \(dcContext.getAuthFlags())")
         dcContext.configure()
-        showProgressHud(title: String.localized("login_header"))
+        showProgressAlert(title: String.localized("login_header"))
     }
 
     @objc func closeButtonPressed() {
@@ -690,11 +661,11 @@ class AccountSetupController: UITableViewController {
             notification in
             if let ui = notification.userInfo {
                 if ui["error"] as! Bool {
-                    self.updateProgressHud(error: ui["errorMessage"] as? String)
+                    self.updateProgressAlert(error: ui["errorMessage"] as? String)
                 } else if ui["done"] as! Bool {
-                    self.updateProgressHudSuccess()
+                    self.updateProgressAlertSuccess(completion: self.handleLoginSuccess)
                 } else {
-                    self.updateProgressHudValue(value: ui["progress"] as? Int)
+                    self.updateProgressAlertValue(value: ui["progress"] as? Int)
                 }
             }
         }
@@ -710,11 +681,11 @@ class AccountSetupController: UITableViewController {
             notification in
             if let ui = notification.userInfo {
                 if ui["error"] as! Bool {
-                    self.updateProgressHud(error: ui["errorMessage"] as? String)
+                    self.updateProgressAlert(error: ui["errorMessage"] as? String)
                 } else if ui["done"] as! Bool {
-                    self.updateProgressHudSuccess()
+                    self.updateProgressAlertSuccess(completion: self.handleLoginSuccess)
                 } else {
-                    self.updateProgressHudValue(value: ui["progress"] as? Int)
+                    self.updateProgressAlertValue(value: ui["progress"] as? Int)
                 }
             }
         }
@@ -757,7 +728,7 @@ class AccountSetupController: UITableViewController {
 
             if let file = dcContext.imexHasBackup(filePath: documents[0]) {
                 logger.info("restoring backup: \(file)")
-                showProgressHud(title: String.localized("import_backup_title"))
+                showProgressAlert(title: String.localized("import_backup_title"))
                 dcContext.imex(what: DC_IMEX_IMPORT_BACKUP, directory: file)
             }
             else {

+ 94 - 24
deltachat-ios/Controller/QrCodeReaderController.swift

@@ -2,20 +2,43 @@ import AVFoundation
 import UIKit
 
 class QrCodeReaderController: UIViewController {
-    var captureSession = AVCaptureSession()
-
-    var videoPreviewLayer: AVCaptureVideoPreviewLayer?
 
     weak var delegate: QrCodeReaderDelegate?
 
+    private let captureSession = AVCaptureSession()
+
+    private lazy var videoPreviewLayer: AVCaptureVideoPreviewLayer = {
+        let videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
+        videoPreviewLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
+        return videoPreviewLayer
+    }()
+
+    private var infoLabel: UILabel = {
+        let label = UILabel()
+           label.translatesAutoresizingMaskIntoConstraints = false
+           label.text = String.localized("qrscan_hint")
+           label.lineBreakMode = .byWordWrapping
+           label.numberOfLines = 0
+           label.textAlignment = .center
+           label.textColor = .white
+           return label
+    }()
+
+    private lazy var closeButton: UIBarButtonItem = {
+        return UIBarButtonItem(title: String.localized("cancel"), style: .done, target: self, action: #selector(closeButtonPressed(_:)))
+    }()
+
+
     private let supportedCodeTypes = [
         AVMetadataObject.ObjectType.qr
     ]
 
+    // MARK: - lifecycle
     override func viewDidLoad() {
         super.viewDidLoad()
         self.edgesForExtendedLayout = []
         title = String.localized("qrscan_title")
+        navigationItem.leftBarButtonItem = closeButton
 
         guard let captureDevice = AVCaptureDevice.DiscoverySession.init(
             deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera],
@@ -40,33 +63,23 @@ class QrCodeReaderController: UIViewController {
             return
         }
 
-        videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
-        videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
-        videoPreviewLayer?.frame = view.layer.bounds
-        view.layer.addSublayer(videoPreviewLayer!)
-
-        let infoLabel = createInfoLabel()
-        view.addSubview(infoLabel)
-        view.addConstraint(infoLabel.constraintAlignBottomTo(view, paddingBottom: 8))
-        view.addConstraint(infoLabel.constraintCenterXTo(view))
-        view.bringSubviewToFront(infoLabel)
+        setupSubviews()
     }
 
-    private func createInfoLabel() -> UIView {
-        let label = UILabel()
-        label.translatesAutoresizingMaskIntoConstraints = false
-        label.text = String.localized("qrscan_hint")
-        label.lineBreakMode = .byWordWrapping
-        label.numberOfLines = 0
-        label.textAlignment = .center
-        label.textColor = .white
-        return label
+    override func viewWillAppear(_ animated: Bool) {
+        captureSession.startRunning()
     }
 
+  override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
+        super.viewWillTransition(to: size, with: coordinator)
 
-    override func viewWillAppear(_ animated: Bool) {
-        captureSession.startRunning()
+        coordinator.animate(alongsideTransition: nil, completion: { [weak self] _ in
+            DispatchQueue.main.async(execute: {
+                self?.updateVideoOrientation()
+            })
+        })
     }
+
     override func viewWillDisappear(_ animated: Bool) {
         captureSession.stopRunning()
     }
@@ -75,6 +88,50 @@ class QrCodeReaderController: UIViewController {
         super.didReceiveMemoryWarning()
         // Dispose of any resources that can be recreated.
     }
+
+    // MARK: - setup
+    private func setupSubviews() {
+        view.layer.addSublayer(videoPreviewLayer)
+        videoPreviewLayer.frame = view.layer.bounds
+        view.addSubview(infoLabel)
+        infoLabel.translatesAutoresizingMaskIntoConstraints = false
+
+        infoLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
+        infoLabel.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
+        view.bringSubviewToFront(infoLabel)
+    }
+
+    private func updateVideoOrientation() {
+
+        guard let connection = videoPreviewLayer.connection else {
+            return
+        }
+
+        guard connection.isVideoOrientationSupported else {
+            return
+        }
+
+        let statusBarOrientation = UIApplication.shared.statusBarOrientation
+        let videoOrientation: AVCaptureVideoOrientation =  statusBarOrientation.videoOrientation ?? .portrait
+
+        if connection.videoOrientation == videoOrientation {
+            print("no change to videoOrientation")
+            return
+        }
+
+        videoPreviewLayer.frame = view.bounds
+        connection.videoOrientation = videoOrientation
+        videoPreviewLayer.removeAllAnimations()
+    }
+
+    // MARK: - actions
+    @objc private func closeButtonPressed(_ sender: UIBarButtonItem) {
+        self.dismiss(animated: true, completion: nil)
+    }
+
+    func startSession() {
+        captureSession.startRunning()
+    }
 }
 
 extension QrCodeReaderController: AVCaptureMetadataOutputObjectsDelegate {
@@ -84,8 +141,21 @@ extension QrCodeReaderController: AVCaptureMetadataOutputObjectsDelegate {
             if supportedCodeTypes.contains(metadataObj.type) {
                 if metadataObj.stringValue != nil {
                     self.delegate?.handleQrCode(metadataObj.stringValue!)
+                    self.captureSession.stopRunning()
                 }
             }
         }
     }
 }
+
+extension UIInterfaceOrientation {
+    var videoOrientation: AVCaptureVideoOrientation? {
+        switch self {
+        case .portraitUpsideDown: return .portraitUpsideDown
+        case .landscapeRight: return .landscapeRight
+        case .landscapeLeft: return .landscapeLeft
+        case .portrait: return .portrait
+        default: return nil
+        }
+    }
+}

+ 83 - 83
deltachat-ios/Controller/QrViewController.swift

@@ -1,7 +1,7 @@
 import Foundation
 import UIKit
 
-class QrViewController: UITableViewController, QrCodeReaderDelegate {
+class QrViewController: UITableViewController {
     private let rowQRCode = 0
     private let rowScanQR = 1
 
@@ -129,85 +129,7 @@ class QrViewController: UITableViewController, QrCodeReaderDelegate {
         }
     }
 
-    //QRCodeDelegate
-    func handleQrCode(_ code: String) {
-        //remove qr code scanner view
-        if let ctrl = navigationController,
-            let lastController = ctrl.viewControllers.last {
-                if type(of: lastController) === QrCodeReaderController.self {
-                    ctrl.viewControllers.removeLast()
-                }
-        }
-
-        let qrParsed: DcLot = self.dcContext.checkQR(qrCode: code)
-        let state = Int32(qrParsed.state)
-        switch state {
-        case DC_QR_ASK_VERIFYCONTACT:
-            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
-            joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), nameAndAddress), code: code)
-
-        case DC_QR_ASK_VERIFYGROUP:
-            let groupName = qrParsed.text1 ?? "ErrGroupName"
-            joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("qrscan_ask_join_group"), groupName), code: code)
-
-        case DC_QR_FPR_WITHOUT_ADDR:
-            let msg = String.localized("qrscan_no_addr_found") + "\n\n" +
-                String.localized("qrscan_fingerprint_label") + ":\n" + (qrParsed.text1 ?? "")
-            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
-            present(alert, animated: true, completion: nil)
-
-        case DC_QR_FPR_MISMATCH:
-            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
-            let msg = String.localizedStringWithFormat(String.localized("qrscan_fingerprint_mismatch"), nameAndAddress)
-            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
-            present(alert, animated: true, completion: nil)
-
-        case DC_QR_ADDR, DC_QR_FPR_OK:
-            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
-            let msg = String.localizedStringWithFormat(String.localized(state==DC_QR_ADDR ? "ask_start_chat_with" : "qrshow_x_verified"), nameAndAddress)
-            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
-                let chatId = self.dcContext.createChatByContactId(contactId: qrParsed.id)
-                self.coordinator?.showChat(chatId: chatId)
-            }))
-            alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
-            present(alert, animated: true, completion: nil)
-
-        case DC_QR_TEXT:
-            let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), qrParsed.text1 ?? "")
-            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
-            present(alert, animated: true, completion: nil)
-
-        case DC_QR_URL:
-            let url = qrParsed.text1 ?? ""
-            let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_url"), url)
-            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: String.localized("open"), style: .default, handler: { _ in
-                if let url = URL(string: url) {
-                    UIApplication.shared.open(url)
-                }
-            }))
-            alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
-            present(alert, animated: true, completion: nil)
-
-        default:
-            var msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), code)
-            if state == DC_QR_ERROR {
-                if let errorMsg = qrParsed.text1 {
-                    msg = errorMsg + "\n\n" + msg
-                }
-            }
-            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
-            present(alert, animated: true, completion: nil)
-        }
-
-    }
-
-    private func joinSecureJoin(alertMessage: String, code: String) {
+      private func joinSecureJoin(alertMessage: String, code: String) {
         let alert = UIAlertController(title: alertMessage,
                                       message: nil,
                                       preferredStyle: .alert)
@@ -288,9 +210,9 @@ class QrViewController: UITableViewController, QrCodeReaderDelegate {
     }
 
     @objc func openQRCodeScanner() {
-        if let ctrl = navigationController {
-            ctrl.pushViewController(qrCodeReaderController, animated: true)
-        }
+        let nav = UINavigationController(rootViewController: qrCodeReaderController)
+        nav.modalPresentationStyle = .fullScreen
+        present(nav, animated: true)
     }
 
     private func createQRCodeView() -> UIView {
@@ -318,3 +240,81 @@ class QrViewController: UITableViewController, QrCodeReaderDelegate {
         navigationController?.pushViewController(chatVC, animated: true)
     }
 }
+
+// MARK: - QRCodeDelegate
+extension QrViewController: QrCodeReaderDelegate {
+
+    func handleQrCode(_ code: String) {
+        qrCodeReaderController.dismiss(animated: true) {
+            self.processQrCode(code)
+        }
+    }
+
+    private func processQrCode(_ code: String) {
+        let qrParsed: DcLot = self.dcContext.checkQR(qrCode: code)
+        let state = Int32(qrParsed.state)
+        switch state {
+        case DC_QR_ASK_VERIFYCONTACT:
+            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
+            joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), nameAndAddress), code: code)
+
+        case DC_QR_ASK_VERIFYGROUP:
+            let groupName = qrParsed.text1 ?? "ErrGroupName"
+            joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("qrscan_ask_join_group"), groupName), code: code)
+
+        case DC_QR_FPR_WITHOUT_ADDR:
+            let msg = String.localized("qrscan_no_addr_found") + "\n\n" +
+                String.localized("qrscan_fingerprint_label") + ":\n" + (qrParsed.text1 ?? "")
+            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
+            present(alert, animated: true, completion: nil)
+
+        case DC_QR_FPR_MISMATCH:
+            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
+            let msg = String.localizedStringWithFormat(String.localized("qrscan_fingerprint_mismatch"), nameAndAddress)
+            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
+            present(alert, animated: true, completion: nil)
+
+        case DC_QR_ADDR, DC_QR_FPR_OK:
+            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
+            let msg = String.localizedStringWithFormat(String.localized(state==DC_QR_ADDR ? "ask_start_chat_with" : "qrshow_x_verified"), nameAndAddress)
+            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
+                let chatId = self.dcContext.createChatByContactId(contactId: qrParsed.id)
+                self.coordinator?.showChat(chatId: chatId)
+            }))
+            alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
+            present(alert, animated: true, completion: nil)
+
+        case DC_QR_TEXT:
+            let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), qrParsed.text1 ?? "")
+            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
+            present(alert, animated: true, completion: nil)
+
+        case DC_QR_URL:
+            let url = qrParsed.text1 ?? ""
+            let msg = String.localizedStringWithFormat(String.localized("qrscan_contains_url"), url)
+            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: String.localized("open"), style: .default, handler: { _ in
+                if let url = URL(string: url) {
+                    UIApplication.shared.open(url)
+                }
+            }))
+            alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
+            present(alert, animated: true, completion: nil)
+
+        default:
+            var msg = String.localizedStringWithFormat(String.localized("qrscan_contains_text"), code)
+            if state == DC_QR_ERROR {
+                if let errorMsg = qrParsed.text1 {
+                    msg = errorMsg + "\n\n" + msg
+                }
+            }
+            let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
+            present(alert, animated: true, completion: nil)
+        }
+    }
+}

+ 204 - 28
deltachat-ios/Controller/WelcomeViewController.swift

@@ -1,8 +1,12 @@
 import UIKit
 
-class WelcomeViewController: UIViewController {
+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()
@@ -16,23 +20,49 @@ class WelcomeViewController: UIViewController {
             [unowned self] in
             self.coordinator?.showLogin()
         }
+        view.onScanQRCode = {
+            [unowned self] in
+            self.showQRReader()
+        }
         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
+    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()
@@ -50,14 +80,17 @@ class WelcomeViewController: UIViewController {
         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(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)
@@ -75,20 +108,137 @@ class WelcomeViewController: UIViewController {
         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
     }
 
-    func setTransitionState(_ transitioning: Bool) {
-        if transitioning {
-            activityIndicator.startAnimating()
+    /// 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 {
-            activityIndicator.stopAnimating()
+            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()
         }
-        activityIndicator.isHidden = !transitioning
-        scrollView.isHidden = transitioning
+    }
+
+    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?
@@ -140,7 +290,7 @@ class WelcomeContentView: UIView {
 
     private lazy var loginButton: UIButton = {
         let button = UIButton(type: .roundedRect)
-        let title = "log in to your server".uppercased()
+        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)
@@ -154,13 +304,12 @@ class WelcomeContentView: UIView {
     }()
 
     private lazy var buttonStack: UIStackView = {
-        let stack = UIStackView(arrangedSubviews: [loginButton /*, qrCodeButton, importBackupButton */])
+        let stack = UIStackView(arrangedSubviews: [loginButton, qrCodeButton /*, importBackupButton */])
         stack.axis = .vertical
-        stack.spacing = 10
+        stack.spacing = 15
         return stack
     }()
 
-
     private lazy var qrCodeButton: UIButton = {
         let button = UIButton()
         let title = String.localized("qrscan_title")
@@ -179,6 +328,18 @@ class WelcomeContentView: UIView {
         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() {
@@ -203,7 +364,7 @@ class WelcomeContentView: UIView {
 
         containerMinHeightConstraint.isActive = true
 
-        _ = [logoView, titleLabel, subtitleLabel, loginButton /*, qrCodeButton, importBackupButton */].map {
+        _ = [logoView, titleLabel, subtitleLabel].map {
             addSubview($0)
             $0.translatesAutoresizingMaskIntoConstraints = false
         }
@@ -253,6 +414,11 @@ class WelcomeContentView: UIView {
             $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 {
@@ -275,4 +441,14 @@ class WelcomeContentView: UIView {
      @objc private func importBackupButtonPressed(_ sender: UIButton) {
          onImportBackup?()
      }
+
+    func showSpinner(_ show: Bool) {
+        if show {
+            activityIndicator.startAnimating()
+        } else {
+            activityIndicator.stopAnimating()
+        }
+        activityIndicator.isHidden = !show
+        buttonStack.isHidden = show
+    }
 }

+ 17 - 14
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -24,7 +24,7 @@ class AppCoordinator: NSObject, Coordinator {
     }()
 
     private lazy var welcomeController: WelcomeViewController = {
-        let welcomeController = WelcomeViewController()
+        let welcomeController = WelcomeViewController(dcContext: dcContext)
         welcomeController.coordinator = self
         return welcomeController
     }()
@@ -132,10 +132,10 @@ class AppCoordinator: NSObject, Coordinator {
 
     func presentWelcomeController(animated: Bool) {
         if animated {
-            welcomeController.setTransitionState(true)
+            welcomeController.activateSpinner(true)
             showWelcomeController()
             DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-                self.welcomeController.setTransitionState(false)
+                self.welcomeController.activateSpinner(false)
             }
         } else {
             showWelcomeController()
@@ -160,6 +160,7 @@ 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 {
@@ -173,21 +174,23 @@ extension AppCoordinator: WelcomeCoordinator {
         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) {
+    func handleLoginSuccess() {
+        welcomeController.activateSpinner(true) // this will hide welcomeController's content
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+            self.loginController.dismiss(animated: true) { // this is ignored if loginController is not shown
                 self.presentTabBarController()
-                self.welcomeController.setTransitionState(false)
+                self.welcomeController.activateSpinner(false)
             }
         }
     }
 
+    func handleQRAccountCreationSuccess() {
+        self.presentTabBarController()
+        self.welcomeController.activateSpinner(false)
+    }
+
     @objc private func cancelButtonPressed(_ sender: UIBarButtonItem) {
         loginController.dismiss(animated: true, completion: nil)
     }
@@ -857,7 +860,6 @@ class EditContactCoordinator: Coordinator, EditContactCoordinatorProtocol {
     }
 }
 
-
 /*
  boilerplate - I tend to remove that interface (cyberta)
  */
@@ -876,5 +878,6 @@ protocol EditContactCoordinatorProtocol: class {
 
 protocol WelcomeCoordinator: class {
     func showLogin()
-    func showQR()
+    func handleLoginSuccess()
+    func handleQRAccountCreationSuccess()
 }

+ 9 - 0
deltachat-ios/DC/Wrapper.swift

@@ -139,6 +139,15 @@ class DcContext {
         return DcLot(dc_check_qr(contextPointer, qrCode))
     }
 
+    func configureAccountFromQR(qrCode: String) -> Bool {
+        let state = checkQR(qrCode: qrCode).state
+        if state != DC_QR_ACCOUNT {
+            return false
+        }
+        let success = dc_set_config_from_qr(contextPointer, qrCode)
+        return success == 1
+    }
+
     func stopOngoingProcess() {
         dc_stop_ongoing_process(contextPointer)
     }

+ 66 - 0
deltachat-ios/Handler/ProgressAlertHandler.swift

@@ -0,0 +1,66 @@
+import UIKit
+
+protocol ProgressAlertHandler: UIViewController {
+    var progressAlert: UIAlertController { get }
+    var configureProgressObserver: Any? { get set }
+    var onProgressSuccess: VoidFunction? { get set }
+    func showProgressAlert(title: String)
+    func updateProgressAlertValue(value: Int?)
+    func updateProgressAlert(error: String?)
+    func updateProgressAlertSuccess(completion: VoidFunction?)
+    func addProgressAlertListener(onSuccess: @escaping VoidFunction)
+}
+
+extension ProgressAlertHandler {
+
+    func showProgressAlert(title: String) {
+        progressAlert.actions[0].isEnabled = true
+        progressAlert.title = title
+        progressAlert.message = String.localized("one_moment")
+        present(progressAlert, animated: true, completion: nil)
+    }
+
+    func updateProgressAlertValue(value: Int?) {
+        if let value = value {
+            progressAlert.message = String.localized("one_moment") + " " + String(value/10) + "%"
+        }
+    }
+
+    func updateProgressAlert(error message: String?) {
+        DispatchQueue.main.async(execute: {
+            self.progressAlert.dismiss(animated: false)
+            let errorAlert = UIAlertController(title: String.localized("error"), message: message, preferredStyle: .alert)
+            errorAlert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
+            self.present(errorAlert, animated: true, completion: nil)
+        })
+    }
+
+    func updateProgressAlertSuccess(completion onComplete: VoidFunction?) {
+        updateProgressAlertValue(value: 1000)
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
+            self.progressAlert.dismiss(animated: true) {
+                onComplete?()
+            }
+        })
+    }
+
+    func addProgressAlertListener(onSuccess: @escaping VoidFunction) {
+        let nc = NotificationCenter.default
+        configureProgressObserver = nc.addObserver(
+            forName: dcNotificationConfigureProgress,
+            object: nil,
+            queue: nil
+        ) { notification in
+            if let ui = notification.userInfo {
+                if ui["error"] as! Bool {
+                    self.updateProgressAlert(error: ui["errorMessage"] as? String)
+                } else if ui["done"] as! Bool {
+                    self.updateProgressAlertSuccess(completion: onSuccess)
+                } else {
+                    self.updateProgressAlertValue(value: ui["progress"] as? Int)
+                }
+            }
+        }
+    }
+}
+

+ 1 - 0
tools/untranslated.xml

@@ -5,4 +5,5 @@
     <string name="import_contacts">Import device contacts</string>
     <string name="import_contacts_message">To chat with contacts from your device open Settings and enable Contacts.</string>
     <string name="stop_sharing_location">Stop sharing location</string>
+    <string  name="qraccount_config_failed">Account creation failed.</string>
 </resources>