Browse Source

Merge pull request #164 from deltachat/qr_code_scanner

Qr code scanner
björn petersen 6 năm trước cách đây
mục cha
commit
820e36ac5f

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

@@ -7,9 +7,12 @@
 	objects = {
 
 /* Begin PBXBuildFile section */
+		30149D9322F21129003C12B5 /* NewProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30149D9222F21129003C12B5 /* NewProfileViewController.swift */; };
 		3022E6BE22E8768800763272 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3022E6C022E8768800763272 /* InfoPlist.strings */; };
 		3060119C22DDE24000C1CE6F /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 3060119E22DDE24000C1CE6F /* Localizable.strings */; };
 		306011B622E5E7FB00C1CE6F /* Localizable.stringsdict in Resources */ = {isa = PBXBuildFile; fileRef = 306011B422E5E7FB00C1CE6F /* Localizable.stringsdict */; };
+		30BD261422F8679200F399DF /* ProfileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BD261322F8679200F399DF /* ProfileView.swift */; };
+		30BD261622F8812700F399DF /* UIView+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30BD261522F8812700F399DF /* UIView+Extension.swift */; };
 		6795F63A82E94FF7CD2CEC0F /* Pods_deltachat_iosTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F7009234DB9408201A6CDCB /* Pods_deltachat_iosTests.framework */; };
 		7070FB9B2101ECBB000DC258 /* GroupNameController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7070FB9A2101ECBB000DC258 /* GroupNameController.swift */; };
 		7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7092474020B3869500AF8799 /* ContactDetailViewController.swift */; };
@@ -83,6 +86,7 @@
 /* Begin PBXFileReference section */
 		21EE28844E7A690D73BF5285 /* Pods-deltachat-iosTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-iosTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-iosTests/Pods-deltachat-iosTests.debug.xcconfig"; sourceTree = "<group>"; };
 		2F7009234DB9408201A6CDCB /* Pods_deltachat_iosTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_deltachat_iosTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		30149D9222F21129003C12B5 /* NewProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewProfileViewController.swift; sourceTree = "<group>"; };
 		3022E6BF22E8768800763272 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		3022E6C122E8768C00763272 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
 		3022E6C222E8768E00763272 /* sq */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sq; path = sq.lproj/InfoPlist.strings; sourceTree = "<group>"; };
@@ -143,6 +147,8 @@
 		306011C722E5E82E00C1CE6F /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = lt; path = lt.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
 		306011C822E5E83100C1CE6F /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
 		306011C922E5E83500C1CE6F /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = uk; path = uk.lproj/Localizable.stringsdict; sourceTree = "<group>"; };
+		30BD261322F8679200F399DF /* ProfileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileView.swift; sourceTree = "<group>"; usesTabs = 0; };
+		30BD261522F8812700F399DF /* UIView+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Extension.swift"; sourceTree = "<group>"; usesTabs = 0; };
 		6241BE1534A653E79AD5D01D /* Pods_deltachat_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_deltachat_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		7070FB9A2101ECBB000DC258 /* GroupNameController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupNameController.swift; sourceTree = "<group>"; };
 		7092474020B3869500AF8799 /* ContactDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewController.swift; sourceTree = "<group>"; };
@@ -251,7 +257,7 @@
 			indentWidth = 4;
 			sourceTree = "<group>";
 			tabWidth = 4;
-			usesTabs = 1;
+			usesTabs = 0;
 		};
 		7A9FB1411FB061E2001FEA36 /* Products */ = {
 			isa = PBXGroup;
@@ -355,6 +361,7 @@
 				AE0D26FC1FB1FE88002FAFCE /* ChatListController.swift */,
 				78E45E3F21D3D70700D4B15E /* ContactListController.swift */,
 				78ED838E21D5927A00243125 /* ProfileViewController.swift */,
+				30149D9222F21129003C12B5 /* NewProfileViewController.swift */,
 				78E45E3921D3CFBC00D4B15E /* SettingsController.swift */,
 				7070FB9A2101ECBB000DC258 /* GroupNameController.swift */,
 				AE851ACF227DF50900ED86F0 /* GroupChatDetailViewController.swift */,
@@ -385,6 +392,7 @@
 				AEE56D7F225504DB007DC082 /* Extensions.swift */,
 				AE38B3192267328200EC37A1 /* Colors.swift */,
 				AE851AC4227C755A00ED86F0 /* Protocols.swift */,
+				30BD261522F8812700F399DF /* UIView+Extension.swift */,
 			);
 			path = Helper;
 			sourceTree = "<group>";
@@ -404,6 +412,7 @@
 				AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */,
 				AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */,
 				AE9DAF0E22C278C6004C9591 /* ChatTitleView.swift */,
+				30BD261322F8679200F399DF /* ProfileView.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -713,6 +722,7 @@
 				78E45E4421D3F14A00D4B15E /* UIImage+Extension.swift in Sources */,
 				AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */,
 				70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */,
