Bläddra i källkod

more things

- qr code scanning
- refactor message handling with custom message type
dignifiedquire 6 år sedan
förälder
incheckning
5f34072d40

+ 1 - 1
Podfile

@@ -1,6 +1,7 @@
 target 'deltachat-ios' do
   use_frameworks!
   swift_version = '4.2'
+  
   pod 'ALCameraViewController', :git => 'https://github.com/dignifiedquire/ALCameraViewController'
   pod 'openssl-ios-bitcode', '1.0.210'
   pod 'ReachabilitySwift'
@@ -9,5 +10,4 @@ target 'deltachat-ios' do
   pod 'SwiftyBeaver'
   pod 'DBDebugToolkit'
   pod 'MessageKit', '2.0.0'
-
 end

+ 1 - 1
Podfile.lock

@@ -51,6 +51,6 @@ SPEC CHECKSUMS:
   ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3
   SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a
 
-PODFILE CHECKSUM: 754ae089b2ba2b296811903f136cbcc4b8f52d5a
+PODFILE CHECKSUM: f1e841a85f483feff977aea1a006be4b735b4bf8
 
 COCOAPODS: 1.5.3

+ 1 - 1
Pods/Manifest.lock

@@ -51,6 +51,6 @@ SPEC CHECKSUMS:
   ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3
   SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a
 
-PODFILE CHECKSUM: 754ae089b2ba2b296811903f136cbcc4b8f52d5a
+PODFILE CHECKSUM: f1e841a85f483feff977aea1a006be4b735b4bf8
 
 COCOAPODS: 1.5.3

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

@@ -51,6 +51,8 @@
 		7092474120B3869500AF8799 /* ContactProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7092474020B3869500AF8799 /* ContactProfileViewController.swift */; };
 		70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */; };
 		70B8882E2091B8550074812E /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B8882D2091B8550074812E /* ContactCell.swift */; };
+		789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */; };
+		789E879D21D6DF86003ED1C5 /* ProgressHud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789E879C21D6DF86003ED1C5 /* ProgressHud.swift */; };
 		78E45E2921D176C400D4B15E /* dc_jobthread.h in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E2821D176C300D4B15E /* dc_jobthread.h */; };
 		78E45E2B21D176FB00D4B15E /* dc_jobthread.c in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E2A21D176FB00D4B15E /* dc_jobthread.c */; };
 		78E45E2F21D1774200D4B15E /* dc_context.h in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E2D21D1774200D4B15E /* dc_context.h */; };
@@ -178,6 +180,8 @@
 		7092474020B3869500AF8799 /* ContactProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactProfileViewController.swift; sourceTree = "<group>"; };
 		70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupMemberChoiceController.swift; sourceTree = "<group>"; };
 		70B8882D2091B8550074812E /* ContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; };
+		789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeReaderController.swift; sourceTree = "<group>"; };
+		789E879C21D6DF86003ED1C5 /* ProgressHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressHud.swift; sourceTree = "<group>"; };
 		78C7036A21D46752005D4525 /* deltachat-ios.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "deltachat-ios.entitlements"; sourceTree = "<group>"; };
 		78E45E2121D1768900D4B15E /* src */ = {isa = PBXFileReference; lastKnownFileType = folder; name = src; path = "deltachat-ios/libraries/deltachat-core/src"; sourceTree = "<group>"; };
 		78E45E2821D176C300D4B15E /* dc_jobthread.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = dc_jobthread.h; path = "deltachat-ios/libraries/deltachat-core/src/dc_jobthread.h"; sourceTree = "<group>"; };
@@ -419,8 +423,10 @@
 				7A451D921FB1B1DB00177250 /* wrapper.c */,
 				7A451D931FB1B1DB00177250 /* wrapper.h */,
 				7A451DBD1FB4AD0700177250 /* Wrapper.swift */,
+				789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */,
 				70B8882D2091B8550074812E /* ContactCell.swift */,
 				78ED838C21D577D000243125 /* events.swift */,
+				789E879C21D6DF86003ED1C5 /* ProgressHud.swift */,
 				7032FF8E2149C1DB00B7EC83 /* BaseController.swift */,
 				78ED839021D5929800243125 /* TopViews */,
 				78E45E3221D3CBC000D4B15E /* AppTabBarController.swift */,
