Эх сурвалжийг харах

notifications round 1

- add delete account
-  ensure backup restore works
- enable notifications
- send local notifications
- tweak background handling
dignifiedquire 6 жил өмнө
parent
commit
9f9e67f1a0

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

@@ -69,7 +69,6 @@
 		78ED838D21D577D000243125 /* events.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78ED838C21D577D000243125 /* events.swift */; };
 		78ED838F21D5927A00243125 /* ProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78ED838E21D5927A00243125 /* ProfileViewController.swift */; };
 		78ED839421D5AF8A00243125 /* QrCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78ED839321D5AF8A00243125 /* QrCodeView.swift */; };
-		7A0052A11FBC50C40048C3BF /* CredentialsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0052A01FBC50C40048C3BF /* CredentialsController.swift */; };
 		7A0052C81FBE6CB40048C3BF /* NewContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0052C71FBE6CB40048C3BF /* NewContactController.swift */; };
 		7A451D941FB1B1DB00177250 /* wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 7A451D921FB1B1DB00177250 /* wrapper.c */; };
 		7A451DAE1FB1F5A200177250 /* libetpan-ios.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7A451DA71FB1F4BF00177250 /* libetpan-ios.a */; };
@@ -201,7 +200,6 @@
 		78ED838C21D577D000243125 /* events.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = events.swift; sourceTree = "<group>"; };
 		78ED838E21D5927A00243125 /* ProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileViewController.swift; sourceTree = "<group>"; };
 		78ED839321D5AF8A00243125 /* QrCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeView.swift; sourceTree = "<group>"; };
-		7A0052A01FBC50C40048C3BF /* CredentialsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsController.swift; sourceTree = "<group>"; };
 		7A0052C71FBE6CB40048C3BF /* NewContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewContactController.swift; sourceTree = "<group>"; };
 		7A451D921FB1B1DB00177250 /* wrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wrapper.c; sourceTree = "<group>"; };
 		7A451D931FB1B1DB00177250 /* wrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = wrapper.h; sourceTree = "<group>"; };
@@ -432,7 +430,6 @@
 				78E45E3221D3CBC000D4B15E /* AppTabBarController.swift */,
 				AEACE2DC1FB323CA00DCDD78 /* ChatViewController.swift */,
 				78E45E3D21D3D28C00D4B15E /* NavigationController.swift */,
-				7A0052A01FBC50C40048C3BF /* CredentialsController.swift */,
 				7092474020B3869500AF8799 /* ContactProfileViewController.swift */,
 				7A0052C71FBE6CB40048C3BF /* NewContactController.swift */,
 				78ED838A21D5570700243125 /* Extensions */,
@@ -570,13 +567,16 @@
 						ProvisioningStyle = Automatic;
 						SystemCapabilities = {
 							com.apple.ApplicationGroups.iOS = {
-								enabled = 0;
+								enabled = 1;
 							};
 							com.apple.BackgroundModes = {
 								enabled = 1;
 							};
 							com.apple.Push = {
-								enabled = 0;
+								enabled = 1;
+							};
+							com.apple.SafariKeychain = {
+								enabled = 1;
 							};
 						};
 					};
@@ -707,7 +707,6 @@
 				78ED839421D5AF8A00243125 /* QrCodeView.swift in Sources */,
 				78E45E2921D176C400D4B15E /* dc_jobthread.h in Sources */,
 				7A79236E1FB0A2C800BC2DE5 /* misc.c in Sources */,
-				7A0052A11FBC50C40048C3BF /* CredentialsController.swift in Sources */,
 				7070FB6B20FF345F000DC258 /* dc_job.c in Sources */,
 				7070FB7520FF345F000DC258 /* dc_strbuilder.c in Sources */,
 				7070FB6220FF345F000DC258 /* dc_configure.c in Sources */,
@@ -921,6 +920,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = "deltachat-ios/deltachat-ios.entitlements";
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				DEVELOPMENT_TEAM = EEQW58QXHC;
@@ -934,7 +934,7 @@
 					"-lcrypto",
 					"$(inherited)",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.jonasreinsch.deltachatios;
+				PRODUCT_BUNDLE_IDENTIFIER = com.delta.chat.ios;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_OBJC_BRIDGING_HEADER = "deltachat-ios/deltachat-ios-Bridging-Header.h";
@@ -950,6 +950,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
+				CODE_SIGN_ENTITLEMENTS = "deltachat-ios/deltachat-ios.entitlements";
 				CODE_SIGN_IDENTITY = "iPhone Developer";
 				CODE_SIGN_STYLE = Automatic;
 				DEVELOPMENT_TEAM = EEQW58QXHC;
@@ -963,7 +964,7 @@
 					"-lcrypto",
 					"$(inherited)",
 				);
-				PRODUCT_BUNDLE_IDENTIFIER = com.jonasreinsch.deltachatios;
+				PRODUCT_BUNDLE_IDENTIFIER = com.delta.chat.ios;
 				PRODUCT_NAME = "$(TARGET_NAME)";
 				PROVISIONING_PROFILE_SPECIFIER = "";
 				SWIFT_OBJC_BRIDGING_HEADER = "deltachat-ios/deltachat-ios-Bridging-Header.h";

+ 1 - 0
deltachat-ios.xcodeproj/xcshareddata/xcschemes/deltachat-ios.xcscheme

@@ -49,6 +49,7 @@
       useCustomWorkingDirectory = "NO"
       ignoresPersistentStateOnLaunch = "NO"
       debugDocumentVersioning = "YES"
+      stopOnEveryMainThreadCheckerIssue = "YES"
       debugServiceExtension = "internal"
       allowLocationSimulation = "YES">
       <BuildableProductRunnable

+ 0 - 35
deltachat-ios/AppCoordinator.swift