+				30BD261422F8679200F399DF /* ProfileView.swift in Sources */,
 				78E45E3E21D3D28C00D4B15E /* DCNavigationController.swift in Sources */,
 				78E45E4021D3D70700D4B15E /* ContactListController.swift in Sources */,
 				AE18F294228C602A0007B1BE /* SecuritySettingsController.swift in Sources */,
@@ -726,6 +736,7 @@
 				78ED838321D5379000243125 /* TextFieldCell.swift in Sources */,
 				78E45E3C21D3D03700D4B15E /* TextFieldTableViewCell.swift in Sources */,
 				AE0D26FD1FB1FE88002FAFCE /* ChatListController.swift in Sources */,
+				30149D9322F21129003C12B5 /* NewProfileViewController.swift in Sources */,
 				AEE56D80225504DB007DC082 /* Extensions.swift in Sources */,
 				7A0052C81FBE6CB40048C3BF /* NewContactController.swift in Sources */,
 				AEE56D762253431E007DC082 /* AccountSetupController.swift in Sources */,
@@ -744,6 +755,7 @@
 				AE851ACE227CA54400ED86F0 /* InitialsBadge.swift in Sources */,
 				70B8882E2091B8550074812E /* ContactCell.swift in Sources */,
 				7A451D941FB1B1DB00177250 /* wrapper.c in Sources */,
+				30BD261622F8812700F399DF /* UIView+Extension.swift in Sources */,
 				7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */,
 				AE18F292228C17BC0007B1BE /* PortSettingsController.swift in Sources */,
 				AE851AD0227DF50900ED86F0 /* GroupChatDetailViewController.swift in Sources */,

+ 3 - 2
deltachat-ios/AppDelegate.swift