@@ -761,9 +767,11 @@
 				AEACE2E51FB32E1900DCDD78 /* Utils.swift in Sources */,
 				7070FB7620FF345F000DC258 /* dc_pgp.c in Sources */,
 				7070FB5D20FF345F000DC258 /* dc_mimefactory.c in Sources */,
+				789E879D21D6DF86003ED1C5 /* ProgressHud.swift in Sources */,
 				7070FB8C20FF4118000DC258 /* dc_contact.c in Sources */,
 				78E45E4C21D404AE00D4B15E /* CustomCell.swift in Sources */,
 				7070FB7020FF345F000DC258 /* dc_lot.c in Sources */,
+				789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */,
 				7A79236F1FB0A2C800BC2DE5 /* openssl_crypto.c in Sources */,
 				7A79236C1FB0A2C800BC2DE5 /* crypto.c in Sources */,
 				7070FB5F20FF345F000DC258 /* dc_tools.c in Sources */,

+ 40 - 119
deltachat-ios/ChatViewController.swift

@@ -6,47 +6,36 @@
 //  Copyright © 2017 Jonas Reinsch. All rights reserved.
 //
 
+import ALCameraViewController
 import MapKit
 import MessageInputBar
 import MessageKit
 import UIKit
-import ALCameraViewController
 
 class ChatViewController: MessagesViewController {
     let outgoingAvatarOverlap: CGFloat = 17.5
 
     let chatId: Int
     let refreshControl = UIRefreshControl()
-    var messageIds: [Int] = []
-    var messageList: [Message] = []
+    var messageList: [MRMessage] = []
 
     var msgChangedObserver: Any?
     var incomingMsgObserver: Any?
 
     var disableWriting = false
-    
+
     var previewView: UIView?
 
     init(chatId: Int) {
         self.chatId = chatId
         super.init(nibName: nil, bundle: nil)
-        // self.getMessageIds()
-
-        /*
-         let chat = MRChat(id: chatId)
-         let subtitle = dc_chat_get_subtitle(chat.chatPointer)!
-
-         let s = String(validatingUTF8: subtitle)
-         logger.info( s)
-         */
     }
 
     @objc
     func loadMoreMessages() {
         DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
-            self.getMessageIds()
             DispatchQueue.main.async {
-                self.messageList = self.messageIds.map(self.idToMessage)
+                self.messageList = self.getMessageIds(30, from: self.messageList.count) + self.messageList
                 self.messagesCollectionView.reloadDataAndKeepOffset()
                 self.refreshControl.endRefreshing()
             }
@@ -55,9 +44,8 @@ class ChatViewController: MessagesViewController {
 
     func loadFirstMessages() {
         DispatchQueue.global(qos: .userInitiated).async {
-            self.getMessageIds()
             DispatchQueue.main.async {
-                self.messageList = self.messageIds.map(self.idToMessage)
+                self.messageList = self.getMessageIds(30)
                 self.messagesCollectionView.reloadData()
                 self.refreshControl.endRefreshing()
                 self.messagesCollectionView.scrollToBottom(animated: false)
@@ -65,31 +53,6 @@ class ChatViewController: MessagesViewController {
         }
     }
 
-    private func idToMessage(messageId: Int) -> Message {
-        let message = MRMessage(id: messageId)
-        let contact = MRContact(id: message.fromContactId)
-        let messageId = "\(messageId)"
-        let date = Date(timeIntervalSince1970: Double(message.timestamp))
-        let sender = Sender(id: "\(contact.id)", displayName: contact.name)
-
-        if message.isInfo {
-            let text = NSAttributedString(string: message.text ?? "", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12), NSAttributedString.Key.foregroundColor: UIColor.darkGray])
-            return Message(attributedText: text, sender: sender, messageId: messageId, date: date)
-        } else if let image = message.image {
-            return Message(image: image, sender: sender, messageId: messageId, date: date)
-        } else {
-            return Message(text: message.text ?? "- empty -", sender: sender, messageId: messageId, date: date)
-        }
-    }
-
-    private func messageToMRMessage(message: Message) -> MRMessage? {
-        if let id = Int(message.messageId) {
-            return MRMessage(id: id)
-        }
-
-        return nil
-    }
-
     var textDraft: String? {
         // FIXME: need to free pointer
         if let draft = dc_get_draft(mailboxPointer, UInt32(chatId)) {
@@ -102,15 +65,19 @@ class ChatViewController: MessagesViewController {
         return nil
     }
 
-    func getMessageIds() {
+    func getMessageIds(_ count: Int, from: Int? = nil) -> [MRMessage] {
         let c_messageIds = dc_get_chat_msgs(mailboxPointer, UInt32(chatId), 0, 0)
-        messageIds = Utils.copyAndFreeArray(inputArray: c_messageIds)
 
-        let ids: UnsafePointer = UnsafePointer(messageIds.map { id in
-            UInt32(id)
-        })
+        let ids: [Int]
+        if let from = from {
+            ids = Utils.copyAndFreeArrayWithOffset(inputArray: c_messageIds, len: count, skipEnd: from)
+        } else {
+            ids = Utils.copyAndFreeArrayWithLen(inputArray: c_messageIds, len: count)
+        }
 
-        dc_markseen_msgs(mailboxPointer, ids, Int32(messageIds.count))
+        return ids.map {
+            MRMessage(id: $0)
+        }
     }
 
     required init?(coder _: NSCoder) {
@@ -137,7 +104,7 @@ class ChatViewController: MessagesViewController {
             if let ui = notification.userInfo {
                 if self.chatId == ui["chat_id"] as! Int {
                     let id = ui["message_id"] as! Int
-                    self.insertMessage(self.idToMessage(messageId: id))
+                    self.insertMessage(MRMessage(id: id))
                 }
             }
         }
@@ -331,13 +298,9 @@ extension ChatViewController: MessagesDataSource {
     }
 
     func avatar(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> Avatar {
-        if let id = Int(messageList[indexPath.section].messageId) {
-            let message = MRMessage(id: id)
-            let contact = message.fromContact
-            return Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
-        }
-
-        return Avatar(image: nil, initials: "?")
+        let message = messageList[indexPath.section]
+        let contact = message.fromContact
+        return Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
     }
 
     func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
@@ -363,36 +326,30 @@ extension ChatViewController: MessagesDataSource {
 
     func isPreviousMessageSameSender(at indexPath: IndexPath) -> Bool {
         guard indexPath.section - 1 >= 0 else { return false }
-        return messageList[indexPath.section].sender == messageList[indexPath.section - 1].sender
+        return messageList[indexPath.section].fromContactId == messageList[indexPath.section - 1].fromContactId
     }
 
     func isInfoMessage(at indexPath: IndexPath) -> Bool {
-        if let id = Int(messageList[indexPath.section].messageId) {
-            return MRMessage(id: id).isInfo
-        }
-
-        return false
+        return messageList[indexPath.section].isInfo
     }
 
     func isNextMessageSameSender(at indexPath: IndexPath) -> Bool {
         guard indexPath.section + 1 < messageList.count else { return false }
-        return messageList[indexPath.section].sender == messageList[indexPath.section + 1].sender
+        return messageList[indexPath.section].fromContactId == messageList[indexPath.section + 1].fromContactId
     }
 
     func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
         guard indexPath.section < messageList.count else { return nil }
-        if let m = messageToMRMessage(message: messageList[indexPath.section]) {
-            if !isNextMessageSameSender(at: indexPath), isFromCurrentSender(message: message) {
-                return NSAttributedString(string: m.stateOutDescription(), attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
-            }
+        let m = messageList[indexPath.section]
+        if !isNextMessageSameSender(at: indexPath), isFromCurrentSender(message: message) {
+            return NSAttributedString(string: m.stateOutDescription(), attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
         }
         return nil
     }
 
     func updateMessage(_ messageId: Int) {
-        let messageIdStr = String(messageId)
-        if let index = messageList.firstIndex(where: { $0.messageId == messageIdStr }) {
-            messageList[index] = idToMessage(messageId: messageId)
+        if let index = messageList.firstIndex(where: { $0.id == messageId }) {
+            messageList[index] = MRMessage(id: messageId)
             // Reload section to update header/footer labels
             messagesCollectionView.performBatchUpdates({
                 messagesCollectionView.reloadSections([index])
@@ -408,11 +365,11 @@ extension ChatViewController: MessagesDataSource {
                 }
             })
         } else {
-            insertMessage(idToMessage(messageId: messageId))
+            insertMessage(MRMessage(id: messageId))
         }
     }
 
-    func insertMessage(_ message: Message) {
+    func insertMessage(_ message: MRMessage) {
         messageList.append(message)
         // Reload last section to update header/footer labels and insert a new one
         messagesCollectionView.performBatchUpdates({
@@ -491,13 +448,11 @@ extension ChatViewController: MessagesDisplayDelegate {
     }
 
     func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) {
-        if let id = Int(messageList[indexPath.section].messageId) {
-            let message = MRMessage(id: id)
-            let contact = message.fromContact
-            let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
-            avatarView.set(avatar: avatar)
-            avatarView.isHidden = isNextMessageSameSender(at: indexPath) || message.isInfo
-        }
+        let message = messageList[indexPath.section]
+        let contact = message.fromContact
+        let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
+        avatarView.set(avatar: avatar)
+        avatarView.isHidden = isNextMessageSameSender(at: indexPath) || message.isInfo
     }
 
     func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
@@ -537,68 +492,34 @@ extension ChatViewController: MessagesLayoutDelegate {
 
     @objc func didPressPhotoButton() {
         if UIImagePickerController.isSourceTypeAvailable(.camera) {
-            let cameraViewController = CameraViewController { [weak self] image, asset in
+            let cameraViewController = CameraViewController { [weak self] image, _ in
                 self?.dismiss(animated: true, completion: nil)
-                
+
                 DispatchQueue.global().async {
                     if let pickedImage = image {
                         let width = Int32(exactly: pickedImage.size.width)!
                         let height = Int32(exactly: pickedImage.size.height)!
-                        let path = self?.saveImage(image: pickedImage)
+                        let path = Utils.saveImage(image: pickedImage)
                         let msg = dc_msg_new(mailboxPointer, DC_MSG_IMAGE)
                         dc_msg_set_file(msg, path, "image/jpeg")
                         dc_msg_set_dimension(msg, width, height)
                         dc_send_msg(mailboxPointer, UInt32(self!.chatId), msg)
-                        
+
                         // cleanup
                         dc_msg_unref(msg)
                     }
                 }
             }
-            
+
             present(cameraViewController, animated: true, completion: nil)
         } else {
             let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: {_ in
+            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
                 self.dismiss(animated: true, completion: nil)
             }))
             present(alert, animated: true, completion: nil)
         }
     }
-
-    func saveImage(image: UIImage) -> String? {
-        guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
-            return nil
-        }
-
-        let size = image.size.applying(CGAffineTransform(scaleX: 0.2, y: 0.2))
-        let hasAlpha = false
-        let scale: CGFloat = 0.0
-
-        UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
-        image.draw(in: CGRect(origin: CGPoint.zero, size: size))
-
-        let _scaledImage = UIGraphicsGetImageFromCurrentImageContext()
-        UIGraphicsEndImageContext()
-
-        guard let scaledImage = _scaledImage else {
-            return nil
-        }
-
-        guard let data = scaledImage.jpegData(compressionQuality: 0.9) else {
-            return nil
-        }
-
-        do {
-            let timestamp = Int(Date().timeIntervalSince1970)
-            let path = directory.appendingPathComponent("\(chatId)_\(timestamp).jpg")
-            try data.write(to: path!)
-            return path?.relativePath
-        } catch {
-            logger.info(error.localizedDescription)
-            return nil
-        }
-    }
 }
 
 // MARK: - MessageCellDelegate

+ 100 - 8
deltachat-ios/NewChatViewController.swift

@@ -6,18 +6,23 @@
 //  Copyright © 2017 Jonas Reinsch. All rights reserved.
 //
 
+import ALCameraViewController
 import UIKit
 
 protocol ChatDisplayer: class {
     func displayNewChat(contactId: Int)
+    func displayChatForId(chatId: Int)
 }
 
 class NewChatViewController: UITableViewController {
     var contactIds: [Int] = Utils.getContactIds()
     weak var chatDisplayer: ChatDisplayer?
 
+    var syncObserver: Any?
+    var hud: ProgressHud?
+
     override func viewDidLoad() {
-        // super.viewDidLoad()
+        super.viewDidLoad()
 
         title = "New Chat"
         navigationController?.navigationBar.prefersLargeTitles = true
@@ -27,9 +32,38 @@ class NewChatViewController: UITableViewController {
         navigationItem.rightBarButtonItem = cancelButton
     }
 
-    override func viewDidAppear(_: Bool) {
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+
         contactIds = Utils.getContactIds()
         tableView.reloadData()
+
+        let nc = NotificationCenter.default
+        syncObserver = nc.addObserver(
+            forName: dc_notificationSecureJoinerProgress,
+            object: nil,
+            queue: nil
+        ) {
+            notification in
+            if let ui = notification.userInfo {
+                if ui["error"] as! Bool {
+                    self.hud?.error(ui["errorMessage"] as? String)
+                } else if ui["done"] as! Bool {
+                    self.hud?.done()
+                } else {
+                    self.hud?.progress(ui["progress"] as! Int)
+                }
+            }
+        }
+    }
+
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+
+        let nc = NotificationCenter.default
+        if let syncObserver = self.syncObserver {
+            nc.removeObserver(syncObserver)
+        }
     }
 
     @objc func cancelButtonPressed() {
@@ -48,7 +82,7 @@ class NewChatViewController: UITableViewController {
     }
 
     override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-        return contactIds.count + 2
+        return contactIds.count + 3
     }
 
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
@@ -67,6 +101,20 @@ class NewChatViewController: UITableViewController {
             return cell
         }
         if row == 1 {
+            // new contact row
+            let cell: UITableViewCell
+            if let c = tableView.dequeueReusableCell(withIdentifier: "scanGroupCell") {
+                cell = c
+            } else {
+                cell = UITableViewCell(style: .default, reuseIdentifier: "scanGroupCell")
+            }
+            cell.textLabel?.text = "Scan Group QR Code"
+            cell.textLabel?.textColor = view.tintColor
+
+            return cell
+        }
+
+        if row == 2 {
             // new contact row
             let cell: UITableViewCell
             if let c = tableView.dequeueReusableCell(withIdentifier: "newContactCell") {
@@ -87,7 +135,7 @@ class NewChatViewController: UITableViewController {
             cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
         }
 
-        let contactRow = row - 2
+        let contactRow = row - 3
 
         let contact = MRContact(id: contactIds[contactRow])
         cell.nameLabel.text = contact.name
@@ -106,11 +154,25 @@ class NewChatViewController: UITableViewController {
             navigationController?.pushViewController(newGroupController, animated: true)
         }
         if row == 1 {
+            if UIImagePickerController.isSourceTypeAvailable(.camera) {
+                let controller = QrCodeReaderController()
+                controller.delegate = self
+                present(controller, animated: true, completion: nil)
+
+            } else {
+                let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
+                alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+                    self.dismiss(animated: true, completion: nil)
+                }))
+                present(alert, animated: true, completion: nil)
+            }
+        }
+        if row == 2 {
             let newContactController = NewContactController()
             navigationController?.pushViewController(newContactController, animated: true)
         }
-        if row > 1 {
-            let contactIndex = row - 2
+        if row > 2 {
+            let contactIndex = row - 3
             let contactId = contactIds[contactIndex]
             dismiss(animated: false) {
                 self.chatDisplayer?.displayNewChat(contactId: contactId)
@@ -120,8 +182,8 @@ class NewChatViewController: UITableViewController {
 
     override func tableView(_: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
         let row = indexPath.row
-        if row > 1 {
-            let contactIndex = row - 2
+        if row > 2 {
+            let contactIndex = row - 3
             let contactId = contactIds[contactIndex]
             // let newContactController = NewContactController(contactIdForUpdate: contactId)
             // navigationController?.pushViewController(newContactController, animated: true)
@@ -130,3 +192,33 @@ class NewChatViewController: UITableViewController {
         }
     }
 }
+
+extension NewChatViewController: QrCodeReaderDelegate {
+    func handleQrCode(_ code: String) {
+        logger.info("decoded: \(code)")
+
+        let check = dc_check_qr(mailboxPointer, code)!
+        logger.info("got ver: \(check)")
+
+        if dc_lot_get_state(check) == DC_QR_ASK_VERIFYGROUP {
+            hud = ProgressHud("Synchronizing Account", in: view)
+            DispatchQueue.global(qos: .userInitiated).async {
+                let id = dc_join_securejoin(mailboxPointer, code)
+
+                DispatchQueue.main.async {
+                    self.dismiss(animated: true) {
+                        self.chatDisplayer?.displayChatForId(chatId: Int(id))
+                    }
+                }
+            }
+        } else {
+            let alert = UIAlertController(title: "Not a valid group QR Code", message: code, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+                self.dismiss(animated: true, completion: nil)
+            }))
+            present(alert, animated: true, completion: nil)
+        }
+
+        dc_lot_unref(check)
+    }
+}

+ 57 - 0
deltachat-ios/ProgressHud.swift

@@ -0,0 +1,57 @@
+//
+//  ProgressHud.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 28.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import JGProgressHUD
+
+class ProgressHud {
+    var hud: JGProgressHUD
+
+    func error(_ message: String?, _ cb: (() -> Void)? = nil) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+            UIView.animate(
+                withDuration: 0.1, animations: {
+                    self.hud.textLabel.text = message ?? "Error"
+                    self.hud.detailTextLabel.text = nil
+                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                }
+            )
+            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(5000)) {
+                self.hud.dismiss()
+                cb?()
+            }
+        }
+    }
+
+    func done(_ message: String? = nil) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+            UIView.animate(
+                withDuration: 0.1, animations: {
+                    self.hud.textLabel.text = message ?? "Success"
+                    self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
+                }
+            )
+
+            self.hud.dismiss(afterDelay: 1.0)
+        }
+    }
+
+    func progress(_ progress: Int) {
+        hud.progress = Float(progress) / 1000.0
+        hud.detailTextLabel.text = "\(progress / 10)% Complete"
+    }
+
+    init(_ text: String, in view: UIView) {
+        hud = JGProgressHUD(style: .dark)
+        hud.vibrancyEnabled = true
+        hud.indicatorView = JGProgressHUDPieIndicatorView()
+
+        hud.detailTextLabel.text = "0% Complete"
+        hud.textLabel.text = text
+        hud.show(in: view)
+    }
+}

+ 103 - 0
deltachat-ios/QrCodeReaderController.swift

@@ -0,0 +1,103 @@
+//
+//  QrCodeReaderController.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 28.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import AVFoundation
+import UIKit
+
+protocol QrCodeReaderDelegate: class {
+    func handleQrCode(_ code: String)
+}
+
+class QrCodeReaderController: UIViewController {
+    var captureSession = AVCaptureSession()
+
+    var videoPreviewLayer: AVCaptureVideoPreviewLayer?
+    var qrCodeFrameView: UIView?
+
+    weak var delegate: QrCodeReaderDelegate?
+
+    private let supportedCodeTypes = [
+        AVMetadataObject.ObjectType.qr,
+    ]
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        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
+        }
+
+        do {
+            let input = try AVCaptureDeviceInput(device: captureDevice)
+            captureSession.addInput(input)
+
+            let captureMetadataOutput = AVCaptureMetadataOutput()
+            captureSession.addOutput(captureMetadataOutput)
+
+            captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
+            captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
+        } catch {
+            // If any error occurs, simply print it out and don't continue any more.
+            logger.error("failed to setup QR Code Scanner: \(error)")
+            return
+        }
+
+        videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
+        videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
+        videoPreviewLayer?.frame = view.layer.bounds
+        view.layer.addSublayer(videoPreviewLayer!)
+
+        captureSession.startRunning()
+
+        qrCodeFrameView = UIView()
+
+        if let qrCodeFrameView = qrCodeFrameView {
+            qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
+            qrCodeFrameView.layer.borderWidth = 2
+            view.addSubview(qrCodeFrameView)
+            view.bringSubviewToFront(qrCodeFrameView)
+        }
+    }
+
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+        // Dispose of any resources that can be recreated.
+    }
+}
+
+extension QrCodeReaderController: AVCaptureMetadataOutputObjectsDelegate {
+    func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
+        if metadataObjects.count == 0 {
+            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!)
+                    }
+                }
+            }
+        }
+    }
+}

+ 5 - 1
deltachat-ios/TopViews/ChatListController.swift

@@ -125,7 +125,11 @@ extension ChatListController: ChatPresenter {
 extension ChatListController: ChatDisplayer {
     func displayNewChat(contactId: Int) {
         let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
-        let chatVC = ChatViewController(chatId: Int(chatId))
+        displayChatForId(chatId: Int(chatId))
+    }
+
+    func displayChatForId(chatId: Int) {
+        let chatVC = ChatViewController(chatId: chatId)
 
         chatVC.hidesBottomBarWhenPushed = true
         navigationController?.pushViewController(chatVC, animated: true)

+ 3 - 3
deltachat-ios/TopViews/SettingsController.swift

@@ -176,7 +176,7 @@ internal final class SettingsViewController: QuickTableViewController {
                 title: "Flags",
                 rows: [
                     SwitchRow(title: "E2EE enabled", switchValue: MRConfig.e2eeEnabled, action: editCell()),
-                    SwitchRow(title: "MDNS enabled", switchValue: MRConfig.mdnsEnabled, action: editCell()),
+                    SwitchRow(title: "Read Receipts", switchValue: MRConfig.mdnsEnabled, action: editCell()),
                     SwitchRow(title: "Watch Inbox", switchValue: MRConfig.inboxWatch, action: editCell()),
                     SwitchRow(title: "Watch Sentbox", switchValue: MRConfig.sentboxWatch, action: editCell()),
                     SwitchRow(title: "Watch Mvbox", switchValue: MRConfig.mvboxWatch, action: editCell()),
@@ -216,7 +216,7 @@ internal final class SettingsViewController: QuickTableViewController {
                 switch title {
                 case "E2EE enabled":
                     MRConfig.e2eeEnabled = value
-                case "MDNS enabled":
+                case "Read Receipts":
                     MRConfig.mdnsEnabled = value
                 case "Watch Inbox":
                     MRConfig.inboxWatch = value
@@ -353,7 +353,7 @@ internal final class SettingsViewController: QuickTableViewController {
                 hud.dismiss(afterDelay: 1.0)
             } else {
                 let alert = UIAlertController(title: "Can not restore", message: "No Backup found", preferredStyle: .alert)
-                alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: {_ in
+                alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
                     self.dismiss(animated: true, completion: nil)
                 }))
                 present(alert, animated: true, completion: nil)

+ 70 - 0
deltachat-ios/Utils.swift

@@ -30,6 +30,42 @@ struct Utils {
             acc.append(Int(e))
         }
         dc_array_unref(inputArray)
+
+        return acc
+    }
+
+    static func copyAndFreeArrayWithLen(inputArray: UnsafeMutablePointer<dc_array_t>?, len: Int = 0) -> [Int] {
+        var acc: [Int] = []
+        let arrayLen = dc_array_get_cnt(inputArray)
+        let start = max(0, arrayLen - len)
+        for i in start ..< arrayLen {
+            let e = dc_array_get_id(inputArray, i)
+            acc.append(Int(e))
+        }
+        dc_array_unref(inputArray)
+
+        return acc
+    }
+
+    static func copyAndFreeArrayWithOffset(inputArray: UnsafeMutablePointer<dc_array_t>?, len: Int = 0, from: Int = 0, skipEnd: Int = 0) -> [Int] {
+        let lenArray = dc_array_get_cnt(inputArray)
+        if lenArray <= skipEnd || lenArray == 0 {
+            dc_array_unref(inputArray)
+            return [] }
+
+        let start = lenArray - 1 - skipEnd
+        let end = max(0, start - len)
+        let finalLen = start - end + (len > 0 ? 0 : 1)
+        var acc: [Int] = [Int](repeating: 0, count: finalLen)
+
+        for i in stride(from: start, to: end, by: -1) {
+            let index = finalLen - (start - i) - 1
+            acc[index] = Int(dc_array_get_id(inputArray, i))
+        }
+
+        dc_array_unref(inputArray)
+        logger.info("got: \(from) \(len) \(lenArray) - \(acc)")
+
         return acc
     }
 
@@ -60,6 +96,40 @@ struct Utils {
         addAddressPart(address["Country"])
         return addressParts.joined(separator: ", ")
     }
+
+    static func saveImage(image: UIImage) -> String? {
+        guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
+            return nil
+        }
+
+        let size = image.size.applying(CGAffineTransform(scaleX: 0.2, y: 0.2))
+        let hasAlpha = false
+        let scale: CGFloat = 0.0
+
+        UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
+        image.draw(in: CGRect(origin: CGPoint.zero, size: size))
+
+        let _scaledImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        guard let scaledImage = _scaledImage else {
+            return nil
+        }
+
+        guard let data = scaledImage.jpegData(compressionQuality: 0.9) else {
+            return nil
+        }
+
+        do {
+            let timestamp = Int(Date().timeIntervalSince1970)
+            let path = directory.appendingPathComponent("\(timestamp).jpg")
+            try data.write(to: path!)
+            return path?.relativePath
+        } catch {
+            logger.info(error.localizedDescription)
+            return nil
+        }
+    }
 }
 
 extension UIColor {

+ 67 - 6
deltachat-ios/Wrapper.swift

@@ -7,6 +7,7 @@
 //
 
 import Foundation
+import MessageKit
 import UIKit
 
 class MRContact {
@@ -75,9 +76,39 @@ class MRContact {
     }
 }
 
-class MRMessage {
+class MRMessage: MessageType {
     private var messagePointer: UnsafeMutablePointer<dc_msg_t>
 
+    lazy var sender: Sender = {
+        Sender(id: "\(fromContactId)", displayName: fromContact.name)
+    }()
+
+    lazy var sentDate: Date = {
+        Date(timeIntervalSince1970: Double(timestamp))
+    }()
+
+    lazy var kind: MessageKind = {
+        if isInfo {
+            let text = NSAttributedString(string: self.text ?? "", attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12), NSAttributedString.Key.foregroundColor: UIColor.darkGray])
+            return MessageKind.attributedText(text)
+        }
+
+        if let image = self.image {
+            return MessageKind.photo(Media(image: image))
+        }
+
+        if let filename = self.filename {
+            // TODO:
+            return MessageKind.text("File: \(self.filename ?? "") (\(self.filesize) bytes)")
+        }
+
+        return MessageKind.text(self.text ?? "- empty -")
+    }()
+
+    var messageId: String {
+        return "\(id)"
+    }
+
     var id: Int {
         return Int(messagePointer.pointee.id)
     }
@@ -102,11 +133,6 @@ class MRMessage {
         return String(cString: messagePointer.pointee.text)
     }
 
-    var mimeType: String? {
-        guard let result = dc_msg_get_filemime(messagePointer) else { return nil }
-        return String(cString: result)
-    }
-
     lazy var image: UIImage? = { [unowned self] in
         let filetype = dc_msg_get_viewtype(messagePointer)
         let file = dc_msg_get_file(messagePointer)
@@ -129,6 +155,40 @@ class MRMessage {
         }
     }()
 
+    var file: String? {
+        if let cStr = dc_msg_get_file(messagePointer) {
+            let str = String(cString: cStr)
+
+            return str == "" ? nil : str
+        }
+
+        return nil
+    }
+
+    var filemime: String? {
+        if let cStr = dc_msg_get_filemime(messagePointer) {
+            let str = String(cString: cStr)
+
+            return str == "" ? nil : str
+        }
+
+        return nil
+    }
+
+    var filename: String? {
+        if let cStr = dc_msg_get_filename(messagePointer) {
+            let str = String(cString: cStr)
+
+            return str == "" ? nil : str
+        }
+
+        return nil
+    }
+
+    var filesize: Int {
+        return Int(dc_msg_get_filebytes(messagePointer))
+    }
+
     // MR_MSG_*
     var type: Int {
         return Int(messagePointer.pointee.type)
@@ -164,6 +224,7 @@ class MRMessage {
 
     init(id: Int) {
         messagePointer = dc_get_msg(mailboxPointer, UInt32(id))
+        dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(id)]), 1)
     }
 
     func summary(chars: Int) -> String? {

+ 32 - 0
deltachat-ios/events.swift

@@ -13,6 +13,8 @@ let dc_notificationStateChanged = Notification.Name(rawValue: "MrEventStateChang
 let dc_notificationIncoming = Notification.Name(rawValue: "MrEventIncomingMsg")
 let dc_notificationBackupProgress = Notification.Name(rawValue: "MrEventBackupProgress")
 let dc_notificationConfigureProgress = Notification.Name(rawValue: "MrEventConfigureProgress")
+let dc_notificationSecureJoinerProgress = Notification.Name(rawValue: "MrEventSecureJoinerProgress")
+let dc_notificationSecureInviterProgress = Notification.Name(rawValue: "MrEventSecureInviterProgress")
 
 @_silgen_name("callbackSwift")
 
@@ -142,6 +144,36 @@ public func callbackSwift(event: CInt, data1: CUnsignedLong, data2: CUnsignedLon
         }
     case DC_EVENT_IMEX_FILE_WRITTEN:
         logger.info("backup file written: \(String(cString: data1String))")
+
+    case DC_EVENT_SECUREJOIN_INVITER_PROGRESS:
+        logger.info("securejoin inviter progress \(data1)")
+
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            nc.post(
+                name: dc_notificationSecureInviterProgress,
+                object: nil,
+                userInfo: [
+                    "progress": Int(data2),
+                    "error": Int(data2) == 0,
+                    "done": Int(data2) == 1000,
+                ]
+            )
+        }
+    case DC_EVENT_SECUREJOIN_JOINER_PROGRESS:
+        logger.info("securejoin joiner progress \(data1)")
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            nc.post(
+                name: dc_notificationSecureJoinerProgress,
+                object: nil,
+                userInfo: [
+                    "progress": Int(data2),
+                    "error": Int(data2) == 0,
+                    "done": Int(data2) == 1000,
+                ]
+            )
+        }
     case DC_EVENT_GET_STRING:
         // nothing to do for now
         break