@@ -18,41 +18,6 @@ class AppCoordinator: Coordinator {
     func setupViewControllers(window: UIWindow) {
         window.rootViewController = AppTabBarController()
         window.makeKeyAndVisible()
-        // window.backgroundColor = UIColor.white
-
-//            setupInnerViewControllers()
-//        } else {
-        ////            let email = "alice@librechat.net"
-        ////            let password = "foobar"
-        ////            initCore(email: email, password: password)
-//
-//            displayCredentialsController()
-//        }
-    }
-
-    func displayCredentialsController(message: String? = nil, isCancellable: Bool = false) {
-        let credentialsController = CredentialsController(isCancellable: isCancellable)
-
-        let credentialsNav = UINavigationController(rootViewController: credentialsController)
-
-        if baseController.presentedViewController != nil {
-            baseController.dismiss(animated: false, completion: nil)
-        }
-
-        baseController.present(credentialsNav, animated: false) {
-            if let message = message {
-                let alert = UIAlertController(title: "Warning", message: message, preferredStyle: .alert)
-
-                alert.addAction(UIAlertAction(title: "Help / Provider Overview", style: UIAlertAction.Style.default, handler: {
-                    _ in
-                    let url = URL(string: "https://support.delta.chat/t/provider-overview/56/2")!
-                    UIApplication.shared.open(url, options: [:])
-                }))
-
-                alert.addAction(UIAlertAction(title: "Ok", style: .cancel, handler: nil))
-                credentialsNav.present(alert, animated: false, completion: nil)
-            }
-        }
     }
 
     func setupInnerViewControllers() {

+ 95 - 99
deltachat-ios/AppDelegate.swift

@@ -24,11 +24,11 @@ enum ApplicationState {
 }
 
 @UIApplicationMain
-class AppDelegate: UIResponder, UIApplicationDelegate {
+class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
     static let appCoordinator = AppCoordinator()
     static var progress: Float = 0
     static var lastErrorDuringConfig: String?
-    static var cancellableCredentialsController = false
+    var backgroundTask: UIBackgroundTaskIdentifier = .invalid
 
     var reachability = Reachability()!
     var window: UIWindow?
@@ -69,7 +69,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         start()
         open()
 
-        // registerForPushNotifications()
+        registerForPushNotifications()
 
         return true
     }
@@ -109,8 +109,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
     func applicationDidEnterBackground(_: UIApplication) {
         logger.info("---- background ----")
 
-        stop()
-
+        // stop()
         reachability.stopNotifier()
         NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
     }
@@ -118,15 +117,22 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
     func applicationWillTerminate(_: UIApplication) {
         logger.info("---- terminate ----")
         close()
+
+        reachability.stopNotifier()
+        NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
     }
 
-    func open() {
+    func dbfile() -> String {
         let paths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
         let documentsPath = paths[0]
-        let dbfile = documentsPath + "/messenger.db"
-        logger.info("open: \(dbfile)")
 
-        _ = dc_open(mailboxPointer, dbfile, nil)
+        return documentsPath + "/messenger.db"
+    }
+
+    func open() {
+        logger.info("open: \(dbfile())")
+
+        _ = dc_open(mailboxPointer, dbfile(), nil)
     }
 
     func stop() {
@@ -138,19 +144,20 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         dc_interrupt_sentbox_idle(mailboxPointer)
     }
 
-    private func close() {
+    func close() {
         state = .stopped
 
         dc_close(mailboxPointer)
         mailboxPointer = nil
-
-        reachability.stopNotifier()
-        NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
     }
 
     func start() {
         logger.info("---- start ----")
 
+        if state == .running {
+            return
+        }
+
         if mailboxPointer == nil {
             //       - second param remains nil (user data for more than one mailbox)
             mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
@@ -162,31 +169,55 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         state = .running
 
         DispatchQueue.global(qos: .background).async {
+            self.registerBackgroundTask()
             while self.state == .running {
+                DispatchQueue.main.async {
+                    switch UIApplication.shared.applicationState {
+                    case .active:
+                        logger.info("active - imap")
+                    case .background:
+                        logger.info("background - time remaining = " +
+                            "\(UIApplication.shared.backgroundTimeRemaining) seconds")
+                    case .inactive:
+                        break
+                    }
+                }
+
                 dc_perform_imap_jobs(mailboxPointer)
                 dc_perform_imap_fetch(mailboxPointer)
                 dc_perform_imap_idle(mailboxPointer)
             }
+            if self.backgroundTask != .invalid {
+                self.endBackgroundTask()
+            }
         }
 
         DispatchQueue.global(qos: .utility).async {
+            self.registerBackgroundTask()
             while self.state == .running {
                 dc_perform_smtp_jobs(mailboxPointer)
                 dc_perform_smtp_idle(mailboxPointer)
             }
+            if self.backgroundTask != .invalid {
+                self.endBackgroundTask()
+            }
         }
 
-        DispatchQueue.global(qos: .background).async {
-            while self.state == .running {
-                dc_perform_sentbox_fetch(mailboxPointer)
-                dc_perform_sentbox_idle(mailboxPointer)
+        if MRConfig.sentboxWatch {
+            DispatchQueue.global(qos: .background).async {
+                while self.state == .running {
+                    dc_perform_sentbox_fetch(mailboxPointer)
+                    dc_perform_sentbox_idle(mailboxPointer)
+                }
             }
         }
 
-        DispatchQueue.global(qos: .background).async {
-            while self.state == .running {
-                dc_perform_mvbox_fetch(mailboxPointer)
-                dc_perform_mvbox_idle(mailboxPointer)
+        if MRConfig.mvboxWatch {
+            DispatchQueue.global(qos: .background).async {
+                while self.state == .running {
+                    dc_perform_mvbox_fetch(mailboxPointer)
+                    dc_perform_mvbox_idle(mailboxPointer)
+                }
             }
         }
 
@@ -201,6 +232,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
             let value = kv.count > 1 ? kv[1] : ""
             return DBCustomVariable(name: kv[0], value: value)
         }
+
         DBDebugToolkit.add(info)
     }
 
@@ -229,97 +261,61 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         }
     }
 
+    // MARK: - BackgroundTask
+
+    func registerBackgroundTask() {
+        logger.info("background task registered")
+        backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
+            self?.endBackgroundTask()
+        }
+        assert(backgroundTask != .invalid)
+    }
+
+    func endBackgroundTask() {
+        logger.info("background task ended")
+        UIApplication.shared.endBackgroundTask(backgroundTask)
+        backgroundTask = .invalid
+    }
+
+    // MARK: - PushNotifications
+
     func registerForPushNotifications() {
+        UNUserNotificationCenter.current().delegate = self
+
         UNUserNotificationCenter.current()
             .requestAuthorization(options: [.alert, .sound, .badge]) {
                 granted, _ in
                 logger.info("permission granted: \(granted)")
+                guard granted else { return }
+                self.getNotificationSettings()
             }
     }
-}
-
-func initCore(withCredentials: Bool, advancedMode: Bool = false, model: CredentialsModel? = nil, cancellableCredentialsUponFailure: Bool = false) {
-    AppDelegate.cancellableCredentialsController = cancellableCredentialsUponFailure
 
-    if withCredentials {
-        guard let model = model else {
-            fatalError("withCredentials == true implies non-nil model")
+    func getNotificationSettings() {
+        UNUserNotificationCenter.current().getNotificationSettings { settings in
+            logger.info("Notification settings: \(settings)")
         }
-        if !(model.email.contains("@") && (model.email.count >= 3)) {
-            fatalError("initCore called with withCredentials flag set to true, but email not valid")
-        }
-        if model.password.isEmpty {
-            fatalError("initCore called with withCredentials flag set to true, password is empty")
-        }
-        dc_set_config(mailboxPointer, "addr", model.email)
-        dc_set_config(mailboxPointer, "mail_pw", model.password)
-        if advancedMode {
-            if let imapLoginName = model.imapLoginName {
-                dc_set_config(mailboxPointer, "mail_user", imapLoginName)
-            }
-            if let imapServer = model.imapServer {
-                dc_set_config(mailboxPointer, "mail_server", imapServer)
-            }
-            if let imapPort = model.imapPort {
-                dc_set_config(mailboxPointer, "mail_port", imapPort)
-            }
-
-            if let smtpLoginName = model.smtpLoginName {
-                dc_set_config(mailboxPointer, "send_user", smtpLoginName)
-            }
-            if let smtpPassword = model.smtpPassword {
-                dc_set_config(mailboxPointer, "send_pw", smtpPassword)
-            }
-            if let smtpServer = model.smtpServer {
-                dc_set_config(mailboxPointer, "send_server", smtpServer)
-            }
-            if let smtpPort = model.smtpPort {
-                dc_set_config(mailboxPointer, "send_port", smtpPort)
-            }
+    }
 
-            var flags: Int32 = 0
-            if model.smtpSecurity == .automatic, (model.imapSecurity == .automatic) {
-                flags = DC_LP_AUTH_NORMAL
-            } else {
-                if model.smtpSecurity == .off {
-                    flags |= DC_LP_SMTP_SOCKET_PLAIN
-                } else if model.smtpSecurity == .ssltls {
-                    flags |= DC_LP_SMTP_SOCKET_SSL
-                } else if model.smtpSecurity == .starttls {
-                    flags |= DC_LP_SMTP_SOCKET_STARTTLS
-                }
+    func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+        logger.info("forground notification")
+        completionHandler([.alert, .sound])
+    }
 
-                if model.imapSecurity == .off {
-                    flags |= DC_LP_IMAP_SOCKET_PLAIN
-                } else if model.imapSecurity == .ssltls {
-                    flags |= DC_LP_IMAP_SOCKET_SSL
-                } else if model.imapSecurity == .starttls {
-                    flags |= DC_LP_IMAP_SOCKET_STARTTLS
-                }
+    func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+        if response.notification.request.identifier == Constants.notificationIdentifier {
+            logger.info("handling notifications")
+            let userInfo = response.notification.request.content.userInfo
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dc_notificationViewChat,
+                    object: nil,
+                    userInfo: userInfo
+                )
             }
-            let ptr: UnsafeMutablePointer<Int32> = UnsafeMutablePointer.allocate(capacity: 1)
-            ptr.pointee = flags
-            let rp = UnsafeRawPointer(ptr)
-            // rebind memory from Int32 to Int8
-            let up = rp.bindMemory(to: Int8.self, capacity: 1)
-            dc_set_config(mailboxPointer, "server_flags", up)
         }
 
-        // TODO: - handle failure, need to show credentials screen again
-        dc_configure(mailboxPointer)
-        // TODO: next two lines should move here in success case
-        // UserDefaults.standard.set(true, forKey: Constants.Keys.deltachatUserProvidedCredentialsKey)
-        // UserDefaults.standard.synchronize()
-    }
-
-    addVibrationOnIncomingMessage()
-}
-
-func addVibrationOnIncomingMessage() {
-    let nc = NotificationCenter.default
-    nc.addObserver(forName: Notification.Name(rawValue: "MrEventIncomingMsg"),
-                   object: nil, queue: nil) {
-        _ in
-        AudioServicesPlaySystemSound(UInt32(kSystemSoundID_Vibrate))
+        completionHandler()
     }
 }

+ 1 - 11
deltachat-ios/AppTabBarController.swift

@@ -17,7 +17,7 @@ class AppTabBarController: UITabBarController {
         let contactImage = UIImage(named: "contacts")
         contactNavigationController.tabBarItem = UITabBarItem(title: "Contacts", image: contactImage, tag: 0)
 
-        let mailboxController = ChatViewController(chatId: Int(DC_CHAT_ID_DEADDROP))
+        let mailboxController = ChatViewController(chatId: Int(DC_CHAT_ID_DEADDROP), title: "Mailbox")
         mailboxController.disableWriting = true
         let mailboxNavigationController = NavigationController(rootViewController: mailboxController)
         let mailboxImage = UIImage(named: "message")
@@ -51,14 +51,4 @@ class AppTabBarController: UITabBarController {
 
         tabBar.tintColor = Constants.primaryColor
     }
-
-    /*
-     // MARK: - Navigation
-
-     // In a storyboard-based application, you will often want to do a little preparation before navigation
-     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
-     // Get the new view controller using segue.destination.
-     // Pass the selected object to the new view controller.
-     }
-     */
 }

+ 39 - 4
deltachat-ios/ChatViewController.swift

@@ -14,6 +14,7 @@ import UIKit
 
 class ChatViewController: MessagesViewController {
     let outgoingAvatarOverlap: CGFloat = 17.5
+    let loadCount = 30
 
     let chatId: Int
     let refreshControl = UIRefreshControl()
@@ -26,16 +27,19 @@ class ChatViewController: MessagesViewController {
 
     var previewView: UIView?
 
-    init(chatId: Int) {
+    init(chatId: Int, title: String? = nil) {
         self.chatId = chatId
         super.init(nibName: nil, bundle: nil)
+        if let title = title {
+            updateTitleView(title: title, subtitle: nil)
+        }
     }
 
     @objc
     func loadMoreMessages() {
         DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
             DispatchQueue.main.async {
-                self.messageList = self.getMessageIds(30, from: self.messageList.count) + self.messageList
+                self.messageList = self.getMessageIds(self.loadCount, from: self.messageList.count) + self.messageList
                 self.messagesCollectionView.reloadDataAndKeepOffset()
                 self.refreshControl.endRefreshing()
             }
@@ -45,7 +49,7 @@ class ChatViewController: MessagesViewController {
     func loadFirstMessages() {
         DispatchQueue.global(qos: .userInitiated).async {
             DispatchQueue.main.async {
-                self.messageList = self.getMessageIds(30)
+                self.messageList = self.getMessageIds(self.loadCount)
                 self.messagesCollectionView.reloadData()
                 self.refreshControl.endRefreshing()
                 self.messagesCollectionView.scrollToBottom(animated: false)
@@ -75,6 +79,9 @@ class ChatViewController: MessagesViewController {
             ids = Utils.copyAndFreeArrayWithLen(inputArray: c_messageIds, len: count)
         }
 
+        let markIds: [UInt32] = ids.map { return UInt32($0) }
+        dc_markseen_msgs(mailboxPointer, UnsafePointer(markIds), Int32(ids.count))
+
         return ids.map {
             MRMessage(id: $0)
         }
@@ -87,6 +94,16 @@ class ChatViewController: MessagesViewController {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
 
+        let cnt = Int(dc_get_fresh_msg_cnt(mailboxPointer, UInt32(chatId)))
+        logger.info("updating count for chat \(cnt)")
+        UIApplication.shared.applicationIconBadgeNumber = cnt
+
+        if #available(iOS 11.0, *) {
+            if disableWriting {
+                navigationController?.navigationBar.prefersLargeTitles = true
+            }
+        }
+
         let nc = NotificationCenter.default
         msgChangedObserver = nc.addObserver(forName: dc_notificationChanged,
                                             object: nil, queue: OperationQueue.main) {
@@ -121,6 +138,16 @@ class ChatViewController: MessagesViewController {
         }
     }
 
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+
+        if #available(iOS 11.0, *) {
+            if disableWriting {
+                navigationController?.navigationBar.prefersLargeTitles = false
+            }
+        }
+    }
+
     override func viewDidDisappear(_ animated: Bool) {
         super.viewDidDisappear(animated)
 
@@ -145,8 +172,13 @@ class ChatViewController: MessagesViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
 
+        if !MRConfig.configured {
+            // TODO: display message about nothing being configured
+            return
+        }
+
         let chat = MRChat(id: chatId)
-        updateTitleView(title: chat.name, subtitle: nil)
+        updateTitleView(title: chat.name, subtitle: chat.subtitle)
 
         configureMessageCollectionView()
         if !disableWriting {
@@ -349,6 +381,8 @@ extension ChatViewController: MessagesDataSource {
 
     func updateMessage(_ messageId: Int) {
         if let index = messageList.firstIndex(where: { $0.id == messageId }) {
+            dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(messageId)]), 1)
+
             messageList[index] = MRMessage(id: messageId)
             // Reload section to update header/footer labels
             messagesCollectionView.performBatchUpdates({
@@ -370,6 +404,7 @@ extension ChatViewController: MessagesDataSource {
     }
 
     func insertMessage(_ message: MRMessage) {
+        dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(message.id)]), 1)
         messageList.append(message)
         // Reload last section to update header/footer labels and insert a new one
         messagesCollectionView.performBatchUpdates({

+ 2 - 0
deltachat-ios/Constants.swift

@@ -27,4 +27,6 @@ struct Constants {
 
     static let defaultShadow = UIImage(color: UIColor(hexString: "ff2b82"), size: CGSize(width: 1, height: 1))
     static let onlineShadow = UIImage(color: UIColor(hexString: "3ed67e"), size: CGSize(width: 1, height: 1))
+
+    static let notificationIdentifier = "deltachat-ios-local-notifications"
 }

+ 0 - 322
deltachat-ios/CredentialsController.swift

@@ -1,322 +0,0 @@
-//
-//  CredentialsController.swift
-//  deltachat-ios
-//
-//  Created by Jonas Reinsch on 15.11.17.
-//  Copyright © 2017 Jonas Reinsch. All rights reserved.
-//
-
-import UIKit
-
-// valid security modes for IMAP and SMTP
-enum SecurityMode: String, CaseIterable {
-    case automatic = "Automatic"
-    case ssltls = "SSL/TLS"
-    case starttls = "STARTTLS"
-    case off = "Off"
-}
-
-typealias CredentialsModel = (
-    email: String,
-    password: String,
-    imapLoginName: String?,
-    imapServer: String?,
-    imapPort: String?,
-    imapSecurity: SecurityMode,
-    smtpLoginName: String?,
-    smtpPassword: String?,
-    smtpServer: String?,
-    smtpPort: String?,
-    smtpSecurity: SecurityMode
-)
-
-class CredentialsController: UITableViewController {
-    let emailCell = TextFieldCell.makeEmailCell()
-    let passwordCell = TextFieldCell.makePasswordCell()
-
-    let imapCellLoginName = TextFieldCell.makeConfigCell(label: "IMAP Login Name", placeholder: "Automatic")
-    let imapCellServer = TextFieldCell.makeConfigCell(label: "IMAP Server", placeholder: "Automatic")
-    let imapCellPort = TextFieldCell.makeConfigCell(label: "IMAP Port", placeholder: "Automatic")
-    let imapCellSecurity = UITableViewCell(style: UITableViewCell.CellStyle.value1, reuseIdentifier: nil)
-
-    let smtpCellLoginName = TextFieldCell.makeConfigCell(label: "SMTP Login Name", placeholder: "Automatic")
-    let smtpCellPassword = TextFieldCell.makeConfigCell(label: "SMTP Password", placeholder: "As above")
-    let smtpCellServer = TextFieldCell.makeConfigCell(label: "SMTP Server", placeholder: "Automatic")
-    let smtpCellPort = TextFieldCell.makeConfigCell(label: "SMTP Port", placeholder: "Automatic")
-    let smtpCellSecurity = UITableViewCell(style: UITableViewCell.CellStyle.value1, reuseIdentifier: nil)
-
-    var doneButton: UIBarButtonItem?
-    var advancedButton: UIBarButtonItem?
-    let progressBar = UIProgressView(progressViewStyle: .default)
-
-    func readyForLogin() -> Bool {
-        return Utils.isValid(model.email) && !model.password.isEmpty
-    }
-
-    var advancedMode = false {
-        didSet {
-            if advancedMode {
-                advancedButton?.title = "Standard"
-            } else {
-                advancedButton?.title = "Advanced"
-            }
-            tableView.reloadData()
-        }
-    }
-
-    var model: CredentialsModel = ("", "", nil, nil, nil, SecurityMode.automatic, nil, nil, nil, nil, SecurityMode.automatic) {
-        didSet {
-            if readyForLogin() {
-                doneButton?.isEnabled = true
-            } else {
-                doneButton?.isEnabled = false
-            }
-            smtpCellSecurity.detailTextLabel?.text = model.smtpSecurity.rawValue
-            imapCellSecurity.detailTextLabel?.text = model.imapSecurity.rawValue
-            logger.info("model: \(model)")
-        }
-    }
-
-    let cells: [UITableViewCell]
-    let isCancellable: Bool
-
-    init(isCancellable: Bool = false) {
-        cells = [emailCell, passwordCell]
-        self.isCancellable = isCancellable
-
-        super.init(style: .grouped)
-        doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(didPressSaveAccountButton))
-        doneButton?.isEnabled = false
-        advancedButton = UIBarButtonItem(title: "Advanced", style: .done, target: self, action: #selector(didPressAdvancedButton))
-
-        let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(didPressCancelButton))
-
-        if isCancellable {
-            navigationItem.rightBarButtonItems = [doneButton!, cancelButton]
-        } else {
-            navigationItem.rightBarButtonItem = doneButton
-        }
-
-        navigationItem.leftBarButtonItem = advancedButton
-
-        // FIXME: refactor: do not use target/action here for text field changes
-        //        but text field delegate
-        emailCell.textField.addTarget(self, action: #selector(emailTextChanged), for: UIControl.Event.editingChanged)
-        passwordCell.textField.addTarget(self, action: #selector(passwordTextChanged), for: UIControl.Event.editingChanged)
-        imapCellLoginName.textField.addTarget(self, action: #selector(imapLoginNameChanged), for: .editingChanged)
-        imapCellServer.textField.addTarget(self, action: #selector(imapServerChanged), for: .editingChanged)
-        imapCellPort.textField.addTarget(self, action: #selector(imapPortChanged), for: .editingChanged)
-
-        smtpCellLoginName.textField.addTarget(self, action: #selector(smtpLoginNamedChanged), for: .editingChanged)
-        smtpCellPassword.textField.addTarget(self, action: #selector(smtpPasswordChanged), for: .editingChanged)
-        smtpCellServer.textField.addTarget(self, action: #selector(smtpServerChanged), for: .editingChanged)
-        smtpCellPort.textField.addTarget(self, action: #selector(smtpPortChanged), for: .editingChanged)
-
-        emailCell.textField.textContentType = UITextContentType.emailAddress
-        emailCell.textField.delegate = self
-        passwordCell.textField.delegate = self
-        emailCell.textField.returnKeyType = .next
-        passwordCell.textField.returnKeyType = .done
-    }
-
-    override func viewDidAppear(_: Bool) {
-        emailCell.textField.becomeFirstResponder()
-    }
-
-    @objc func didPressCancelButton() {
-        dismiss(animated: false) {
-            AppDelegate.appCoordinator.setupInnerViewControllers()
-        }
-    }
-
-    @objc func didPressSaveAccountButton() {
-        let m = model
-        let a = advancedMode
-        dismiss(animated: true) {
-            initCore(withCredentials: true, advancedMode: a, model: m, cancellableCredentialsUponFailure: self.isCancellable)
-        }
-    }
-
-    @objc func didPressAdvancedButton() {
-        advancedMode = !advancedMode
-    }
-
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        title = "Settings"
-        navigationController?.navigationBar.prefersLargeTitles = true
-    }
-
-    override func numberOfSections(in _: UITableView) -> Int {
-        return advancedMode ? 3 : 1
-    }
-
-    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-        if section == 0 {
-            return cells.count
-        }
-        if section == 1 {
-            return 4
-        }
-        if section == 2 {
-            return 5
-        }
-        return 0 // should never happen
-    }
-
-    override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
-        if section == 1 {
-            return "IMAP"
-        }
-        if section == 2 {
-            return "SMTP"
-        }
-        return nil
-    }
-
-    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let isIMAP = indexPath.section == 1
-        let isSMTP = indexPath.section == 2
-        if (isIMAP && (indexPath.row == 3)) || (isSMTP && (indexPath.row == 4)) {
-            let actionSheet = UIAlertController(title: "Security", message: nil, preferredStyle: .actionSheet)
-            for securityMode in SecurityMode.allCases {
-                actionSheet.addAction(UIAlertAction(title: securityMode.rawValue, style: .default, handler: {
-                    [unowned self]
-                    _ in
-                    if isIMAP {
-                        self.model.imapSecurity = securityMode
-                    }
-                    if isSMTP {
-                        self.model.smtpSecurity = securityMode
-                    }
-                }))
-            }
-            actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in }))
-
-            if let popoverController = actionSheet.popoverPresentationController {
-                if let cell = tableView.cellForRow(at: indexPath) {
-                    popoverController.sourceView = cell.detailTextLabel
-                }
-            }
-            present(actionSheet, animated: true, completion: {})
-        }
-    }
-
-    override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let section = indexPath.section
-        let row = indexPath.row
-
-        if section == 0 {
-            return cells[row]
-        }
-
-        if section == 1 {
-            if row == 0 {
-                return imapCellLoginName
-            } else if row == 1 {
-                return imapCellServer
-            } else if row == 2 {
-                return imapCellPort
-//            } else if row == 3 {
-//                return imapCellSecurity
-            } else if row == 3 {
-                // FIXME: support iPad
-                imapCellSecurity.textLabel?.text = "Security:"
-                imapCellSecurity.selectionStyle = .none
-                imapCellSecurity.detailTextLabel?.text = model.imapSecurity.rawValue
-                return imapCellSecurity
-            }
-        }
-        if section == 2 {
-            if row == 0 {
-                return smtpCellLoginName
-            } else if row == 1 {
-                return smtpCellPassword
-            } else if row == 2 {
-                return smtpCellServer
-            } else if row == 3 {
-                return smtpCellPort
-            } else if row == 4 {
-                // FIXME: support iPad
-
-                smtpCellSecurity.selectionStyle = .none
-                smtpCellSecurity.textLabel?.text = "Security:"
-                smtpCellSecurity.detailTextLabel?.text = model.smtpSecurity.rawValue
-                return smtpCellSecurity
-            }
-        }
-        return UITableViewCell()
-    }
-
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
-    }
-}
-
-extension CredentialsController: UITextFieldDelegate {
-    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-        if textField == emailCell.textField {
-            if let emailText = emailCell.textField.text {
-                // only jump to next field if valid email
-                if Utils.isValid(emailText) {
-                    passwordCell.textField.becomeFirstResponder()
-                }
-            }
-        }
-        if textField == passwordCell.textField {
-            if readyForLogin() {
-                didPressSaveAccountButton()
-            }
-        }
-
-        return true
-    }
-}
-
-extension CredentialsController {
-    @objc func emailTextChanged() {
-        let emailText = emailCell.textField.text ?? ""
-
-        model.email = emailText
-    }
-
-    @objc func passwordTextChanged() {
-        let passwordText = passwordCell.textField.text ?? ""
-
-        model.password = passwordText
-    }
-
-    @objc func imapLoginNameChanged() {
-        model.imapLoginName = imapCellLoginName.textField.text
-    }
-
-    @objc func imapServerChanged() {
-        model.imapServer = imapCellServer.textField.text
-    }
-
-    @objc func imapPortChanged() {
-        model.imapPort = imapCellPort.textField.text
-    }
-
-    @objc func smtpLoginNamedChanged() {
-        model.smtpLoginName = smtpCellLoginName.textField.text
-    }
-
-    @objc func smtpPasswordChanged() {
-        model.smtpPassword = smtpCellPassword.textField.text
-    }
-
-    @objc func smtpServerChanged() {
-        model.smtpServer = smtpCellServer.textField.text
-    }
-
-    @objc func smtpPortChanged() {
-        model.smtpPort = smtpCellPort.textField.text
-    }
-}

+ 5 - 5
deltachat-ios/Info.plist

@@ -5,7 +5,7 @@
 	<key>CFBundleDevelopmentRegion</key>
 	<string>$(DEVELOPMENT_LANGUAGE)</string>
 	<key>CFBundleDisplayName</key>
-	<string>Delta Chat</string>
+	<string>Delta Chat Beta</string>
 	<key>CFBundleExecutable</key>
 	<string>$(EXECUTABLE_NAME)</string>
 	<key>CFBundleIdentifier</key>
@@ -26,6 +26,10 @@
 	<true/>
 	<key>NSCameraUsageDescription</key>
 	<string>Allowing access to the camera lets you take photos and videos.</string>
+	<key>NSMicrophoneUsageDescription</key>
+	<string>Allowing access to the microphone lets you record audio.</string>
+	<key>NSPhotoLibraryUsageDescription</key>
+	<string>Allowing access to the photo library allows you to upload images from it.</string>
 	<key>UIBackgroundModes</key>
 	<array>
 		<string>fetch</string>
@@ -44,8 +48,6 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
-	<key>NSMicrophoneUsageDescription</key>
-	<string>Allowing access to the microphone lets you record audio.</string>
 	<key>UISupportedInterfaceOrientations~ipad</key>
 	<array>
 		<string>UIInterfaceOrientationPortrait</string>
@@ -53,7 +55,5 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
-	<key>NSPhotoLibraryUsageDescription</key>
-	<string>Allowing access to the photo library allows you to upload images from it.</string>
 </dict>
 </plist>

+ 11 - 0
deltachat-ios/TopViews/ChatListController.swift

@@ -18,6 +18,7 @@ class ChatListController: UIViewController {
 
     var msgChangedObserver: Any?
     var incomingMsgObserver: Any?
+    var viewChatObserver: Any?
 
     var newButton: UIBarButtonItem!
 
@@ -63,6 +64,13 @@ class ChatListController: UIViewController {
             _ in
             self.getChatList()
         }
+
+        viewChatObserver = nc.addObserver(forName: dc_notificationViewChat, object: nil, queue: nil) {
+            notification in
+            if let chatId = notification.userInfo?["chat_id"] as? Int {
+                self.displayChatForId(chatId: chatId)
+            }
+        }
     }
 
     override func viewDidDisappear(_ animated: Bool) {
@@ -75,6 +83,9 @@ class ChatListController: UIViewController {
         if let incomingMsgObserver = self.incomingMsgObserver {
             nc.removeObserver(incomingMsgObserver)
         }
+        if let viewChatObserver = self.viewChatObserver {
+            nc.removeObserver(viewChatObserver)
+        }
     }
 
     override func viewDidLoad() {

+ 34 - 12
deltachat-ios/TopViews/ContactListController.swift

@@ -18,12 +18,32 @@ class ContactListController: UITableViewController {
         title = "Contacts"
         navigationController?.navigationBar.prefersLargeTitles = true
 
-        contactIds = Utils.getContactIds()
-
         tableView.rowHeight = 80
         tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
     }
 
+    private func getContactIds() {
+        contactIds = Utils.getContactIds()
+        tableView.reloadData()
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = true
+        }
+
+        getContactIds()
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+    }
+
     override func didReceiveMemoryWarning() {
         super.didReceiveMemoryWarning()
     }
@@ -46,19 +66,21 @@ class ContactListController: UITableViewController {
         let row = indexPath.row
         let contactRow = row
 
-        let contact = MRContact(id: contactIds[contactRow])
-        cell.nameLabel.text = contact.name
-        cell.emailLabel.text = contact.email
+        if contactRow < contactIds.count {
+            let contact = MRContact(id: contactIds[contactRow])
+            cell.nameLabel.text = contact.name
+            cell.emailLabel.text = contact.email
 
-        // TODO: provider a nice selection
-        cell.selectionStyle = .none
+            // TODO: provider a nice selection
+            cell.selectionStyle = .none
 
-        if let img = contact.profileImage {
-            cell.setImage(img)
-        } else {
-            cell.setBackupImage(name: contact.name, color: contact.color)
+            if let img = contact.profileImage {
+                cell.setImage(img)
+            } else {
+                cell.setBackupImage(name: contact.name, color: contact.color)
+            }
+            cell.setVerified(isVerified: contact.isVerified)
         }
-        cell.setVerified(isVerified: contact.isVerified)
         return cell
     }
 

+ 34 - 14
deltachat-ios/TopViews/ProfileViewController.swift

@@ -9,10 +9,26 @@
 import UIKit
 
 class ProfileViewController: UITableViewController {
-    var contact: MRContact {
+    var contact: MRContact? {
+        // This is nil if we do not have an account setup yet
+        if !MRConfig.configured {
+            return nil
+        }
         return MRContact(id: Int(DC_CONTACT_ID_SELF))
     }
 
+    var fingerprint: String? {
+        if !MRConfig.configured {
+            return nil
+        }
+
+        if let cString = dc_get_securejoin_qr(mailboxPointer, 0) {
+            return String(cString: cString)
+        }
+
+        return nil
+    }
+
     init() {
         super.init(style: .plain)
     }
@@ -64,19 +80,19 @@ class ProfileViewController: UITableViewController {
         let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
         if indexPath.section == 0 {
             if row == 0 {
-                if let fingerprint = dc_get_securejoin_qr(mailboxPointer, 0) {
+                if let fingerprint = self.fingerprint {
                     cell.textLabel?.text = "Fingerprint: \(fingerprint)"
                     cell.textLabel?.textAlignment = .center
                 }
             }
             if row == 1 {
-                if let fingerprint = dc_get_securejoin_qr(mailboxPointer, 0) {
+                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(
-                        String(cString: fingerprint),
+                        fingerprint,
                         foregroundColor: .darkText,
                         backgroundColor: .white
                     )
@@ -121,18 +137,22 @@ class ProfileViewController: UITableViewController {
         let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
         if section == 0 {
             let contactCell = ContactCell()
-            let name = MRConfig.displayname ?? contact.name
-            contactCell.backgroundColor = bg
-            contactCell.nameLabel.text = name
-            contactCell.emailLabel.text = contact.email
-            contactCell.darkMode = false
-            contactCell.selectionStyle = .none
-            if let img = contact.profileImage {
-                contactCell.setImage(img)
+            if let contact = self.contact {
+                let name = MRConfig.displayname ?? contact.name
+                contactCell.backgroundColor = bg
+                contactCell.nameLabel.text = name
+                contactCell.emailLabel.text = contact.email
+                contactCell.darkMode = false
+                contactCell.selectionStyle = .none
+                if let img = contact.profileImage {
+                    contactCell.setImage(img)
+                } else {
+                    contactCell.setBackupImage(name: name, color: contact.color)
+                }
+                contactCell.setVerified(isVerified: contact.isVerified)
             } else {
-                contactCell.setBackupImage(name: name, color: contact.color)
+                contactCell.nameLabel.text = "No Account set up"
             }
-            contactCell.setVerified(isVerified: contact.isVerified)
             return contactCell
         }
 

+ 91 - 35
deltachat-ios/TopViews/SettingsController.swift

@@ -66,6 +66,21 @@ internal final class SettingsViewController: QuickTableViewController {
         }
     }
 
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = true
+        }
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+    }
+
     private func setHudError(_ message: String?) {
         if let hud = self.backupHud {
             DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
@@ -93,6 +108,9 @@ internal final class SettingsViewController: QuickTableViewController {
                     }
                 )
 
+                self.setTable()
+                self.tableView.reloadData()
+
                 hud.dismiss(afterDelay: 1.0)
             }
         }
@@ -106,15 +124,17 @@ internal final class SettingsViewController: QuickTableViewController {
     }
 
     private func showBackupHud(_ text: String) {
-        let hud = JGProgressHUD(style: .dark)
-        hud.vibrancyEnabled = true
-        hud.indicatorView = JGProgressHUDPieIndicatorView()
+        DispatchQueue.main.async {
+            let hud = JGProgressHUD(style: .dark)
+            hud.vibrancyEnabled = true
+            hud.indicatorView = JGProgressHUDPieIndicatorView()
 
-        hud.detailTextLabel.text = "0% Complete"
-        hud.textLabel.text = text
-        hud.show(in: view)
+            hud.detailTextLabel.text = "0% Complete"
+            hud.textLabel.text = text
+            hud.show(in: self.view)
 
-        backupHud = hud
+            self.backupHud = hud
+        }
     }
 
     override func viewDidDisappear(_ animated: Bool) {
@@ -140,6 +160,8 @@ internal final class SettingsViewController: QuickTableViewController {
             TapActionRow(title: "Restore from backup", action: { [weak self] in self?.restoreBackup($0) }),
         ]
 
+        let deleteRow = TapActionRow(title: "Delete Account", action: { [weak self] in self?.deleteAccount($0) })
+
         if MRConfig.configured {
             backupRows.removeLast()
         }
@@ -189,6 +211,10 @@ internal final class SettingsViewController: QuickTableViewController {
                 title: "Backup",
                 rows: backupRows
             ),
+
+            Section(title: "Danger", rows: [
+                deleteRow,
+            ]),
         ]
     }
 
@@ -320,52 +346,82 @@ internal final class SettingsViewController: QuickTableViewController {
     }
 
     private func createBackup(_: Row) {
-        if let documents = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.delta.chat.ios")?.path {
-            logger.info("create backup in", documents)
-            dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents, nil)
+        // if let documents = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.delta.chat.ios")?.path {
+
+        let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
+        if documents.count > 0 {
+            logger.info("create backup in \(documents)")
             showBackupHud("Creating Backup")
+            DispatchQueue.main.async {
+                dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
+            }
+        } else {
+            logger.error("document directory not found")
         }
     }
 
     private func restoreBackup(_: Row) {
+        logger.info("restoring backup")
         if MRConfig.configured {
             return
         }
+        let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
+        if documents.count > 0 {
+            logger.info("looking for backup in: \(documents[0])")
 
-        if let documents = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.delta.chat.ios")?.path {
-            logger.info("looking for backup in", documents)
-
-            if let file = dc_imex_has_backup(mailboxPointer, documents) {
-                // Close as we are resetting the world
-                dc_close(mailboxPointer)
-
-                mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
-                guard mailboxPointer != nil else {
-                    fatalError("Error: dc_context_new returned nil")
-                }
-
-                let hud = JGProgressHUD(style: .dark)
-                hud.textLabel.text = "Restoring Backup"
-                hud.show(in: view)
+            if let file = dc_imex_has_backup(mailboxPointer, documents[0]) {
+                logger.info("restoring backup: \(String(cString: file))")
 
+                showBackupHud("Restoring Backup")
                 dc_imex(mailboxPointer, DC_IMEX_IMPORT_BACKUP, file, nil)
 
-                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
-                    self.dismiss(animated: true, completion: nil)
-                }))
-                present(alert, animated: true, completion: nil)
+                return
             }
+
+            let alert = UIAlertController(title: "Can not restore", message: "No Backup found", preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+                self.dismiss(animated: true, completion: nil)
+            }))
+            present(alert, animated: true, completion: nil)
+            return
         }
+
+        logger.error("no documents directory found")
     }
 
     private func configure(_: Row) {
         showBackupHud("Configuring account")
         dc_configure(mailboxPointer)
-        // refresh our view
-        setTable()
-        tableView.reloadData()
+    }
+
+    private func deleteAccount(_: Row) {
+        logger.info("deleting account")
+        let appDelegate = UIApplication.shared.delegate as! AppDelegate
+
+        let dbfile = appDelegate.dbfile()
+        let dburl = URL(fileURLWithPath: dbfile, isDirectory: false)
+        let alert = UIAlertController(title: "Delete Account", message: "Are you sure you wante to delete your account data?", preferredStyle: .alert)
+
+        alert.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { _ in
+            appDelegate.stop()
+            appDelegate.close()
+            do {
+                try FileManager.default.removeItem(at: dburl)
+            } catch {
+                logger.error("failed to delete db: \(error)")
+            }
+
+            appDelegate.open()
+            appDelegate.start()
+
+            // refresh our view
+            self.setTable()
+            self.tableView.reloadData()
+
+            self.dismiss(animated: true, completion: nil)
+        }))
+        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+
+        present(alert, animated: true, completion: nil)
     }
 }

+ 13 - 2
deltachat-ios/Wrapper.swift

@@ -224,7 +224,6 @@ class MRMessage: MessageType {
 
     init(id: Int) {
         messagePointer = dc_get_msg(mailboxPointer, UInt32(id))
-        dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(id)]), 1)
     }
 
     func summary(chars: Int) -> String? {
@@ -285,8 +284,20 @@ class MRChat {
         return nil
     }()
 
+    var subtitle: String? {
+        if let cString = dc_chat_get_subtitle(chatPointer) {
+            let str = String(cString: cString)
+            return str == "" ? nil : str
+        }
+        return nil
+    }
+
     init(id: Int) {
-        chatPointer = dc_get_chat(mailboxPointer, UInt32(id))
+        if let p = dc_get_chat(mailboxPointer, UInt32(id)) {
+            chatPointer = p
+        } else {
+            fatalError("Invalid chatID opened \(id)")
+        }
     }
 
     deinit {

+ 10 - 1
deltachat-ios/deltachat-ios.entitlements

@@ -1,5 +1,14 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
-<dict/>
+<dict>
+	<key>aps-environment</key>
+	<string>development</string>
+	<key>com.apple.developer.associated-domains</key>
+	<array/>
+	<key>com.apple.security.application-groups</key>
+	<array>
+		<string>group.delta.chat.ios</string>
+	</array>
+</dict>
 </plist>

+ 25 - 8
deltachat-ios/events.swift

@@ -15,6 +15,7 @@ let dc_notificationBackupProgress = Notification.Name(rawValue: "MrEventBackupPr
 let dc_notificationConfigureProgress = Notification.Name(rawValue: "MrEventConfigureProgress")
 let dc_notificationSecureJoinerProgress = Notification.Name(rawValue: "MrEventSecureJoinerProgress")
 let dc_notificationSecureInviterProgress = Notification.Name(rawValue: "MrEventSecureInviterProgress")
+let dc_notificationViewChat = Notification.Name(rawValue: "MrEventViewChat")
 
 @_silgen_name("callbackSwift")
 
@@ -82,9 +83,11 @@ public func callbackSwift(event: CInt, data1: CUnsignedLong, data2: CUnsignedLon
 
         let nc = NotificationCenter.default
         DispatchQueue.main.async {
-            nc.post(name: dc_notificationStateChanged,
-                    object: nil,
-                    userInfo: ["state": "offline"])
+            DispatchQueue.main.async {
+                nc.post(name: dc_notificationStateChanged,
+                        object: nil,
+                        userInfo: ["state": "offline"])
+            }
         }
     case DC_EVENT_IMAP_CONNECTED, DC_EVENT_SMTP_CONNECTED:
         logger.warning("network: \(String(cString: data2String))")
@@ -114,15 +117,29 @@ public func callbackSwift(event: CInt, data1: CUnsignedLong, data2: CUnsignedLon
         }
     case DC_EVENT_INCOMING_MSG:
         let nc = NotificationCenter.default
+        let userInfo = [
+            "message_id": Int(data2),
+            "chat_id": Int(data1),
+        ]
 
         DispatchQueue.main.async {
             nc.post(name: dc_notificationIncoming,
                     object: nil,
-                    userInfo: [
-                        "message_id": Int(data2),
-                        "chat_id": Int(data1),
-                        "date": Date(),
-            ])
+                    userInfo: userInfo)
+
+            let content = UNMutableNotificationContent()
+            let msg = MRMessage(id: Int(data2))
+            content.title = msg.fromContact.name
+            content.body = msg.summary(chars: 40) ?? ""
+            content.badge = 1
+            content.userInfo = userInfo
+            content.sound = .default
+
+            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
+
+            let request = UNNotificationRequest(identifier: Constants.notificationIdentifier, content: content, trigger: trigger)
+            UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
+            logger.info("notifications: added \(content)")
         }
     case DC_EVENT_SMTP_MESSAGE_SENT:
         logger.info("network: \(String(cString: data2String))")