@@ -17,6 +17,7 @@ enum ApplicationState {
 
 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
+    private let dcContext = DcContext()
     var appCoordinator: AppCoordinator!
     // static let appCoordinatorDeprecated = AppCoordinatorDeprecated()
     static var progress: Float = 0 // TODO: delete
@@ -67,7 +68,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         //       - second param remains nil (user data for more than one mailbox)
         open()
         let isConfigured = dc_is_configured(mailboxPointer) != 0
-        appCoordinator = AppCoordinator(window: window)
+        appCoordinator = AppCoordinator(window: window, dcContext: dcContext)
         appCoordinator.start()
         UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
         start()
@@ -135,7 +136,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         logger.info("open: \(dbfile())")
 
         if mailboxPointer == nil {
-            mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
+            mailboxPointer = dcContext.contextPointer
             guard mailboxPointer != nil else {
                 fatalError("Error: dc_context_new returned nil")
             }

+ 1 - 1
deltachat-ios/Controller/DCNavigationController.swift

@@ -12,7 +12,7 @@ final class DCNavigationController: UINavigationController {
         } else {
             navigationBar.setBackgroundImage(UIImage(), for: .default)
         }
-
+        navigationBar.backgroundColor = .white;
         setShadow(nil)
     }
 

+ 308 - 0
deltachat-ios/Controller/NewProfileViewController.swift

@@ -0,0 +1,308 @@
+import Foundation
+import UIKit
+
+class NewProfileViewController: UIViewController, QrCodeReaderDelegate {
+
+    weak var coordinator: ProfileCoordinator?
+    let qrCodeReaderController = QrCodeReaderController()
+    var secureJoinObserver: Any?
+    var dcContext: DcContext
+
+    var contactCell: UIView?
+    var infoLabel: UIView?
+    var qrCode: UIView?
+    var qrCodeScanner: UIView?
+
+    var contactCellConstraints: [NSLayoutConstraint] = []
+    var infoLabelConstraints: [NSLayoutConstraint] = []
+    var qrCodeConstraints: [NSLayoutConstraint] = []
+    var qrCodeScannerConstraints: [NSLayoutConstraint] = []
+
+    init(dcContext: DcContext) {
+        self.dcContext = dcContext
+        super.init(nibName: nil, bundle: nil)
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private lazy var progressAlert: UIAlertController = {
+        let alert = UIAlertController(title: String.localized("one_moment"), message: "TESTMESSAGE", preferredStyle: .alert)
+
+        let rect = CGRect(x: 0, y: 0, width: 25, height: 25)
+        let activityIndicator = UIActivityIndicatorView(frame: rect)
+        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+        activityIndicator.style = .gray
+
+        alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: { _ in
+            self.dcContext.stopOngoingProcess()
+            self.dismiss(animated: true, completion: nil)
+        }))
+        return alert
+    }()
+
+    private func showProgressAlert() {
+        self.present(self.progressAlert, animated: true, completion: {
+            let rect = CGRect(x: 10, y: 10, width: 20, height: 20)
+            let progressView = UIActivityIndicatorView(frame: rect)
+            progressView.tintColor = .blue
+            progressView.startAnimating()
+            self.progressAlert.view.addSubview(progressView)
+        })
+    }
+
+    private func showErrorAlert(error: String) {
+        let alert = UIAlertController(title: String.localized("error"), message: error, preferredStyle: .alert)
+        alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
+            alert.dismiss(animated: true, completion: nil)
+        }))
+    }
+
+    var contact: DCContact? {
+        // This is nil if we do not have an account setup yet
+        if !DCConfig.configured {
+            return nil
+        }
+        return DCContact(id: Int(DC_CONTACT_ID_SELF))
+    }
+
+    var fingerprint: String? {
+        if !DCConfig.configured {
+            return nil
+        }
+        return dcContext.getSecurejoinQr(chatId: 0)
+    }
+
+    override func loadView() {
+        let view = UIView()
+        view.backgroundColor = UIColor.white
+        self.view = view
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = String.localized("my_profile")
+        qrCodeReaderController.delegate = self
+        self.edgesForExtendedLayout = []
+
+        initViews()
+
+        if UIDevice.current.orientation.isLandscape {
+            setupLandscapeConstraints()
+        } else {
+            setupPortraitConstraints()
+        }
+
+    }
+
+    private func initViews() {
+        contactCell = createContactCell()
+        infoLabel = createInfoLabel()
+        qrCode = createQRCodeView()
+        qrCodeScanner = createQRCodeScannerButton()
+        self.view.addSubview(contactCell!)
+        self.view.addSubview(qrCode!)
+        self.view.addSubview(infoLabel!)
+        self.view.addSubview(qrCodeScanner!)
+    }
+
+    private func applyConstraints() {
+        self.view.addConstraints(contactCellConstraints)
+        self.view.addConstraints(qrCodeConstraints)
+        self.view.addConstraints(infoLabelConstraints)
+        self.view.addConstraints(qrCodeScannerConstraints)
+    }
+
+    private func removeConstraints() {
+        self.view.removeConstraints(contactCellConstraints)
+        self.view.removeConstraints(qrCodeConstraints)
+        self.view.removeConstraints(infoLabelConstraints)
+        self.view.removeConstraints(qrCodeScannerConstraints)
+    }
+
+    func setupPortraitConstraints() {
+        removeConstraints()
+        contactCellConstraints = [contactCell!.constraintAlignTopTo(self.view),
+                                  contactCell!.constraintAlignLeadingTo(self.view),
+                                  contactCell!.constraintAlignTrailingTo(self.view)]
+        qrCodeScannerConstraints = [qrCodeScanner!.constraintAlignBottomTo(self.view, paddingBottom: 25),
+                                    qrCodeScanner!.constraintCenterXTo(self.view)]
+        qrCodeConstraints = [qrCode!.constraintCenterYTo(self.view),
+                             qrCode!.constraintCenterYTo(self.view, paddingY: -25),
+                             qrCode!.constraintCenterXTo(self.view)]
+        infoLabelConstraints = [infoLabel!.constraintToBottomOf(qrCode!, paddingTop: 25),
+                                infoLabel!.constraintAlignLeadingTo(self.view, paddingLeading: 8),
+                                infoLabel!.constraintAlignTrailingTo(self.view, paddingTrailing: 8)]
+        applyConstraints()
+    }
+
+    func setupLandscapeConstraints() {
+        removeConstraints()
+        contactCellConstraints = [contactCell!.constraintAlignTopTo(self.view),
+                                  contactCell!.constraintAlignLeadingTo(self.view),
+                                  contactCell!.constraintAlignTrailingTo(self.view)]
+        qrCodeScannerConstraints = [qrCodeScanner!.constraintToTrailingOf(qrCode!, paddingLeading: 50),
+                                    qrCodeScanner!.constraintAlignTrailingTo(self.view, paddingTrailing: 50),
+                                    qrCodeScanner!.constraintAlignBottomTo(qrCode!)]
+        qrCodeConstraints = [qrCode!.constraintToBottomOf(contactCell!, paddingTop: 25),
+                             qrCode!.constraintAlignLeadingTo(self.view, paddingLeading: 50)]
+        infoLabelConstraints = [infoLabel!.constraintToBottomOf(contactCell!, paddingTop: 25),
+                                infoLabel!.constraintToTrailingOf(qrCode!, paddingLeading: 50),
+                                infoLabel!.constraintAlignTrailingTo(self.view, paddingTrailing: 50)]
+        applyConstraints()
+    }
+
+    override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
+        super.viewWillTransition(to: size, with: coordinator)
+        if UIDevice.current.orientation.isLandscape {
+            setupLandscapeConstraints()
+        } else {
+            setupPortraitConstraints()
+        }
+    }
+
+    private func addSecureJoinProgressListener() {
+        let nc = NotificationCenter.default
+        secureJoinObserver = nc.addObserver(
+            forName: dcNotificationSecureJoinerProgress,
+            object: nil,
+            queue: nil
+        ) { notification in
+            print("secure join: ", notification)
+            if let ui = notification.userInfo {
+                if ui["progress"] as! Int == 400 {
+                    let contactId = ui["contact_id"] as! Int
+                    self.progressAlert.message = String.localizedStringWithFormat(
+                        String.localized("qrscan_x_verified_introduce_myself"),
+                        DCContact(id: contactId).nameNAddr)
+                }
+            }
+        }
+    }
+
+    private func removeSecureJoinProgressListener() {
+        let nc = NotificationCenter.default
+        if let secureJoinObserver = self.secureJoinObserver {
+            nc.removeObserver(secureJoinObserver)
+        }
+    }
+
+    //QRCodeDelegate
+    func handleQrCode(_ code: String) {
+        //remove qr code scanner view
+        if let ctrl = navigationController {
+            ctrl.viewControllers.removeLast()
+        }
+
+        let qrParsed: DcLot = self.dcContext.checkQR(qrCode: code)
+        let nameAndAddress = DCContact(id: qrParsed.id).nameNAddr;
+        let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("qrscan_ask_fingerprint_ask_oob"), nameAndAddress),
+                                      message: nil,
+                                      preferredStyle: .alert)
+        alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
+        alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: { _ in
+            alert.dismiss(animated: true, completion: nil)
+            self.showProgressAlert()
+            // execute blocking secure join in background
+            DispatchQueue.global(qos: .background).async {
+                self.addSecureJoinProgressListener()
+                AppDelegate.lastErrorDuringConfig = nil
+                let chatId = self.dcContext.joinSecurejoin(qrCode: code)
+                let errorString = AppDelegate.lastErrorDuringConfig
+                self.removeSecureJoinProgressListener()
+
+                DispatchQueue.main.async {
+                    self.progressAlert.dismiss(animated: true, completion: nil)
+                    if chatId != 0 {
+                        self.coordinator?.showChat(chatId: chatId)
+                    } else if errorString != nil {
+                       self.showErrorAlert(error: errorString!)
+                    }
+                }
+            }
+        }))
+        present(alert, animated: true, completion: nil)
+    }
+
+    private func createInfoLabel() -> UIView {
+        let label = UILabel.init()
+        label.translatesAutoresizingMaskIntoConstraints = false
+        if let contact = contact {
+            label.text = String.localizedStringWithFormat(String.localized("qrshow_join_contact_hint"), contact.email)
+        }
+        label.lineBreakMode = .byWordWrapping
+        label.numberOfLines = 0
+        label.textAlignment = .center
+        label.font = UIFont.systemFont(ofSize: 14)
+        return label
+    }
+    private func createQRCodeScannerButton() -> UIView {
+        let btn = UIButton.init(type: UIButton.ButtonType.system)
+        btn.translatesAutoresizingMaskIntoConstraints = false
+        btn.setTitle(String.localized("qrscan_title"), for: .normal)
+        btn.addTarget(self, action:#selector(self.openQRCodeScanner), for: .touchUpInside)
+        return btn
+    }
+
+    @objc func openQRCodeScanner() {
+        if let ctrl = navigationController {
+            ctrl.pushViewController(qrCodeReaderController, animated: true)
+        }
+    }
+
+    private func createQRCodeView() -> UIView {
+        if let fingerprint = self.fingerprint {
+            let width: CGFloat = 130
+
+            let frame = CGRect(origin: .zero, size: .init(width: width, height: width))
+            let imageView = QRCodeView(frame: frame)
+            imageView.generateCode(
+                fingerprint,
+                foregroundColor: .darkText,
+                backgroundColor: .white
+            )
+            imageView.translatesAutoresizingMaskIntoConstraints = false
+            imageView.widthAnchor.constraint(equalToConstant: width).isActive = true
+            imageView.heightAnchor.constraint(equalToConstant: width).isActive = true
+            imageView.translatesAutoresizingMaskIntoConstraints = false
+            return imageView
+        }
+        return UIImageView()
+    }
+
+    private func createContactCell() -> UIView {
+        let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
+
+        let profileView = ProfileView(frame: CGRect())
+        if let contact = self.contact {
+            let name = DCConfig.displayname ?? contact.name
+            profileView.setBackgroundColor(bg)
+            profileView.nameLabel.text = name
+            profileView.emailLabel.text = contact.email
+            profileView.darkMode = false
+            if let img = contact.profileImage {
+                profileView.setImage(img)
+            } else {
+                profileView.setBackupImage(name: name, color: contact.color)
+            }
+        } else {
+            profileView.nameLabel.text = String.localized("no_account_setup")
+        }
+
+        return profileView
+    }
+
+    override func viewWillAppear(_: Bool) {
+        navigationController?.navigationBar.prefersLargeTitles = true
+    }
+
+    func displayNewChat(contactId: Int) {
+        let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
+        let chatVC = ChatViewController(chatId: Int(chatId))
+
+        chatVC.hidesBottomBarWhenPushed = true
+        navigationController?.pushViewController(chatVC, animated: true)
+    }
+}
+

+ 34 - 34
deltachat-ios/Controller/QrCodeReaderController.swift

@@ -5,27 +5,25 @@ class QrCodeReaderController: UIViewController {
     var captureSession = AVCaptureSession()
 
     var videoPreviewLayer: AVCaptureVideoPreviewLayer?
-    var qrCodeFrameView: UIView?
 
     weak var delegate: QrCodeReaderDelegate?
 
     private let supportedCodeTypes = [
-        AVMetadataObject.ObjectType.qr,
+        AVMetadataObject.ObjectType.qr
     ]
 
     override func viewDidLoad() {
         super.viewDidLoad()
+        self.edgesForExtendedLayout = []
+        title = String.localized("qrscan_title")
 
-        let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(
-            deviceTypes: [.builtInDualCamera],
-            mediaType: AVMediaType.video,
-            position: .back
-        )
-
-        guard let captureDevice = deviceDiscoverySession.devices.first else {
-            print("Failed to get the camera device")
-            return
-        }
+        guard let captureDevice = AVCaptureDevice.DiscoverySession.init(
+			deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera],
+			mediaType: .video,
+			position: .back).devices.first else {
+				print("Failed to get the camera device")
+				return
+		}
 
         do {
             let input = try AVCaptureDeviceInput(device: captureDevice)
@@ -47,18 +45,32 @@ class QrCodeReaderController: UIViewController {
         videoPreviewLayer?.frame = view.layer.bounds
         view.layer.addSublayer(videoPreviewLayer!)
 
-        captureSession.startRunning()
-
-        qrCodeFrameView = UIView()
+        let infoLabel = createInfoLabel()
+        view.addSubview(infoLabel)
+        view.addConstraint(infoLabel.constraintAlignBottomTo(view, paddingBottom: 8))
+        view.addConstraint(infoLabel.constraintCenterXTo(view))
+        view.bringSubviewToFront(infoLabel)
+    }
 
-        if let qrCodeFrameView = qrCodeFrameView {
-            qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
-            qrCodeFrameView.layer.borderWidth = 2
-            view.addSubview(qrCodeFrameView)
-            view.bringSubviewToFront(qrCodeFrameView)
-        }
+    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 viewWillDisappear(_ animated: Bool) {
+		captureSession.stopRunning()
+	}
+
     override func didReceiveMemoryWarning() {
         super.didReceiveMemoryWarning()
         // Dispose of any resources that can be recreated.
@@ -67,24 +79,12 @@ class QrCodeReaderController: UIViewController {
 
 extension QrCodeReaderController: AVCaptureMetadataOutputObjectsDelegate {
     func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
-        if metadataObjects.isEmpty {
-            qrCodeFrameView?.frame = CGRect.zero
-            return
-        }
 
         let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
 
         if supportedCodeTypes.contains(metadataObj.type) {
-            let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
-            qrCodeFrameView?.frame = barCodeObject!.bounds
-
             if metadataObj.stringValue != nil {
-                DispatchQueue.main.async {
-                    self.captureSession.stopRunning()
-                    self.dismiss(animated: true) {
-                        self.delegate?.handleQrCode(metadataObj.stringValue!)
-                    }
-                }
+				self.delegate?.handleQrCode(metadataObj.stringValue!)
             }
         }
     }

+ 2 - 2
deltachat-ios/Controller/SettingsController.swift

@@ -174,7 +174,7 @@ internal final class SettingsViewController: QuickTableViewController {
         let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
         if !documents.isEmpty {
             logger.info("create backup in \(documents)")
-            hudHandler.showBackupHud(String.localized("creating_backup"))
+            hudHandler.showHud(String.localized("creating_backup"))
             DispatchQueue.main.async {
                 dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
             }
@@ -184,7 +184,7 @@ internal final class SettingsViewController: QuickTableViewController {
     }
 
     private func configure(_: Row) {
-        hudHandler.showBackupHud(String.localized("configuring_account"))
+        hudHandler.showHud(String.localized("configuring_account"))
         dc_configure(mailboxPointer)
     }
 

+ 22 - 6
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -5,6 +5,7 @@ import MobileCoreServices
 
 class AppCoordinator: NSObject, Coordinator {
     private let window: UIWindow
+    private let dcContext: DcContext
 
     var rootViewController: UIViewController {
         return tabBarController
@@ -18,6 +19,7 @@ class AppCoordinator: NSObject, Coordinator {
         // put viewControllers here
         tabBarController.delegate = self
         tabBarController.tabBar.tintColor = DCColors.primary
+		tabBarController.tabBar.backgroundColor = .white
         return tabBarController
     }()
 
@@ -47,11 +49,11 @@ class AppCoordinator: NSObject, Coordinator {
     }()
 
     private lazy var profileController: UIViewController = {
-        let controller = ProfileViewController()
+        let controller = NewProfileViewController(dcContext: dcContext)
         let nav = DCNavigationController(rootViewController: controller)
         let settingsImage = UIImage(named: "report_card")
         nav.tabBarItem = UITabBarItem(title: String.localized("my_profile"), image: settingsImage, tag: 2)
-        let coordinator = ProfileCoordinator(rootViewController: nav)
+        let coordinator = ProfileCoordinator(navigationController: nav)
         self.childCoordinators.append(coordinator)
         controller.coordinator = coordinator
         return nav
@@ -79,8 +81,9 @@ class AppCoordinator: NSObject, Coordinator {
         return nav
     }()
 
-    init(window: UIWindow) {
+    init(window: UIWindow, dcContext: DcContext) {
         self.window = window
+        self.dcContext = dcContext
         super.init()
         window.rootViewController = rootViewController
         window.makeKeyAndVisible()
@@ -95,6 +98,15 @@ class AppCoordinator: NSObject, Coordinator {
         tabBarController.selectedIndex = index
     }
 
+    func showChat(chatId: Int) {
+        showTab(index: 3)
+        let navController = self.chatListController as! UINavigationController
+        let chatVC = ChatViewController(chatId: chatId)
+        let coordinator = ChatViewCoordinator(navigationController: navController, chatId: chatId)
+        chatVC.coordinator = coordinator
+        navController.pushViewController(chatVC, animated: true)
+    }
+
     func presentLoginController() {
         let accountSetupController = AccountSetupController()
         let accountSetupNav = DCNavigationController(rootViewController: accountSetupController)
@@ -175,10 +187,14 @@ class MailboxCoordinator: ChatViewCoordinator {
 }
 
 class ProfileCoordinator: Coordinator {
-    var rootViewController: UIViewController
+	var navigationController: UINavigationController
+	init(navigationController: UINavigationController) {
+		self.navigationController = navigationController
+    }
 
-    init(rootViewController: UIViewController) {
-        self.rootViewController = rootViewController
+    func showChat(chatId: Int) {
+        let appDelegate = UIApplication.shared.delegate as! AppDelegate
+        appDelegate.appCoordinator.showChat(chatId: chatId)
     }
 }
 

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

@@ -28,6 +28,10 @@ enum MessageViewType: CustomStringConvertible {
 class DCContact {
     private var contactPointer: OpaquePointer
 
+    var nameNAddr: String {
+        return String(cString: dc_contact_get_name_n_addr(contactPointer))
+    }
+
     var name: String {
         return String(cString: dc_contact_get_name(contactPointer))
     }
@@ -93,6 +97,77 @@ class DCContact {
     }
 }
 
+class DcContext {
+	let contextPointer: OpaquePointer
+
+	init() {
+		contextPointer = dc_context_new(callback_ios, nil, "iOS")
+	}
+
+	deinit {
+		dc_context_unref(contextPointer)
+	}
+
+
+	func getSecurejoinQr (chatId: Int) -> String? {
+		if let cString = dc_get_securejoin_qr(self.contextPointer,  UInt32(chatId)) {
+			return String(cString: cString)
+		}
+		return nil
+	}
+
+	func joinSecurejoin (qrCode: String) -> Int {
+		return Int(dc_join_securejoin(contextPointer, qrCode))
+	}
+
+	func checkQR(qrCode: String) -> DcLot {
+		return DcLot(dc_check_qr(contextPointer, qrCode))
+	}
+
+    func stopOngoingProcess() {
+        dc_stop_ongoing_process(contextPointer)
+    }
+
+}
+
+class DcLot {
+	private var dcLotPointer: OpaquePointer
+
+	init(_ dcLotPointer: OpaquePointer) {
+		self.dcLotPointer = dcLotPointer
+	}
+
+	deinit {
+		dc_lot_unref(dcLotPointer)
+	}
+
+	var text1: String {
+		guard let result = dc_lot_get_text1(dcLotPointer) else { return "" }
+		return String(cString: result)
+	}
+
+	var text1Meaning: Int {
+		return Int(dc_lot_get_text1_meaning(dcLotPointer))
+	}
+
+	var getText2: String {
+		guard let result = dc_lot_get_text2(dcLotPointer) else { return "" }
+		return String(cString: result)
+	}
+
+	var timestamp: Int64 {
+		return Int64(dc_lot_get_timestamp(dcLotPointer))
+	}
+
+	var state: Int {
+		return Int(dc_lot_get_state(dcLotPointer))
+	}
+
+	var id: Int {
+		return Int(dc_lot_get_id(dcLotPointer))
+	}
+}
+
 class DCMessage: MessageType {
     private var messagePointer: OpaquePointer
 

+ 1 - 0
deltachat-ios/DC/events.swift

@@ -165,6 +165,7 @@ public func callbackSwift(event: CInt, data1: CUnsignedLong, data2: CUnsignedLon
                 name: dcNotificationSecureJoinerProgress,
                 object: nil,
                 userInfo: [
+                    "contact_id": Int(data1),
                     "progress": Int(data2),
                     "error": Int(data2) == 0,
                     "done": Int(data2) == 1000,

+ 1 - 1
deltachat-ios/Handler/HudHandler.swift

@@ -16,7 +16,7 @@ class HudHandler {
         }
     }
 
-    func showBackupHud(_ text: String) {
+    func showHud(_ text: String) {
         DispatchQueue.main.async {
             let hud = JGProgressHUD(style: .dark)
             hud.vibrancyEnabled = true

+ 0 - 32
deltachat-ios/Helper/Extensions.swift

@@ -198,38 +198,6 @@ extension UIImage {
     }
 }
 
-extension UIView {
-    func makeBorder(color: UIColor = UIColor.red) {
-        self.layer.borderColor = color.cgColor
-        self.layer.borderWidth = 2
-    }
-}
-
-extension UIImage {
-    func resizeImage(targetSize: CGSize) -> UIImage {
-        let size = self.size
-
-        let widthRatio  = targetSize.width  / size.width
-        let heightRatio = targetSize.height / size.height
-
-        var newSize: CGSize
-        if(widthRatio > heightRatio) {
-            newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
-        } else {
-            newSize = CGSize(width: size.width * widthRatio, height: size.height *      widthRatio)
-        }
-
-        let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
-
-        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
-        draw(in: rect)
-        let newImage = UIGraphicsGetImageFromCurrentImageContext()
-        UIGraphicsEndImageContext()
-
-        return newImage!
-    }
-}
-
 
 extension UIColor {
     convenience init(alpha: Int, red: Int, green: Int, blue: Int) {

+ 23 - 0
deltachat-ios/Helper/UIImage+Extension.swift

@@ -23,4 +23,27 @@ extension UIImage {
         guard let cgImage = image?.cgImage else { return nil }
         self.init(cgImage: cgImage)
     }
+
+	func resizeImage(targetSize: CGSize) -> UIImage {
+		let size = self.size
+
+		let widthRatio  = targetSize.width  / size.width
+		let heightRatio = targetSize.height / size.height
+
+		var newSize: CGSize
+		if(widthRatio > heightRatio) {
+			newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
+		} else {
+			newSize = CGSize(width: size.width * widthRatio, height: size.height *      widthRatio)
+		}
+
+		let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
+
+		UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
+		draw(in: rect)
+		let newImage = UIGraphicsGetImageFromCurrentImageContext()
+		UIGraphicsEndImageContext()
+
+		return newImage!
+	}
 }

+ 101 - 0
deltachat-ios/Helper/UIView+Extension.swift

@@ -0,0 +1,101 @@
+import UIKit
+
+extension UIView {
+    func makeBorder(color: UIColor = UIColor.red) {
+        self.layer.borderColor = color.cgColor
+        self.layer.borderWidth = 2
+        print("hello")
+    }
+
+    func constraintAlignTopTo(_ view: UIView) -> NSLayoutConstraint {
+        return constraintAlignTopTo(view, paddingTop: 0.0)
+    }
+
+    func constraintAlignTopTo(_ view: UIView, paddingTop: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .top,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .top,
+            multiplier: 1.0,
+            constant: paddingTop)
+    }
+
+    func constraintAlignBottomTo(_ view: UIView, paddingBottom: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .bottom,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .bottom,
+            multiplier: 1.0,
+            constant: -paddingBottom)
+    }
+
+    func constraintAlignLeadingTo(_ view: UIView, paddingLeading: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .leading,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .leading,
+            multiplier: 1.0,
+            constant: paddingLeading)
+    }
+
+    func constraintAlignTrailingTo(_ view: UIView, paddingTrailing: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .trailing,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .trailing,
+            multiplier: 1.0,
+            constant: -paddingTrailing)
+    }
+
+    func constraintToBottomOf(_ view: UIView, paddingTop: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .top,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .bottom,
+            multiplier: 1.0,
+            constant: paddingTop)
+    }
+
+    func constraintToTrailingOf(_ view: UIView, paddingLeading: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(
+            item: self,
+            attribute: .leading,
+            relatedBy: .equal,
+            toItem: view,
+            attribute: .trailing,
+            multiplier: 1.0,
+            constant: paddingLeading)
+    }
+
+
+    func constraintCenterXTo(_ view: UIView, paddingX: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(item: self,
+                                  attribute: .centerX,
+                                  relatedBy: .equal,
+                                  toItem: view,
+                                  attribute: .centerX,
+                                  multiplier: 1.0,
+                                  constant: paddingX)
+    }
+
+    func constraintCenterYTo(_ view: UIView, paddingY: CGFloat = 0.0) -> NSLayoutConstraint {
+        return NSLayoutConstraint(item: self,
+                                  attribute: .centerY,
+                                  relatedBy: .equal,
+                                  toItem: view,
+                                  attribute: .centerY,
+                                  multiplier: 1.0,
+                                  constant: paddingY)
+    }
+
+}

+ 156 - 0
deltachat-ios/View/ProfileView.swift

@@ -0,0 +1,156 @@
+import UIKit
+
+class ProfileView: UIView {
+
+    private let initialsLabelSize: CGFloat = 54
+    private let imgSize: CGFloat = 25
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        translatesAutoresizingMaskIntoConstraints = false
+        setupSubviews()
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    let avatar: UIView = {
+        let avatar = UIView()
+        return avatar
+    }()
+
+    lazy var imgView: UIImageView = {
+        let imgView = UIImageView()
+        let img = UIImage(named: "approval")!.withRenderingMode(.alwaysTemplate)
+        imgView.isHidden = true
+        imgView.image = img
+        imgView.bounds = CGRect(
+            x: 0,
+            y: 0,
+            width: imgSize, height: imgSize
+        )
+        return imgView
+    }()
+
+    lazy var initialsLabel: UILabel = {
+        let initialsLabel = UILabel()
+        initialsLabel.textAlignment = NSTextAlignment.center
+        initialsLabel.textColor = UIColor.white
+        initialsLabel.font = UIFont.systemFont(ofSize: 22)
+        initialsLabel.backgroundColor = UIColor.green
+        let initialsLabelCornerRadius = (initialsLabelSize - 6) / 2
+        initialsLabel.layer.cornerRadius = initialsLabelCornerRadius
+        initialsLabel.clipsToBounds = true
+        return initialsLabel
+    }()
+
+    let nameLabel: UILabel = {
+        let label = UILabel()
+        label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
+        label.lineBreakMode = .byTruncatingTail
+        label.textColor = UIColor(hexString: "2f3944")
+        // label.makeBorder()
+        return label
+
+    }()
+
+    let emailLabel: UILabel = {
+        let label = UILabel()
+        label.font = UIFont.systemFont(ofSize: 14)
+        label.textColor = UIColor(hexString: "848ba7")
+        label.lineBreakMode = .byTruncatingTail
+        return label
+    }()
+
+    var darkMode: Bool = false {
+        didSet {
+            if darkMode {
+                self.backgroundColor = UIColor.darkGray
+                nameLabel.textColor = UIColor.white
+                emailLabel.textColor = UIColor.white
+            }
+        }
+    }
+
+    private func setupSubviews() {
+        let margin: CGFloat = 10
+
+        initialsLabel.translatesAutoresizingMaskIntoConstraints = false
+        avatar.translatesAutoresizingMaskIntoConstraints = false
+        initialsLabel.widthAnchor.constraint(equalToConstant: initialsLabelSize - 6).isActive = true
+        initialsLabel.heightAnchor.constraint(equalToConstant: initialsLabelSize - 6).isActive = true
+
+        avatar.widthAnchor.constraint(equalToConstant: initialsLabelSize).isActive = true
+        avatar.heightAnchor.constraint(equalToConstant: initialsLabelSize).isActive = true
+
+        avatar.addSubview(initialsLabel)
+        self.addSubview(avatar)
+
+        initialsLabel.topAnchor.constraint(equalTo: avatar.topAnchor, constant: 3).isActive = true
+        initialsLabel.leadingAnchor.constraint(equalTo: avatar.leadingAnchor, constant: 3).isActive = true
+        initialsLabel.trailingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: -3).isActive = true
+
+        avatar.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: margin).isActive = true
+        avatar.center.y = self.center.y
+        avatar.center.x += initialsLabelSize / 2
+        avatar.topAnchor.constraint(equalTo: self.topAnchor, constant: margin).isActive = true
+        avatar.bottomAnchor.constraint(lessThanOrEqualTo: self.bottomAnchor, constant: -margin).isActive = true
+        initialsLabel.center = avatar.center
+
+        let myStackView = UIStackView()
+        myStackView.translatesAutoresizingMaskIntoConstraints = false
+        myStackView.clipsToBounds = true
+
+        let toplineStackView = UIStackView()
+        toplineStackView.axis = .horizontal
+
+        let bottomLineStackView = UIStackView()
+        bottomLineStackView.axis = .horizontal
+
+        toplineStackView.addArrangedSubview(nameLabel)
+        bottomLineStackView.addArrangedSubview(emailLabel)
+
+        self.addSubview(myStackView)
+        myStackView.leadingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: margin).isActive = true
+        myStackView.centerYAnchor.constraint(equalTo: avatar.centerYAnchor).isActive = true
+        myStackView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: -margin).isActive = true
+        myStackView.axis = .vertical
+        myStackView.addArrangedSubview(toplineStackView)
+        myStackView.addArrangedSubview(bottomLineStackView)
+
+        imgView.tintColor = DCColors.primary
+
+        avatar.addSubview(imgView)
+
+        imgView.center.x = avatar.center.x + (avatar.frame.width / 2) + imgSize - 5
+        imgView.center.y = avatar.center.y + (avatar.frame.height / 2) + imgSize - 5
+    }
+
+    func setBackgroundColor(_ color: UIColor) {
+        self.backgroundColor = color
+    }
+
+    func setColor(_ color: UIColor) {
+        initialsLabel.backgroundColor = color
+    }
+
+    func setVerified(isVerified: Bool) {
+        imgView.isHidden = !isVerified
+    }
+
+    func setImage(_ img: UIImage) {
+        let attachment = NSTextAttachment()
+        attachment.image = img
+        initialsLabel.attributedText = NSAttributedString(attachment: attachment)
+    }
+
+    func setBackupImage(name: String, color: UIColor) {
+        let text = Utils.getInitials(inputName: name)
+
+        initialsLabel.textAlignment = .center
+        initialsLabel.text = text
+
+        setColor(color)
+    }
+}