Explorar el Código

refactor wrapper, get rid of DcContext.share, inject dcContext everywhere instead, change the way DcMsg and DcContact are created

cyberta hace 3 años
padre
commit
dc26f31ba1
Se han modificado 49 ficheros con 564 adiciones y 441 borrados
  1. 101 36
      DcCore/DcCore/DC/Wrapper.swift
  2. 176 167
      DcCore/DcCore/DC/events.swift
  3. 7 5
      DcCore/DcCore/Helper/DatabaseHelper.swift
  4. 3 4
      DcCore/DcCore/Helper/DcUtils.swift
  5. 1 1
      DcCore/DcCore/Views/InitialsBadge.swift
  6. 2 1
      DcShare/Controller/ChatListController.swift
  7. 3 2
      DcShare/Controller/SendingController.swift
  8. 6 3
      DcShare/Controller/ShareViewController.swift
  9. 23 22
      DcShare/Helper/ShareAttachment.swift
  10. 4 5
      DcShare/View/ChatListCell.swift
  11. 8 5
      deltachat-ios/AppDelegate.swift
  12. 2 2
      deltachat-ios/Chat/AudioController.swift
  13. 37 35
      deltachat-ios/Chat/ChatViewController.swift
  14. 5 3
      deltachat-ios/Chat/DraftModel.swift
  15. 2 2
      deltachat-ios/Chat/Views/Cells/AudioMessageCell.swift
  16. 12 12
      deltachat-ios/Chat/Views/Cells/BaseMessageCell.swift
  17. 2 2
      deltachat-ios/Chat/Views/Cells/FileTextCell.swift
  18. 2 2
      deltachat-ios/Chat/Views/Cells/ImageTextCell.swift
  19. 2 2
      deltachat-ios/Chat/Views/Cells/TextMessageCell.swift
  20. 1 1
      deltachat-ios/Chat/Views/DocumentPreview.swift
  21. 2 1
      deltachat-ios/Chat/Views/QuotePreview.swift
  22. 2 2
      deltachat-ios/Controller/AccountSetupController.swift
  23. 6 6
      deltachat-ios/Controller/AddGroupMembersViewController.swift
  24. 4 5
      deltachat-ios/Controller/BlockedContactsViewController.swift
  25. 4 4
      deltachat-ios/Controller/ChatListController.swift
  26. 11 11
      deltachat-ios/Controller/ContactDetailViewController.swift
  27. 2 2
      deltachat-ios/Controller/DocumentGalleryController.swift
  28. 1 1
      deltachat-ios/Controller/EditContactController.swift
  29. 1 1
      deltachat-ios/Controller/FullMessageViewController.swift
  30. 8 5
      deltachat-ios/Controller/GalleryViewController.swift
  31. 14 13
      deltachat-ios/Controller/GroupChatDetailViewController.swift
  32. 2 2
      deltachat-ios/Controller/GroupMembersViewController.swift
  33. 2 2
      deltachat-ios/Controller/MailboxViewController.swift
  34. 3 3
      deltachat-ios/Controller/NewChatViewController.swift
  35. 7 6
      deltachat-ios/Controller/NewGroupController.swift
  36. 4 2
      deltachat-ios/Controller/PreviewController.swift
  37. 4 4
      deltachat-ios/Controller/QrPageController.swift
  38. 3 3
      deltachat-ios/Controller/SettingsController.swift
  39. 2 2
      deltachat-ios/Controller/WelcomeViewController.swift
  40. 1 1
      deltachat-ios/Coordinator/AppCoordinator.swift
  41. 4 4
      deltachat-ios/Handler/ProgressAlertHandler.swift
  42. 1 1
      deltachat-ios/Helper/ImageFormat.swift
  43. 26 20
      deltachat-ios/Helper/NotificationManager.swift
  44. 2 2
      deltachat-ios/Model/GalleryItem.swift
  45. 9 10
      deltachat-ios/View/ContactCell.swift
  46. 1 1
      deltachat-ios/View/ContactDetailHeader.swift
  47. 11 6
      deltachat-ios/ViewModel/ChatListViewModel.swift
  48. 17 6
      deltachat-ios/ViewModel/ContactCellViewModel.swift
  49. 11 3
      deltachat-ios/ViewModel/ContactDetailViewModel.swift

+ 101 - 36
DcCore/DcCore/DC/Wrapper.swift

@@ -6,16 +6,16 @@ public class DcContext {
 
     /// TODO: THIS global instance should be replaced in the future, for example for a multi-account scenario,
     /// where we want to have more than one DcContext.
-    static let dcContext: DcContext = DcContext()
+    //static let dcContext: DcContext = DcContext()
     public var logger: Logger?
     var contextPointer: OpaquePointer?
     public var lastErrorString: String?
     public var lastWarningString: String = "" // temporary thing to get a grip on some weird errors
     public var maxConfigureProgress: Int = 0 // temporary thing to get a grip on some weird errors
 
-    private init() {
+    public init() {
     }
-
+    
     deinit {
         if contextPointer == nil { return } // avoid a warning about a "careless call"
         dc_context_unref(contextPointer)
@@ -23,8 +23,22 @@ public class DcContext {
     }
 
     /// Injection of DcContext is preferred over the usage of the shared variable
-    public static var shared: DcContext {
+/*    public static var shared: DcContext {
         return .dcContext
+    }*/
+
+    public func newMessage(viewType: Int32) -> DcMsg {
+        let messagePointer = dc_msg_new(contextPointer, viewType)
+        return DcMsg(pointer: messagePointer)
+    }
+
+    public func getMessage(id: Int) -> DcMsg {
+        let messagePointer = dc_get_msg(contextPointer, UInt32(id))
+        return DcMsg(pointer: messagePointer)
+    }
+
+    public func sendMessage(chatId: Int, message: DcMsg) {
+        dc_send_msg(contextPointer, UInt32(chatId), message.messagePointer)
     }
 
     // TODO: remove count and from parameters if we don't use it
@@ -63,6 +77,27 @@ public class DcContext {
         return DcUtils.copyAndFreeArray(inputArray: cContacts)
     }
 
+    public func getContact(id: Int) -> DcContact {
+        let contactPointer = dc_get_contact(contextPointer, UInt32(id))
+        return DcContact(contactPointer: contactPointer)
+    }
+
+    public func blockContact(id: Int) {
+        dc_block_contact(contextPointer, UInt32(id), 1)
+    }
+
+    public func unblockContact(id: Int) {
+        dc_block_contact(contextPointer, UInt32(id), 0)
+    }
+
+/*    public func getFromContact(messageId: Int): DcContact? {
+        DcContact(id: fromContactId)
+    }() */
+
+    public func isFromCurrentSender(message: DcMsg) -> Bool {
+        return message.fromContactId == getContact(id: Int(DC_CONTACT_ID_SELF)).id
+    }
+
     public func getBlockedContacts() -> [Int] {
         let cBlockedContacts = dc_get_blocked_contacts(contextPointer)
         return DcUtils.copyAndFreeArray(inputArray: cBlockedContacts)
@@ -406,6 +441,32 @@ public class DcContext {
         return DcProvider(dcProviderPointer)
     }
 
+    public func previousMediaURLs(messageId: Int, messageType: Int) -> [URL] {
+            var urls: [URL] = []
+            var prev: Int = Int(dc_get_next_media(contextPointer, UInt32(messageId), -1, Int32(messageType), 0, 0))
+            while prev != 0 {
+                let prevMessage = getMessage(id: prev)
+                if let url = prevMessage.fileURL {
+                    urls.insert(url, at: 0)
+                }
+                prev = Int(dc_get_next_media(contextPointer, UInt32(prevMessage.id), -1, Int32(prevMessage.type), 0, 0))
+            }
+            return urls
+        }
+
+        public func nextMediaURLs(messageId: Int, messageType: Int) -> [URL] {
+            var urls: [URL] = []
+            var next: Int = Int(dc_get_next_media(contextPointer, UInt32(messageId), 1, Int32(messageType), 0, 0))
+            while next != 0 {
+                let nextMessage = getMessage(id: next)
+                if let url = nextMessage.fileURL {
+                    urls.append(url)
+                }
+                next = Int(dc_get_next_media(contextPointer, UInt32(nextMessage.id), 1, Int32(nextMessage.type), 0, 0))
+            }
+            return urls
+        }
+
     public func imex(what: Int32, directory: String) {
         dc_imex(contextPointer, what, directory, nil)
     }
@@ -728,8 +789,8 @@ public class DcChat {
         return dc_chat_is_muted(chatPointer) != 0
     }
 
-    public var contactIds: [Int] {
-        return DcUtils.copyAndFreeArray(inputArray: dc_get_chat_contacts(DcContext.shared.contextPointer, UInt32(id)))
+    public func getContactIds(_ dcContext: DcContext) -> [Int] {
+        return DcUtils.copyAndFreeArray(inputArray: dc_get_chat_contacts(dcContext.contextPointer, UInt32(id)))
     }
 
     public lazy var profileImage: UIImage? = { [weak self] in
@@ -743,7 +804,7 @@ public class DcChat {
                 let image = UIImage(data: data)
                 return image
             } catch {
-                DcContext.shared.logger?.warning("failed to load image: \(filename), \(error)")
+                //DcContext.shared.logger?.warning("failed to load image: \(filename), \(error)")
                 return nil
             }
         }
@@ -793,15 +854,15 @@ public class DcMsg {
             DC_MSG_VIDEO,
             DC_MSG_FILE
      */
-    public init(viewType: Int32) {
+    /*public init(viewType: Int32) {
         messagePointer = dc_msg_new(DcContext.shared.contextPointer, viewType)
     }
 
     public init(id: Int) {
         messagePointer = dc_get_msg(DcContext.shared.contextPointer, UInt32(id))
-    }
+    }*/
 
-    init(pointer: OpaquePointer) {
+    init(pointer: OpaquePointer?) {
         messagePointer = pointer
     }
 
@@ -837,13 +898,13 @@ public class DcMsg {
         return Int(dc_msg_get_from_id(messagePointer))
     }
 
-    public lazy var fromContact: DcContact = {
+/*    public lazy var fromContact: DcContact = {
         DcContact(id: fromContactId)
     }()
 
     public var isFromCurrentSender: Bool {
         return fromContact.id == DcContact(id: Int(DC_CONTACT_ID_SELF)).id
-    }
+    } */
 
     public var chatId: Int {
         return Int(dc_msg_get_chat_id(messagePointer))
@@ -952,7 +1013,7 @@ public class DcMsg {
                     let image = UIImage(data: data)
                     return image
                 } catch {
-                    DcContext.shared.logger?.warning("failed to load image: \(path), \(error)")
+                    //DcContext.shared.logger?.warning("failed to load image: \(path), \(error)")
                     return nil
                 }
             }
@@ -1078,11 +1139,11 @@ public class DcMsg {
         return dc_msg_get_showpadlock(messagePointer) == 1
     }
 
-    public func sendInChat(id: Int) {
-        dc_send_msg(DcContext.shared.contextPointer, UInt32(id), messagePointer)
-    }
+/*    public func sendInChat(_ dcContext: DcContext, id: Int) {
+        dc_send_msg(dcContext.contextPointer, UInt32(id), messagePointer)
+    } */
 
-    public func previousMediaURLs() -> [URL] {
+/*    public func previousMediaURLs() -> [URL] {
         var urls: [URL] = []
         var prev: Int = Int(dc_get_next_media(DcContext.shared.contextPointer, UInt32(id), -1, Int32(type), 0, 0))
         while prev != 0 {
@@ -1106,14 +1167,18 @@ public class DcMsg {
             next = Int(dc_get_next_media(DcContext.shared.contextPointer, UInt32(nextMessage.id), 1, Int32(nextMessage.type), 0, 0))
         }
         return urls
-    }
+    }*/
 }
 
 public class DcContact {
     private var contactPointer: OpaquePointer?
 
-    public init(id: Int) {
+    /*public init(id: Int) {
         contactPointer = dc_get_contact(DcContext.shared.contextPointer, UInt32(id))
+    }*/
+
+    public init(contactPointer: OpaquePointer?) {
+        self.contactPointer = contactPointer
     }
 
     deinit {
@@ -1121,52 +1186,60 @@ public class DcContact {
     }
 
     public var displayName: String {
-        guard let cString = dc_contact_get_display_name(contactPointer) else { return "" }
+        guard let contactPointer = contactPointer,
+              let cString = dc_contact_get_display_name(contactPointer) else { return "" }
         let swiftString = String(cString: cString)
         dc_str_unref(cString)
         return swiftString
     }
 
     public var nameNAddr: String {
-        guard let cString = dc_contact_get_name_n_addr(contactPointer) else { return "" }
+        guard let contactPointer = contactPointer,
+              let cString = dc_contact_get_name_n_addr(contactPointer) else { return "" }
         let swiftString = String(cString: cString)
         dc_str_unref(cString)
         return swiftString
     }
 
     public var editedName: String {
-        guard let cString = dc_contact_get_name(contactPointer) else { return "" }
+        guard let contactPointer = contactPointer,
+              let cString = dc_contact_get_name(contactPointer) else { return "" }
         let swiftString = String(cString: cString)
         dc_str_unref(cString)
         return swiftString
     }
 
     public var authName: String {
-        guard let cString = dc_contact_get_auth_name(contactPointer) else { return "" }
+        guard let contactPointer = contactPointer,
+              let cString = dc_contact_get_auth_name(contactPointer) else { return "" }
         let swiftString = String(cString: cString)
         dc_str_unref(cString)
         return swiftString
     }
 
     public var email: String {
-        guard let cString = dc_contact_get_addr(contactPointer) else { return "" }
+        guard let contactPointer = contactPointer,
+              let cString = dc_contact_get_addr(contactPointer) else { return "" }
         let swiftString = String(cString: cString)
         dc_str_unref(cString)
         return swiftString
     }
 
     public var status: String {
-        guard let cString = dc_contact_get_status(contactPointer) else { return "" }
+        guard let contactPointer = contactPointer,
+              let cString = dc_contact_get_status(contactPointer) else { return "" }
         let swiftString = String(cString: cString)
         dc_str_unref(cString)
         return swiftString
     }
 
-    public var isVerified: Bool {
+    public var isVerified: Bool? {
+        guard let contactPointer = contactPointer else { return  nil }
         return dc_contact_is_verified(contactPointer) > 0
     }
 
-    public var isBlocked: Bool {
+    public var isBlocked: Bool? {
+    guard let contactPointer = contactPointer else { return  nil }
         return dc_contact_is_blocked(contactPointer) == 1
     }
 
@@ -1180,7 +1253,7 @@ public class DcContact {
                 let data = try Data(contentsOf: path)
                 return UIImage(data: data)
             } catch {
-                DcContext.shared.logger?.warning("failed to load image: \(filename), \(error)")
+                print("failed to load image: \(filename), \(error)")
                 return nil
             }
         }
@@ -1201,14 +1274,6 @@ public class DcContact {
     public var id: Int {
         return Int(dc_contact_get_id(contactPointer))
     }
-
-    public func block() {
-        dc_block_contact(DcContext.shared.contextPointer, UInt32(id), 1)
-    }
-
-    public func unblock() {
-        dc_block_contact(DcContext.shared.contextPointer, UInt32(id), 0)
-    }
 }
 
 public class DcLot {

+ 176 - 167
DcCore/DcCore/DC/events.swift

@@ -12,188 +12,197 @@ public let dcNotificationChatModified = Notification.Name(rawValue: "dcNotificat
 public let dcEphemeralTimerModified =  Notification.Name(rawValue: "dcEphemeralTimerModified")
 public let dcMsgsNoticed = Notification.Name(rawValue: "dcMsgsNoticed")
 
-public func handleEvent(event: DcEvent) {
-    let id = event.id
-    let data1 = event.data1Int
-    let data2 = event.data2Int
-
-    if id >= DC_EVENT_ERROR && id <= 499 {
-        let s = event.data2String
-        DcContext.shared.lastErrorString = s
-        DcContext.shared.logger?.error("event: \(s)")
-        return
+public class DcEventHandler {
+    let dcContext: DcContext
+
+    public init(dcContext: DcContext) {
+        self.dcContext = dcContext
     }
 
-    switch id {
-
-    case DC_EVENT_INFO:
-        let s = event.data2String
-        DcContext.shared.logger?.info("event: \(s)")
-
-    case DC_EVENT_WARNING:
-        let s = event.data2String
-        DcContext.shared.lastWarningString = s
-        DcContext.shared.logger?.warning("event: \(s)")
-
-    case DC_EVENT_CONFIGURE_PROGRESS:
-        DcContext.shared.maxConfigureProgress = max(DcContext.shared.maxConfigureProgress, Int(data1))
-        DcContext.shared.logger?.info("configure progress: \(Int(data1)) \(Int(data2))")
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            let done = Int(data1) == 1000
-
-            nc.post(
-                name: dcNotificationConfigureProgress,
-                object: nil,
-                userInfo: [
-                    "progress": Int(data1),
-                    "error": Int(data1) == 0,
-                    "done": done,
-                    "errorMessage": event.data2String,
-                ]
-            )
-
-            if done {
-                UserDefaults.standard.set(true, forKey: Constants.Keys.deltachatUserProvidedCredentialsKey)
-                UserDefaults.standard.synchronize()
-            }
-        }
+    public func handleEvent(event: DcEvent) {
+        let id = event.id
+        let data1 = event.data1Int
+        let data2 = event.data2Int
 
-    case DC_EVENT_IMEX_PROGRESS:
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcNotificationImexProgress,
-                object: nil,
-                userInfo: [
-                    "progress": Int(data1),
-                    "error": Int(data1) == 0,
-                    "done": Int(data1) == 1000,
-                    "errorMessage": DcContext.shared.lastErrorString as Any,
-                ]
-            )
+        if id >= DC_EVENT_ERROR && id <= 499 {
+            let s = event.data2String
+            dcContext.lastErrorString = s
+            dcContext.logger?.error("event: \(s)")
+            return
         }
 
-    case DC_EVENT_IMAP_CONNECTED, DC_EVENT_SMTP_CONNECTED:
-        DcContext.shared.logger?.warning("network: \(event.data2String)")
+        switch id {
 
-    case DC_EVENT_MSGS_CHANGED, DC_EVENT_MSG_READ, DC_EVENT_MSG_DELIVERED, DC_EVENT_MSG_FAILED:
-        DcContext.shared.logger?.info("change: \(id)")
+        case DC_EVENT_INFO:
+            let s = event.data2String
+            dcContext.logger?.info("event: \(s)")
 
-        let nc = NotificationCenter.default
+        case DC_EVENT_WARNING:
+            let s = event.data2String
+            dcContext.lastWarningString = s
+            dcContext.logger?.warning("event: \(s)")
 
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcNotificationChanged,
-                object: nil,
-                userInfo: [
-                    "message_id": Int(data2),
-                    "chat_id": Int(data1),
-                    "date": Date(),
-                ]
-            )
-        }
+        case DC_EVENT_CONFIGURE_PROGRESS:
+            dcContext.maxConfigureProgress = max(dcContext.maxConfigureProgress, Int(data1))
+            dcContext.logger?.info("configure progress: \(Int(data1)) \(Int(data2))")
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                let done = Int(data1) == 1000
 
-    case DC_EVENT_MSGS_NOTICED:
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcMsgsNoticed,
-                object: nil,
-                userInfo: [
-                    "chat_id": Int(data1),
-                ]
-            )
-        }
+                nc.post(
+                    name: dcNotificationConfigureProgress,
+                    object: nil,
+                    userInfo: [
+                        "progress": Int(data1),
+                        "error": Int(data1) == 0,
+                        "done": done,
+                        "errorMessage": event.data2String,
+                    ]
+                )
+
+                if done {
+                    UserDefaults.standard.set(true, forKey: Constants.Keys.deltachatUserProvidedCredentialsKey)
+                    UserDefaults.standard.synchronize()
+                }
+            }
 
-    case DC_EVENT_CHAT_MODIFIED:
-        DcContext.shared.logger?.info("chat modified: \(id)")
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcNotificationChatModified,
-                object: nil,
-                userInfo: [
-                    "chat_id": Int(data1),
-                ]
-            )
-        }
-    case DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED:
-        DcContext.shared.logger?.info("chat ephemeral timer modified: \(id)")
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcEphemeralTimerModified,
-                object: nil,
-                userInfo: nil
-            )
-        }
+        case DC_EVENT_IMEX_PROGRESS:
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async { [weak self] in
+                nc.post(
+                    name: dcNotificationImexProgress,
+                    object: nil,
+                    userInfo: [
+                        "progress": Int(data1),
+                        "error": Int(data1) == 0,
+                        "done": Int(data1) == 1000,
+                        "errorMessage": self?.dcContext.lastErrorString as Any,
+                    ]
+                )
+            }
+
+        case DC_EVENT_IMAP_CONNECTED, DC_EVENT_SMTP_CONNECTED:
+            dcContext.logger?.warning("network: \(event.data2String)")
+
+        case DC_EVENT_MSGS_CHANGED, DC_EVENT_MSG_READ, DC_EVENT_MSG_DELIVERED, DC_EVENT_MSG_FAILED:
+            dcContext.logger?.info("change: \(id)")
 
-    case DC_EVENT_INCOMING_MSG:
-        let nc = NotificationCenter.default
-        let userInfo = [
-            "message_id": Int(data2),
-            "chat_id": Int(data1),
-        ]
+            let nc = NotificationCenter.default
 
-        DcContext.shared.logger?.info("incoming message \(userInfo)")
-        DispatchQueue.main.async {
-            nc.post(name: dcNotificationIncoming,
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcNotificationChanged,
                     object: nil,
-                    userInfo: userInfo)
-        }
+                    userInfo: [
+                        "message_id": Int(data2),
+                        "chat_id": Int(data1),
+                        "date": Date(),
+                    ]
+                )
+            }
 
-    case DC_EVENT_SMTP_MESSAGE_SENT:
-        DcContext.shared.logger?.info("network: \(event.data2String)")
-
-    case DC_EVENT_MSG_DELIVERED:
-        DcContext.shared.logger?.info("message delivered: \(data1)-\(data2)")
-
-    case DC_EVENT_SECUREJOIN_INVITER_PROGRESS:
-        DcContext.shared.logger?.info("securejoin inviter progress \(data1)")
-
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcNotificationSecureInviterProgress,
-                object: nil,
-                userInfo: [
-                    "progress": Int(data2),
-                    "error": Int(data2) == 0,
-                    "done": Int(data2) == 1000,
-                ]
-            )
-        }
+        case DC_EVENT_MSGS_NOTICED:
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcMsgsNoticed,
+                    object: nil,
+                    userInfo: [
+                        "chat_id": Int(data1),
+                    ]
+                )
+            }
 
-    case DC_EVENT_SECUREJOIN_JOINER_PROGRESS:
-        DcContext.shared.logger?.info("securejoin joiner progress \(data1)")
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcNotificationSecureJoinerProgress,
-                object: nil,
-                userInfo: [
-                    "contact_id": Int(data1),
-                    "progress": Int(data2),
-                    "error": Int(data2) == 0,
-                    "done": Int(data2) == 1000,
-                ]
-            )
-        }
-    case DC_EVENT_CONTACTS_CHANGED:
-        DcContext.shared.logger?.info("contact changed: \(data1)")
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dcNotificationContactChanged,
-                object: nil,
-                userInfo: [
-                    "contact_id": Int(data1)
-                ]
-            )
-        }
+        case DC_EVENT_CHAT_MODIFIED:
+            dcContext.logger?.info("chat modified: \(id)")
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcNotificationChatModified,
+                    object: nil,
+                    userInfo: [
+                        "chat_id": Int(data1),
+                    ]
+                )
+            }
+        case DC_EVENT_CHAT_EPHEMERAL_TIMER_MODIFIED:
+            dcContext.logger?.info("chat ephemeral timer modified: \(id)")
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcEphemeralTimerModified,
+                    object: nil,
+                    userInfo: nil
+                )
+            }
+
+        case DC_EVENT_INCOMING_MSG:
+            let nc = NotificationCenter.default
+            let userInfo = [
+                "message_id": Int(data2),
+                "chat_id": Int(data1),
+            ]
+
+            dcContext.logger?.info("incoming message \(userInfo)")
+            DispatchQueue.main.async {
+                nc.post(name: dcNotificationIncoming,
+                        object: nil,
+                        userInfo: userInfo)
+            }
+
+        case DC_EVENT_SMTP_MESSAGE_SENT:
+            dcContext.logger?.info("network: \(event.data2String)")
+
+        case DC_EVENT_MSG_DELIVERED:
+            dcContext.logger?.info("message delivered: \(data1)-\(data2)")
 
-    default:
-        DcContext.shared.logger?.warning("unknown event: \(id)")
+        case DC_EVENT_SECUREJOIN_INVITER_PROGRESS:
+            dcContext.logger?.info("securejoin inviter progress \(data1)")
+
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcNotificationSecureInviterProgress,
+                    object: nil,
+                    userInfo: [
+                        "progress": Int(data2),
+                        "error": Int(data2) == 0,
+                        "done": Int(data2) == 1000,
+                    ]
+                )
+            }
+
+        case DC_EVENT_SECUREJOIN_JOINER_PROGRESS:
+            dcContext.logger?.info("securejoin joiner progress \(data1)")
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcNotificationSecureJoinerProgress,
+                    object: nil,
+                    userInfo: [
+                        "contact_id": Int(data1),
+                        "progress": Int(data2),
+                        "error": Int(data2) == 0,
+                        "done": Int(data2) == 1000,
+                    ]
+                )
+            }
+        case DC_EVENT_CONTACTS_CHANGED:
+            dcContext.logger?.info("contact changed: \(data1)")
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcNotificationContactChanged,
+                    object: nil,
+                    userInfo: [
+                        "contact_id": Int(data1)
+                    ]
+                )
+            }
+
+        default:
+            dcContext.logger?.warning("unknown event: \(id)")
+        }
     }
+
 }

+ 7 - 5
DcCore/DcCore/Helper/DatabaseHelper.swift

@@ -49,8 +49,10 @@ public class DatabaseHelper {
         return sharedDbBlobsDir
     }
 
-    public init() {
+    var dcContext: DcContext
 
+    public init(dcContext: DcContext) {
+        self.dcContext = dcContext
     }
 
     func clearDbBlobsDir(at path: String) {
@@ -65,7 +67,7 @@ public class DatabaseHelper {
                 try fileManager.removeItem(atPath: path)
             }
         } catch {
-            DcContext.shared.logger?.error("Could not clean shared blobs dir, it might be it didn't exist")
+            dcContext.logger?.error("Could not clean shared blobs dir, it might be it didn't exist")
         }
     }
 
@@ -75,7 +77,7 @@ public class DatabaseHelper {
             do {
                 try filemanager.removeItem(atPath: path)
             } catch {
-                DcContext.shared.logger?.error("Failed to delete db: \(error)")
+                dcContext.logger?.error("Failed to delete db: \(error)")
             }
         }
     }
@@ -92,7 +94,7 @@ public class DatabaseHelper {
                 clearDbBlobsDir(at: sharedDbBlobsDir)
                 try filemanager.moveItem(at: URL(fileURLWithPath: localDbBlobsDir), to: URL(fileURLWithPath: sharedDbBlobsDir))
             } catch let error {
-                DcContext.shared.logger?.error("Could not move db blobs directory to shared space: \(error.localizedDescription)")
+                dcContext.logger?.error("Could not move db blobs directory to shared space: \(error.localizedDescription)")
             }
         }
     }
@@ -105,7 +107,7 @@ public class DatabaseHelper {
               try filemanager.moveItem(at: URL(fileURLWithPath: localDbFile), to: URL(fileURLWithPath: sharedDbFile))
               moveBlobsFolder()
           } catch let error {
-              DcContext.shared.logger?.error("Could not update DB location. Share extension will probably not work. \n\(error.localizedDescription)")
+              dcContext.logger?.error("Could not update DB location. Share extension will probably not work. \n\(error.localizedDescription)")
               return localDbFile
           }
       }

+ 3 - 4
DcCore/DcCore/Helper/DcUtils.swift

@@ -14,9 +14,9 @@ public struct DcUtils {
         }
     }
 
-    public static func donateSendMessageIntent(chatId: Int) {
+    public static func donateSendMessageIntent(context: DcContext, chatId: Int) {
         if #available(iOS 13.0, *) {
-            let chat = DcContext.shared.getChat(chatId: chatId)
+            let chat = context.getChat(chatId: chatId)
             let groupName = INSpeakableString(spokenPhrase: chat.name)
 
             let sendMessageIntent = INSendMessageIntent(recipients: nil,
@@ -36,7 +36,7 @@ public struct DcUtils {
             let interaction = INInteraction(intent: sendMessageIntent, response: nil)
             interaction.donate(completion: { error in
                 if error != nil {
-                    DcContext.shared.logger?.error(error.debugDescription)
+                    context.logger?.error(error.debugDescription)
                 }
             })
         }
@@ -86,7 +86,6 @@ public struct DcUtils {
         }
 
         dc_array_unref(inputArray)
-        DcContext.shared.logger?.info("got: \(from) \(length) \(lenArray) - \(acc)")
 
         return acc
     }

+ 1 - 1
DcCore/DcCore/Views/InitialsBadge.swift

@@ -112,7 +112,7 @@ public class InitialsBadge: UIView {
         return !label.isHidden
     }
 
-    public func setColor(_ color: UIColor) {
+    public func setColor(_ color: UIColor?) {
         backgroundColor = color
     }
 

+ 2 - 1
DcShare/Controller/ChatListController.swift

@@ -128,7 +128,8 @@ class ChatListController: UITableViewController {
              if let chatId = data.chatId {
                 chatListDelegate?.onChatSelected(chatId: chatId)
             } else {
-                let chatId = dcContext.createChatByContactId(contactId: data.contactId)
+                let contact = data.contact
+                let chatId = dcContext.createChatByContactId(contactId: contact.id)
                 chatListDelegate?.onChatSelected(chatId: chatId)
             }
         default:

+ 3 - 2
DcShare/Controller/SendingController.swift

@@ -68,13 +68,14 @@ class SendingController: UIViewController {
     }
 
     private func sendMessage() {
-        DispatchQueue.global(qos: .utility).async {
+        DispatchQueue.global(qos: .utility).async { [weak self] in
+            guard let self = self else { return }
             for dcMsg in self.dcMsgs {
                 self.dcContext.sendMsgSync(chatId: self.chatId, msg: dcMsg)
             }
 
             if !self.dcContext.getChat(chatId: self.chatId).isSelfTalk {
-                DcUtils.donateSendMessageIntent(chatId: self.chatId)
+                DcUtils.donateSendMessageIntent(context: self.dcContext, chatId: self.chatId)
             }
             self.delegate?.onSendingAttemptFinished()
         }

+ 6 - 3
DcShare/Controller/ShareViewController.swift

@@ -31,11 +31,14 @@ class ShareViewController: SLComposeServiceViewController {
         }
     }
 
+    lazy var dbHelper: DatabaseHelper = {
+       return DatabaseHelper(dcContext: dcContext)
+    }()
+
     let logger = SimpleLogger()
-    let dcContext = DcContext.shared
+    let dcContext: DcContext = DcContext()
     var selectedChatId: Int?
     var selectedChat: DcChat?
-    let dbHelper = DatabaseHelper()
     var shareAttachment: ShareAttachment?
     var isAccountConfigured: Bool = true
     var isLoading: Bool = true
@@ -130,7 +133,7 @@ class ShareViewController: SLComposeServiceViewController {
                 if messages.count == 1 {
                     messages[0].text?.append(self.contentText)
                 } else {
-                    let message = DcMsg(viewType: DC_MSG_TEXT)
+                    let message = dcContext.newMessage(viewType: DC_MSG_TEXT)
                     message.text = self.contentText
                     messages.insert(message, at: 0)
                 }

+ 23 - 22
DcShare/Helper/ShareAttachment.swift

@@ -81,19 +81,19 @@ class ShareAttachment {
                 self.dcContext.logger?.debug("Unexpected data: \(type(of: data))")
             }
             if let result = result {
+                let msg = self.dcContext.newMessage(viewType: DC_MSG_GIF)
                 let path = ImageFormat.saveImage(image: result)
-                let msg = DcMsg(viewType: DC_MSG_GIF)
-                msg.setFile(filepath: path)
-                self.messages.append(msg)
-                self.delegate?.onAttachmentChanged()
-                if self.imageThumbnail == nil {
-                    self.imageThumbnail = result
-                    self.delegate?.onThumbnailChanged()
-                }
-                if let error = error {
-                    self.dcContext.logger?.error("Could not load share item as image: \(error.localizedDescription)")
+                    msg.setFile(filepath: path)
+                    self.messages.append(msg)
+                    self.delegate?.onAttachmentChanged()
+                    if self.imageThumbnail == nil {
+                        self.imageThumbnail = result
+                        self.delegate?.onThumbnailChanged()
+                    }
+                    if let error = error {
+                        self.dcContext.logger?.error("Could not load share item as image: \(error.localizedDescription)")
+                    }
                 }
-            }
         }
     }
 
@@ -113,19 +113,20 @@ class ShareAttachment {
             }
             if let result = result {
                 let path: String? = ImageFormat.saveImage(image: result)
-                var msg: DcMsg
+                var msg: DcMsg?
                 if result.sd_imageFormat == .webP {
-                    msg = DcMsg(viewType: DC_MSG_STICKER)
+                    msg = self.dcContext.newMessage(viewType: DC_MSG_STICKER)
                 } else {
-                    msg = DcMsg(viewType: DC_MSG_IMAGE)
+                    msg = self.dcContext.newMessage(viewType: DC_MSG_IMAGE)
                 }
-
-                msg.setFile(filepath: path)
-                self.messages.append(msg)
-                self.delegate?.onAttachmentChanged()
-                if self.imageThumbnail == nil {
-                    self.imageThumbnail = result
-                    self.delegate?.onThumbnailChanged()
+                if let msg = msg {
+                    msg.setFile(filepath: path)
+                    self.messages.append(msg)
+                    self.delegate?.onAttachmentChanged()
+                    if self.imageThumbnail == nil {
+                        self.imageThumbnail = result
+                        self.delegate?.onThumbnailChanged()
+                    }
                 }
             }
             if let error = error {
@@ -185,7 +186,7 @@ class ShareAttachment {
     }
 
     private func addDcMsg(url: URL, viewType: Int32) {
-        let msg = DcMsg(viewType: viewType)
+        let msg = dcContext.newMessage(viewType: viewType)
         msg.setFile(filepath: url.relativePath)
         self.messages.append(msg)
     }

+ 4 - 5
DcShare/View/ChatListCell.swift

@@ -84,7 +84,7 @@ class ChatListCell: UITableViewCell {
         avatar.setName("")
     }
 
-    private func setBackupImage(name: String, color: UIColor) {
+    private func setBackupImage(name: String, color: UIColor?) {
         avatar.setColor(color)
         avatar.setName(name)
     }
@@ -98,7 +98,7 @@ class ChatListCell: UITableViewCell {
         // subtitle
         switch cellViewModel.type {
         case .chat(let chatData):
-            let chat = DcContext.shared.getChat(chatId: chatData.chatId)
+            let chat = cellViewModel.dcContext.getChat(chatId: chatData.chatId)
             titleLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: titleLabel.font.pointSize)
             if let img = chat.profileImage {
                 resetBackupImage()
@@ -109,12 +109,11 @@ class ChatListCell: UITableViewCell {
             subtitleLabel.attributedText = nil
 
         case .contact(let contactData):
-            let contact = DcContact(id: contactData.contactId)
             titleLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: titleLabel.font.pointSize)
-            if let profileImage = contact.profileImage {
+            if let profileImage = contactData.contact.profileImage {
                 avatar.setImage(profileImage)
             } else {
-                setBackupImage(name: cellViewModel.title, color: contact.color)
+                setBackupImage(name: cellViewModel.title, color: contactData.contact.color)
             }
             subtitleLabel.attributedText = cellViewModel.subtitle.boldAt(indexes: cellViewModel.subtitleHighlightIndexes,
                                                                          fontSize: subtitleLabel.font.pointSize)

+ 8 - 5
deltachat-ios/AppDelegate.swift

@@ -11,7 +11,7 @@ let logger = SwiftyBeaver.self
 
 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
-    private let dcContext = DcContext.shared
+    private let dcContext = DcContext()
     var appCoordinator: AppCoordinator!
     var relayHelper: RelayHelper!
     var locationManager: LocationManager!
@@ -74,7 +74,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         appCoordinator = AppCoordinator(window: window, dcContext: dcContext)
         locationManager = LocationManager(context: dcContext)
         UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
-        notificationManager = NotificationManager()
+        notificationManager = NotificationManager(dcContext: dcContext)
         dcContext.maybeStartIo()
         setStockTranslations()
 
@@ -446,7 +446,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     // MARK: - misc.
 
     func openDatabase() {
-        guard let databaseLocation = DatabaseHelper().updateDatabaseLocation() else {
+        guard let databaseLocation = DatabaseHelper(dcContext: dcContext).updateDatabaseLocation() else {
             fatalError("Database could not be opened")
         }
         logger.info("open: \(databaseLocation)")
@@ -458,11 +458,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     }
 
     func installEventHandler() {
-        DispatchQueue.global(qos: .background).async {
+
+        DispatchQueue.global(qos: .background).async { [weak self] in
+            guard let self = self else { return }
+            let eventHandler = DcEventHandler(dcContext: self.dcContext)
             let eventEmitter = self.dcContext.getEventEmitter()
             while true {
                 guard let event = eventEmitter.getNextEvent() else { break }
-                handleEvent(event: event)
+                eventHandler.handleEvent(event: event)
             }
             logger.info("⬅️ event emitter finished")
         }

+ 2 - 2
deltachat-ios/Chat/AudioController.swift

@@ -87,7 +87,7 @@ open class AudioController: NSObject, AVAudioPlayerDelegate, AudioMessageCellDel
     }
     
     public func getAudioDuration(messageId: Int, successHandler: @escaping (Int, Double) -> Void) {
-        let message = DcMsg(id: messageId)
+        let message = dcContext.getMessage(id: messageId)
         if playingMessage?.id == messageId {
             // irgnore messages that are currently playing or recently paused
             return
@@ -122,7 +122,7 @@ open class AudioController: NSObject, AVAudioPlayerDelegate, AudioMessageCellDel
     }
 
     public func playButtonTapped(cell: AudioMessageCell, messageId: Int) {
-            let message = DcMsg(id: messageId)
+            let message = dcContext.getMessage(id: messageId)
             guard state != .stopped else {
                 // There is no audio sound playing - prepare to start playing for given audio message
                 playSound(for: message, in: cell)

+ 37 - 35
deltachat-ios/Chat/ChatViewController.swift

@@ -32,7 +32,7 @@ class ChatViewController: UITableViewController {
     }()
 
     lazy var draft: DraftModel = {
-        let draft = DraftModel(chatId: chatId)
+        let draft = DraftModel(dcContext: dcContext, chatId: chatId)
         return draft
     }()
 
@@ -116,7 +116,7 @@ class ChatViewController: UITableViewController {
         onPerform: { [weak self] indexPath in
                 guard let self = self else { return }
                 let id = self.messageIds[indexPath.row]
-                let msg = DcMsg(id: id)
+                let msg = self.dcContext.getMessage(id: id)
 
                 let pasteboard = UIPasteboard.general
                 if msg.type == DC_MSG_TEXT {
@@ -133,7 +133,7 @@ class ChatViewController: UITableViewController {
             action: #selector(BaseMessageCell.messageInfo),
             onPerform: { [weak self] indexPath in
                 guard let self = self else { return }
-                let msg = DcMsg(id: self.messageIds[indexPath.row])
+                let msg = self.dcContext.getMessage(id: self.messageIds[indexPath.row])
                 let msgViewController = MessageInfoViewController(dcContext: self.dcContext, message: msg)
                 if let ctrl = self.navigationController {
                     ctrl.pushViewController(msgViewController, animated: true)
@@ -150,7 +150,7 @@ class ChatViewController: UITableViewController {
                 DispatchQueue.main.async { [weak self] in
                     guard let self = self else { return }
                     self.tableView.becomeFirstResponder()
-                    let msg = DcMsg(id: self.messageIds[indexPath.row])
+                    let msg = self.dcContext.getMessage(id: self.messageIds[indexPath.row])
                     self.askToDeleteMessage(id: msg.id)
                 }
             }
@@ -162,7 +162,7 @@ class ChatViewController: UITableViewController {
             action: #selector(BaseMessageCell.messageForward),
             onPerform: { [weak self] indexPath in
                 guard let self = self else { return }
-                let msg = DcMsg(id: self.messageIds[indexPath.row])
+                let msg = self.dcContext.getMessage(id: self.messageIds[indexPath.row])
                 RelayHelper.sharedInstance.setForwardMessage(messageId: msg.id)
                 self.navigationController?.popViewController(animated: true)
             }
@@ -396,7 +396,7 @@ class ChatViewController: UITableViewController {
                 if self.chatId == ui["chat_id"] as? Int {
                     if let id = ui["message_id"] as? Int {
                         if id > 0 {
-                            self.insertMessage(DcMsg(id: id))
+                            self.insertMessage(self.dcContext.getMessage(id: id))
                         }
                     }
                 }
@@ -531,7 +531,7 @@ class ChatViewController: UITableViewController {
             let cell = tableView.dequeueReusableCell(withIdentifier: "info", for: indexPath) as? InfoMessageCell ?? InfoMessageCell()
             if messageIds.count > indexPath.row + 1 {
                 let nextMessageId = messageIds[indexPath.row + 1]
-                let nextMessage = DcMsg(id: nextMessageId)
+                let nextMessage = dcContext.getMessage(id: nextMessageId)
                 cell.update(text: DateUtils.getDateString(date: nextMessage.sentDate))
             } else {
                 cell.update(text: "ErrDaymarker")
@@ -545,7 +545,7 @@ class ChatViewController: UITableViewController {
             return cell
         }
         
-        let message = DcMsg(id: id)
+        let message = dcContext.getMessage(id: id)
         if message.isInfo {
             let cell = tableView.dequeueReusableCell(withIdentifier: "info", for: indexPath) as? InfoMessageCell ?? InfoMessageCell()
             cell.update(text: message.text)
@@ -571,15 +571,17 @@ class ChatViewController: UITableViewController {
             cell = tableView.dequeueReusableCell(withIdentifier: "text", for: indexPath) as? TextMessageCell ?? TextMessageCell()
         }
 
-        var showAvatar = isGroupChat && !message.isFromCurrentSender
+        let isFromCurrentSender = dcContext.isFromCurrentSender(message: message)
+        var showAvatar = isGroupChat && !isFromCurrentSender
         var showName = isGroupChat
         if message.overrideSenderName != nil {
-            showAvatar = !message.isFromCurrentSender
+            showAvatar = !isFromCurrentSender
             showName = true
         }
 
         cell.baseDelegate = self
-        cell.update(msg: message,
+        cell.update(dcContext: dcContext,
+                    msg: message,
                     messageStyle: configureMessageStyle(for: message, at: indexPath),
                     showAvatar: showAvatar,
                     showName: showName)
@@ -620,7 +622,7 @@ class ChatViewController: UITableViewController {
 
 
     override func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
-        if disableWriting || DcMsg(id: messageIds[indexPath.row]).isInfo {
+        if disableWriting || dcContext.getMessage(id: messageIds[indexPath.row]).isInfo {
             return nil
         }
 
@@ -645,7 +647,7 @@ class ChatViewController: UITableViewController {
     }
 
     func replyToMessage(at indexPath: IndexPath) {
-        let message = DcMsg(id: self.messageIds[indexPath.row])
+        let message = dcContext.getMessage(id: self.messageIds[indexPath.row])
         self.draft.setQuote(quotedMsg: message)
         self.configureDraftArea(draft: self.draft)
         self.messageInputBar.inputTextView.becomeFirstResponder()
@@ -683,7 +685,7 @@ class ChatViewController: UITableViewController {
             return
         }
         let messageId = messageIds[indexPath.row]
-        let message = DcMsg(id: messageId)
+        let message = dcContext.getMessage(id: messageId)
         if message.isSetupMessage {
             didTapAsm(msg: message, orgText: "")
         } else if message.type == DC_MSG_FILE ||
@@ -698,7 +700,7 @@ class ChatViewController: UITableViewController {
 
         var corners: UIRectCorner = []
 
-        if message.isFromCurrentSender {
+        if dcContext.isFromCurrentSender(message: message) {
             corners.formUnion(.topLeft)
             corners.formUnion(.bottomLeft)
             corners.formUnion(.topRight)
@@ -712,14 +714,14 @@ class ChatViewController: UITableViewController {
     }
 
     private func getBackgroundColor(for currentMessage: DcMsg) -> UIColor {
-        return currentMessage.isFromCurrentSender ? DcColors.messagePrimaryColor : DcColors.messageSecondaryColor
+        return dcContext.isFromCurrentSender(message: currentMessage) ? DcColors.messagePrimaryColor : DcColors.messageSecondaryColor
     }
 
     private func updateTitle(chat: DcChat) {
         let titleView =  ChatTitleView()
 
         var subtitle = "ErrSubtitle"
-        let chatContactIds = chat.contactIds
+        let chatContactIds = chat.getContactIds(dcContext)
         if chat.isMailinglist {
             subtitle = String.localized("mailing_list")
         } else if chat.isGroup {
@@ -729,7 +731,7 @@ class ChatViewController: UITableViewController {
         } else if chat.isSelfTalk {
             subtitle = String.localized("chat_self_talk_subtitle")
         } else if chatContactIds.count >= 1 {
-            subtitle = DcContact(id: chatContactIds[0]).email
+            subtitle = dcContext.getContact(id: chatContactIds[0]).email
         }
 
         titleView.updateTitleView(title: chat.name, subtitle: subtitle)
@@ -1056,7 +1058,7 @@ class ChatViewController: UITableViewController {
     private func showChatDetail(chatId: Int) {
         let chat = dcContext.getChat(chatId: chatId)
         if !chat.isGroup {
-            if let contactId = chat.contactIds.first {
+            if let contactId = chat.getContactIds(dcContext).first {
                 let contactDetailController = ContactDetailViewController(dcContext: dcContext, contactId: contactId)
                 navigationController?.pushViewController(contactDetailController, animated: true)
             }
@@ -1114,7 +1116,7 @@ class ChatViewController: UITableViewController {
     }
 
     private func showMediaGallery(currentIndex: Int, msgIds: [Int]) {
-        let betterPreviewController = PreviewController(type: .multi(msgIds, currentIndex))
+        let betterPreviewController = PreviewController(dcContext: dcContext, type: .multi(msgIds, currentIndex))
         let nav = UINavigationController(rootViewController: betterPreviewController)
         nav.modalPresentationStyle = .fullScreen
         navigationController?.present(nav, animated: true)
@@ -1171,7 +1173,7 @@ class ChatViewController: UITableViewController {
             reloadData()
         } else {
             // new outgoing message
-            let msg = DcMsg(id: messageId)
+            let msg = dcContext.getMessage(id: messageId)
             if msg.chatId == chatId {
                 if let newMsgMarkerIndex = messageIds.index(of: Int(DC_MSG_ID_MARKER1)) {
                     messageIds.remove(at: newMsgMarkerIndex)
@@ -1188,19 +1190,19 @@ class ChatViewController: UITableViewController {
         emptyStateView.isHidden = true
 
         reloadData()
-        if wasLastSectionVisible || message.isFromCurrentSender {
+        if wasLastSectionVisible || dcContext.isFromCurrentSender(message: message) {
             scrollToBottom(animated: true)
         }
     }
 
     private func sendTextMessage(text: String, quoteMessage: DcMsg?) {
         DispatchQueue.global().async {
-            let message = DcMsg(viewType: DC_MSG_TEXT)
+            let message = self.dcContext.newMessage(viewType: DC_MSG_TEXT)
             message.text = text
             if let quoteMessage = quoteMessage {
                 message.quoteMessage = quoteMessage
             }
-            message.sendInChat(id: self.chatId)
+            self.dcContext.sendMessage(chatId: self.chatId, message: message)
         }
     }
 
@@ -1262,20 +1264,20 @@ class ChatViewController: UITableViewController {
     }
 
     private func sendAttachmentMessage(viewType: Int32, filePath: String, message: String? = nil, quoteMessage: DcMsg? = nil) {
-        let msg = DcMsg(viewType: viewType)
+        let msg = dcContext.newMessage(viewType: viewType)
         msg.setFile(filepath: filePath)
         msg.text = (message ?? "").isEmpty ? nil : message
         if quoteMessage != nil {
             msg.quoteMessage = quoteMessage
         }
-        msg.sendInChat(id: self.chatId)
+        dcContext.sendMessage(chatId: self.chatId, message: msg)
     }
 
     private func sendVoiceMessage(url: NSURL) {
         DispatchQueue.global().async {
-            let msg = DcMsg(viewType: DC_MSG_VOICE)
+            let msg = self.dcContext.newMessage(viewType: DC_MSG_VOICE)
             msg.setFile(filepath: url.relativePath, mimeType: "audio/m4a")
-            msg.sendInChat(id: self.chatId)
+            self.dcContext.sendMessage(chatId: self.chatId, message: msg)
         }
     }
 
@@ -1286,7 +1288,7 @@ class ChatViewController: UITableViewController {
     }
 
     override func tableView(_ tableView: UITableView, shouldShowMenuForRowAt indexPath: IndexPath) -> Bool {
-        return !DcMsg(id: messageIds[indexPath.row]).isInfo 
+        return !dcContext.getMessage(id: messageIds[indexPath.row]).isInfo
     }
 
     override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
@@ -1315,7 +1317,7 @@ class ChatViewController: UITableViewController {
 
     func showMediaGalleryFor(indexPath: IndexPath) {
         let messageId = messageIds[indexPath.row]
-        let message = DcMsg(id: messageId)
+        let message = dcContext.getMessage(id: messageId)
         if message.type != DC_MSG_STICKER {
             showMediaGalleryFor(message: message)
         }
@@ -1414,7 +1416,7 @@ extension ChatViewController: BaseMessageCellDelegate {
         if handleUIMenu() || handleSelection(indexPath: indexPath) {
             return
         }
-        let msg = DcMsg(id: messageIds[indexPath.row])
+        let msg = dcContext.getMessage(id: messageIds[indexPath.row])
         let fullMessageViewController = FullMessageViewController(dcContext: dcContext, messageId: msg.id)
         navigationController?.pushViewController(fullMessageViewController, animated: true)
     }
@@ -1422,7 +1424,7 @@ extension ChatViewController: BaseMessageCellDelegate {
     @objc func quoteTapped(indexPath: IndexPath) {
         if handleSelection(indexPath: indexPath) { return }
         _ = handleUIMenu()
-        let msg = DcMsg(id: messageIds[indexPath.row])
+        let msg = dcContext.getMessage(id: messageIds[indexPath.row])
         if let quoteMsg = msg.quoteMessage {
             if self.chatId == quoteMsg.chatId {
                 scrollToMessage(msgId: quoteMsg.id)
@@ -1437,7 +1439,7 @@ extension ChatViewController: BaseMessageCellDelegate {
             return
         }
 
-        let message = DcMsg(id: messageIds[indexPath.row])
+        let message = dcContext.getMessage(id: messageIds[indexPath.row])
         if message.isSetupMessage {
             didTapAsm(msg: message, orgText: "")
         }
@@ -1482,7 +1484,7 @@ extension ChatViewController: BaseMessageCellDelegate {
     }
 
     @objc func avatarTapped(indexPath: IndexPath) {
-        let message = DcMsg(id: messageIds[indexPath.row])
+        let message = dcContext.getMessage(id: messageIds[indexPath.row])
         let contactDetailController = ContactDetailViewController(dcContext: dcContext, contactId: message.fromContactId)
         navigationController?.pushViewController(contactDetailController, animated: true)
     }
@@ -1577,7 +1579,7 @@ extension ChatViewController: DraftPreviewDelegate {
     func onAttachmentTapped() {
         if let attachmentPath = draft.attachment {
             let attachmentURL = URL(fileURLWithPath: attachmentPath, isDirectory: false)
-            let previewController = PreviewController(type: .single(attachmentURL))
+            let previewController = PreviewController(dcContext: dcContext, type: .single(attachmentURL))
             if #available(iOS 13.0, *), draft.viewType == DC_MSG_IMAGE || draft.viewType == DC_MSG_VIDEO {
                 previewController.setEditing(true, animated: true)
                 previewController.delegate = self

+ 5 - 3
deltachat-ios/Chat/DraftModel.swift

@@ -4,6 +4,7 @@ import DcCore
 
 public class DraftModel {
     var quoteMessage: DcMsg?
+    var dcContext: DcContext
     var quoteText: String?
     var text: String?
     var attachment: String?
@@ -12,8 +13,9 @@ public class DraftModel {
     let chatId: Int
     var isEditing: Bool = false
 
-    public init(chatId: Int) {
+    public init(dcContext: DcContext, chatId: Int) {
         self.chatId = chatId
+        self.dcContext = dcContext
     }
 
     public func parse(draftMsg: DcMsg?) {
@@ -30,7 +32,7 @@ public class DraftModel {
     public func setQuote(quotedMsg: DcMsg?) {
         if let quotedMsg = quotedMsg {
             // create a temporary draft to get the correct quoteText
-            let draftMessage = DcMsg(viewType: DC_MSG_TEXT)
+            let draftMessage = dcContext.newMessage(viewType: DC_MSG_TEXT)
             draftMessage.quoteMessage = quotedMsg
             self.quoteText = draftMessage.quoteText
             self.quoteMessage = quotedMsg
@@ -52,7 +54,7 @@ public class DraftModel {
             return
         }
 
-        let draftMessage = DcMsg(viewType: viewType ?? DC_MSG_TEXT)
+        let draftMessage = dcContext.newMessage(viewType: viewType ?? DC_MSG_TEXT)
         draftMessage.text = text
         if quoteMessage != nil {
             draftMessage.quoteMessage = quoteMessage

+ 2 - 2
deltachat-ios/Chat/Views/Cells/AudioMessageCell.swift

@@ -39,7 +39,7 @@ public class AudioMessageCell: BaseMessageCell {
         delegate?.playButtonTapped(cell: self, messageId: messageId)
     }
 
-    override func update(msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
+    override func update(dcContext: DcContext, msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
         messageId = msg.id
         if let text = msg.text {
             mainContentView.spacing = text.isEmpty ? 0 : 8
@@ -61,7 +61,7 @@ public class AudioMessageCell: BaseMessageCell {
         })
         
 
-        super.update(msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
+        super.update(dcContext: dcContext, msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
     }
 
     public override func prepareForReuse() {

+ 12 - 12
deltachat-ios/Chat/Views/Cells/BaseMessageCell.swift

@@ -272,8 +272,9 @@ public class BaseMessageCell: UITableViewCell {
     }
 
     // update classes inheriting BaseMessageCell first before calling super.update(...)
-    func update(msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
-        if msg.isFromCurrentSender {
+    func update(dcContext: DcContext, msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
+        let fromContact = dcContext.getContact(id: msg.fromContactId)
+        if dcContext.isFromCurrentSender(message: msg) {
             topLabel.text = msg.isForwarded ? String.localized("forwarded_message") : nil
             topLabel.textColor = msg.isForwarded ? DcColors.grayDateColor : DcColors.defaultTextColor
             leadingConstraint?.isActive = false
@@ -281,12 +282,11 @@ public class BaseMessageCell: UITableViewCell {
             trailingConstraint?.isActive = false
             leadingConstraintCurrentSender?.isActive = true
             trailingConstraintCurrentSender?.isActive = true
-
         } else {
             topLabel.text = msg.isForwarded ? String.localized("forwarded_message") :
-                showName ? msg.getSenderName(msg.fromContact, markOverride: true) : nil
+                showName ? msg.getSenderName(fromContact, markOverride: true) : nil
             topLabel.textColor = msg.isForwarded ? DcColors.grayDateColor :
-                showName ? msg.fromContact.color : DcColors.defaultTextColor
+                showName ? fromContact.color : DcColors.defaultTextColor
             leadingConstraintCurrentSender?.isActive = false
             trailingConstraintCurrentSender?.isActive = false
             if showName {
@@ -301,9 +301,9 @@ public class BaseMessageCell: UITableViewCell {
 
         if showAvatar {
             avatarView.isHidden = false
-            avatarView.setName(msg.getSenderName(msg.fromContact))
-            avatarView.setColor(msg.fromContact.color)
-            if let profileImage = msg.fromContact.profileImage {
+            avatarView.setName(msg.getSenderName(fromContact))
+            avatarView.setColor(fromContact.color)
+            if let profileImage = fromContact.profileImage {
                 avatarView.setImage(profileImage)
             }
         } else {
@@ -313,7 +313,7 @@ public class BaseMessageCell: UITableViewCell {
         isFullMessageButtonHidden = !msg.hasHtml
 
         messageBackgroundContainer.update(rectCorners: messageStyle,
-                                          color: getBackgroundColor(message: msg))
+                                          color: getBackgroundColor(dcContext: dcContext, message: msg))
 
         if !msg.isInfo {
             bottomLabel.attributedText = getFormattedBottomLine(message: msg)
@@ -330,7 +330,7 @@ public class BaseMessageCell: UITableViewCell {
                     quoteView.senderTitle.textColor = DcColors.grayDateColor
                     quoteView.citeBar.backgroundColor = DcColors.grayDateColor
                 } else {
-                    let contact = quoteMsg.fromContact
+                    let contact = dcContext.getContact(id: quoteMsg.fromContactId)
                     quoteView.senderTitle.text = quoteMsg.getSenderName(contact, markOverride: true)
                     quoteView.senderTitle.textColor = contact.color
                     quoteView.citeBar.backgroundColor = contact.color
@@ -371,11 +371,11 @@ public class BaseMessageCell: UITableViewCell {
             "\(getFormattedBottomLineAccessibilityString(message: message))"
     }
 
-    func getBackgroundColor(message: DcMsg) -> UIColor {
+    func getBackgroundColor(dcContext: DcContext, message: DcMsg) -> UIColor {
         var backgroundColor: UIColor
         if isTransparent {
             backgroundColor = UIColor.init(alpha: 0, red: 0, green: 0, blue: 0)
-        } else if message.isFromCurrentSender {
+        } else if dcContext.isFromCurrentSender(message:message) {
             backgroundColor =  DcColors.messagePrimaryColor
         } else {
             backgroundColor = DcColors.messageSecondaryColor

+ 2 - 2
deltachat-ios/Chat/Views/Cells/FileTextCell.swift

@@ -29,7 +29,7 @@ class FileTextCell: BaseMessageCell {
         fileView.prepareForReuse()
     }
 
-    override func update(msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
+    override func update(dcContext: DcContext, msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
         if let text = msg.text, !text.isEmpty {
             messageLabel.text = text
             spacer?.isActive = true
@@ -39,7 +39,7 @@ class FileTextCell: BaseMessageCell {
         
         fileView.configure(message: msg)
         accessibilityLabel = "\(String.localized("document")), \(fileView.configureAccessibilityLabel())"
-        super.update(msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
+        super.update(dcContext: dcContext, msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
     }
     
 }

+ 2 - 2
deltachat-ios/Chat/Views/Cells/ImageTextCell.swift

@@ -42,7 +42,7 @@ class ImageTextCell: BaseMessageCell {
         contentImageView.addGestureRecognizer(gestureRecognizer)
     }
 
-    override func update(msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
+    override func update(dcContext: DcContext, msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
         messageLabel.text = msg.text
         bottomCompactView = msg.type != DC_MSG_STICKER && msg.text?.isEmpty ?? true
         mainContentView.spacing = msg.text?.isEmpty ?? false ? 0 : 6
@@ -92,7 +92,7 @@ class ImageTextCell: BaseMessageCell {
                 setAspectRatioFor(message: msg, with: placeholderImage, isPlaceholder: true)
             }
         }
-        super.update(msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
+        super.update(dcContext: dcContext, msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
     }
 
     @objc func onImageTapped() {

+ 2 - 2
deltachat-ios/Chat/Views/Cells/TextMessageCell.swift

@@ -11,9 +11,9 @@ class TextMessageCell: BaseMessageCell {
         messageLabel.paddingTrailing = 12
     }
 
-    override func update(msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
+    override func update(dcContext: DcContext, msg: DcMsg, messageStyle: UIRectCorner, showAvatar: Bool, showName: Bool) {
         messageLabel.text = msg.text
-        super.update(msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
+        super.update(dcContext: dcContext, msg: msg, messageStyle: messageStyle, showAvatar: showAvatar, showName: showName)
     }
 
     override func prepareForReuse() {

+ 1 - 1
deltachat-ios/Chat/Views/DocumentPreview.swift

@@ -38,7 +38,7 @@ public class DocumentPreview: DraftPreview {
         if !draft.isEditing,
            draft.viewType == DC_MSG_FILE,
            let path = draft.attachment {
-            let tmpMsg = DcMsg(viewType: DC_MSG_FILE)
+            let tmpMsg = draft.dcContext.newMessage(viewType: DC_MSG_FILE)
             tmpMsg.setFile(filepath: path)
             tmpMsg.text = draft.text
             fileView.configure(message: tmpMsg)

+ 2 - 1
deltachat-ios/Chat/Views/QuotePreview.swift

@@ -39,10 +39,11 @@ public class QuotePreview: DraftPreview {
                     quoteView.senderTitle.textColor = DcColors.grayDateColor
                     quoteView.citeBar.backgroundColor = DcColors.grayDateColor
                 } else {
-                    let contact = quoteMessage.fromContact
+                    let contact = draft.dcContext.getContact(id: quoteMessage.fromContactId)
                     quoteView.senderTitle.text = quoteMessage.getSenderName(contact, markOverride: true)
                     quoteView.senderTitle.textColor = contact.color
                     quoteView.citeBar.backgroundColor = contact.color
+
                 }
             }
             accessibilityLabel = quoteView.configureAccessibilityLabel()

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

@@ -673,7 +673,7 @@ class AccountSetupController: UITableViewController, ProgressAlertHandler {
                     if let appDelegate = UIApplication.shared.delegate as? AppDelegate, appDelegate.reachability.connection == .none {
                         errorMessage = String.localized("login_error_no_internet_connection")
                     } else {
-                        errorMessage = "\(errorMessage ?? "no message")\n\n(warning=\(DcContext.shared.lastWarningString) (progress=\(DcContext.shared.maxConfigureProgress))"
+                        errorMessage = "\(errorMessage ?? "no message")\n\n(warning=\(self.dcContext.lastWarningString) (progress=\(self.dcContext.maxConfigureProgress))"
                     }
                     self.updateProgressAlert(error: errorMessage)
                 } else if ui["done"] as! Bool {
@@ -774,7 +774,7 @@ class AccountSetupController: UITableViewController, ProgressAlertHandler {
         alert.addAction(UIAlertAction(title: String.localized("delete_account"), style: .destructive, handler: { _ in
             self.dcContext.stopIo()
             appDelegate.closeDatabase()
-            DatabaseHelper().clearAccountData()
+            DatabaseHelper(dcContext: self.dcContext).clearAccountData()
             appDelegate.openDatabase()
             appDelegate.installEventHandler()
             self.dcContext.maybeStartIo()

+ 6 - 6
deltachat-ios/Controller/AddGroupMembersViewController.swift

@@ -24,7 +24,7 @@ class AddGroupMembersViewController: GroupMembersViewController {
 
     private lazy var chatMemberIds: [Int] = {
         if let chat = chat {
-            return chat.contactIds
+            return chat.getContactIds(dcContext)
         }
         return []
     }()
@@ -56,20 +56,20 @@ class AddGroupMembersViewController: GroupMembersViewController {
     }()
 
     // add members of new group, no chat object yet
-    init(preselected: Set<Int>, isVerified: Bool) {
-        super.init()
+    init(dcContext: DcContext, preselected: Set<Int>, isVerified: Bool) {
+        super.init(dcContext: dcContext)
         isVerifiedGroup = isVerified
         numberOfSections = sections.count
         selectedContactIds = preselected
     }
 
     // add members of existing group
-    init(chatId: Int) {
+    init(dcContext: DcContext, chatId: Int) {
         self.chatId = chatId
-        super.init()
+        super.init(dcContext: dcContext)
         isVerifiedGroup = chat?.isProtected ?? false
         numberOfSections = sections.count
-        selectedContactIds = Set(dcContext.getChat(chatId: chatId).contactIds)
+        selectedContactIds = Set(dcContext.getChat(chatId: chatId).getContactIds(dcContext))
     }
 
     required init?(coder _: NSCoder) {

+ 4 - 5
deltachat-ios/Controller/BlockedContactsViewController.swift

@@ -9,8 +9,8 @@ class BlockedContactsViewController: GroupMembersViewController, GroupMemberSele
         return view
     }()
 
-    override init() {
-        super.init()
+    override init(dcContext: DcContext) {
+        super.init(dcContext: dcContext)
         enableCheckmarks = false
     }
 
@@ -42,12 +42,11 @@ class BlockedContactsViewController: GroupMembersViewController, GroupMemberSele
     // MARK: - actions + updates
     func selected(contactId: Int, selected: Bool) {
         if !selected {
-            let dcContact = DcContact(id: contactId)
+            let dcContact = dcContext.getContact(id: contactId)
             let title = dcContact.displayName
             let alert = UIAlertController(title: title, message: String.localized("ask_unblock_contact"), preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("menu_unblock_contact"), style: .default, handler: { _ in
-                let contact = DcContact(id: contactId)
-                contact.unblock()
+                self.dcContext.unblockContact(id: contactId)
                 self.contactIds = self.dcContext.getBlockedContacts()
                 self.selectedContactIds = Set(self.contactIds)
                 self.tableView.reloadData()

+ 4 - 4
deltachat-ios/Controller/ChatListController.swift

@@ -77,7 +77,7 @@ class ChatListController: UITableViewController {
 
         // update messages - for new messages, do not reuse or modify strings but create new ones.
         // it is not needed to keep all past update messages, however, when deleted, also the strings should be deleted.
-        let msg = DcMsg(viewType: DC_MSG_TEXT)
+        let msg = dcContext.newMessage(viewType: DC_MSG_TEXT)
         msg.text = String.localized("update_1_20") + " https://delta.chat/en/2021-05-05-email-compat"
         dcContext.addDeviceMessage(label: "update_1_20b_ios", msg: msg)
 
@@ -292,7 +292,7 @@ class ChatListController: UITableViewController {
                 showChat(chatId: chatId, highlightedMsg: chatData.highlightMsgId)
             }
         case .contact(let contactData):
-            let contactId = contactData.contactId
+            let contactId = contactData.contact.id
             if let chatId = contactData.chatId {
                 showChat(chatId: chatId)
             } else {
@@ -408,7 +408,7 @@ class ChatListController: UITableViewController {
     }
 
     private func showDeaddropRequestAlert(msgId: Int) {
-        let dcMsg = DcMsg(id: msgId)
+        let dcMsg = dcContext.getMessage(id: msgId)
         let (title, startButton, blockButton) = MailboxViewController.deaddropQuestion(context: dcContext, msg: dcMsg)
         let alert = UIAlertController(title: title, message: nil, preferredStyle: .safeActionSheet)
         alert.addAction(UIAlertAction(title: startButton, style: .default, handler: { _ in
@@ -430,7 +430,7 @@ class ChatListController: UITableViewController {
     }
 
     private func askToChatWith(contactId: Int) {
-        let dcContact = DcContact(id: contactId)
+        let dcContact = dcContext.getContact(id: contactId)
         let alert = UIAlertController(
             title: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), dcContact.nameNAddr),
             message: nil,

+ 11 - 11
deltachat-ios/Controller/ContactDetailViewController.swift

@@ -33,8 +33,8 @@ class ContactDetailViewController: UITableViewController {
 
     private lazy var blockContactCell: ActionCell = {
         let cell = ActionCell()
-        cell.actionTitle = viewModel.contact.isBlocked ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
-        cell.actionColor = viewModel.contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
+        cell.actionTitle = (viewModel.contact.isBlocked ?? false) ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
+        cell.actionColor = (viewModel.contact.isBlocked ?? false) ? SystemColor.blue.uiColor : UIColor.red
         return cell
     }()
 
@@ -231,7 +231,7 @@ class ContactDetailViewController: UITableViewController {
             } else {
                 headerCell.setBackupImage(name: viewModel.contact.displayName, color: viewModel.contact.color)
             }
-            headerCell.setVerified(isVerified: viewModel.contact.isVerified)
+            headerCell.setVerified(isVerified: (viewModel.contact.isVerified ?? false))
         }
         headerCell.onAvatarTap = showContactAvatarIfNeeded
     }
@@ -297,8 +297,8 @@ class ContactDetailViewController: UITableViewController {
     }
 
     private func updateBlockContactCell() {
-        blockContactCell.actionTitle = viewModel.contact.isBlocked ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
-        blockContactCell.actionColor = viewModel.contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
+        blockContactCell.actionTitle = (viewModel.contact.isBlocked ?? false) ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
+        blockContactCell.actionColor = (viewModel.contact.isBlocked ?? false) ? SystemColor.blue.uiColor : UIColor.red
     }
 
 
@@ -360,10 +360,10 @@ class ContactDetailViewController: UITableViewController {
     }
 
     private func toggleBlockContact() {
-        if viewModel.contact.isBlocked {
+        if viewModel.contact.isBlocked ?? false {
             let alert = UIAlertController(title: String.localized("ask_unblock_contact"), message: nil, preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("menu_unblock_contact"), style: .default, handler: { _ in
-                self.viewModel.contact.unblock()
+                self.viewModel.unblockContact()
                 self.updateBlockContactCell()
             }))
             alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
@@ -371,7 +371,7 @@ class ContactDetailViewController: UITableViewController {
         } else {
             let alert = UIAlertController(title: String.localized("ask_block_contact"), message: nil, preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("menu_block_contact"), style: .destructive, handler: { _ in
-                self.viewModel.contact.block()
+                self.viewModel.blockContact()
                 self.updateBlockContactCell()
             }))
             alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
@@ -413,12 +413,12 @@ class ContactDetailViewController: UITableViewController {
         if viewModel.isSavedMessages {
             let chat = viewModel.context.getChat(chatId: viewModel.chatId)
             if let url = chat.profileImageURL {
-                let previewController = PreviewController(type: .single(url))
+                let previewController = PreviewController(dcContext: viewModel.context, type: .single(url))
                 previewController.customTitle = chat.name
                 present(previewController, animated: true, completion: nil)
             }
         } else if let url = viewModel.contact.profileImageURL {
-            let previewController = PreviewController(type: .single(url))
+            let previewController = PreviewController(dcContext: viewModel.context, type: .single(url))
             previewController.customTitle = viewModel.contact.displayName
             present(previewController, animated: true, completion: nil)
         }
@@ -429,7 +429,7 @@ class ContactDetailViewController: UITableViewController {
             return
         }
         viewModel.context.deleteChat(chatId: viewModel.chatId)
-        NotificationManager.removeNotificationsForChat(chatId: viewModel.chatId)
+        NotificationManager.removeNotificationsForChat(dcContext: viewModel.context, chatId: viewModel.chatId)
 
         // just pop to viewControllers - we've in chatlist or archive then
         // (no not use `navigationController?` here: popping self will make the reference becoming nil)

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

@@ -118,7 +118,7 @@ extension DocumentGalleryController: UITableViewDelegate, UITableViewDataSource
                 as? DocumentGalleryFileCell else {
             return UITableViewCell()
         }
-        let msg = DcMsg(id: fileMessageIds[indexPath.row])
+        let msg = dcContext.getMessage(id: fileMessageIds[indexPath.row])
         cell.update(msg: msg)
         return cell
     }
@@ -163,7 +163,7 @@ extension DocumentGalleryController {
         guard let index = fileMessageIds.index(of: msgId) else {
             return
         }
-        let previewController = PreviewController(type: .multi(fileMessageIds, index))
+        let previewController = PreviewController(dcContext: dcContext, type: .multi(fileMessageIds, index))
         present(previewController, animated: true, completion: nil)
     }
 

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

@@ -10,7 +10,7 @@ class EditContactController: NewContactController {
         super.init(dcContext: dcContext)
         title = String.localized("edit_contact")
 
-        let contact = DcContact(id: contactIdForUpdate)
+        let contact = dcContext.getContact(id: contactIdForUpdate)
 
         nameCell.textField.text = contact.editedName
         if !contact.authName.isEmpty { // else show string "Name" as set by super.init()

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

@@ -43,7 +43,7 @@ class FullMessageViewController: WebViewViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
 
-        var title = DcMsg(id: messageId).subject
+        var title = dcContext.getMessage(id: messageId).subject
         if title.isEmpty {
             title = String.localized("chat_input_placeholder")
         }

+ 8 - 5
deltachat-ios/Controller/GalleryViewController.swift

@@ -127,7 +127,7 @@ class GalleryViewController: UIViewController {
     private func updateFloatingTimeLabel() {
         if let indexPath = grid.indexPathsForVisibleItems.min() {
             let msgId = mediaMessageIds[indexPath.row]
-            let msg = DcMsg(id: msgId)
+            let msg = dcContext.getMessage(id: msgId)
             timeLabel.update(date: msg.sentDate)
         }
     }
@@ -155,7 +155,8 @@ class GalleryViewController: UIViewController {
 extension GalleryViewController: UICollectionViewDataSourcePrefetching {
     func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
         indexPaths.forEach { if items[$0.row] == nil {
-            let item = GalleryItem(msgId: mediaMessageIds[$0.row])
+            let message = dcContext.getMessage(id: mediaMessageIds[$0.row])
+            let item = GalleryItem(msg: message)
             items[$0.row] = item
         }}
     }
@@ -183,12 +184,14 @@ extension GalleryViewController: UICollectionViewDataSource, UICollectionViewDel
         var item: GalleryItem
         if let galleryItem = items[indexPath.row] {
             item = galleryItem
+            galleryCell.update(item: item)
         } else {
-            let galleryItem = GalleryItem(msgId: msgId)
+            let message = dcContext.getMessage(id: msgId)
+            let galleryItem = GalleryItem(msg: message)
             items[indexPath.row] = galleryItem
             item = galleryItem
+            galleryCell.update(item: item)
         }
-        galleryCell.update(item: item)
         UIMenuController.shared.setMenuVisible(false, animated: true)
         return galleryCell
     }
@@ -296,7 +299,7 @@ private extension GalleryViewController {
             return
         }
 
-        let previewController = PreviewController(type: .multi(mediaMessageIds, index))
+        let previewController = PreviewController(dcContext: dcContext, type: .multi(mediaMessageIds, index))
         previewController.delegate = self
         present(previewController, animated: true, completion: nil)
     }

+ 14 - 13
deltachat-ios/Controller/GroupChatDetailViewController.swift

@@ -35,11 +35,11 @@ class GroupChatDetailViewController: UIViewController {
     private let sections: [ProfileSections]
 
     private var currentUser: DcContact? {
-        let myId = groupMemberIds.filter { DcContact(id: $0).email == dcContext.addr }.first
+        let myId = groupMemberIds.filter { dcContext.getContact(id: $0).email == dcContext.addr }.first
         guard let currentUserId = myId else {
             return nil
         }
-        return DcContact(id: currentUserId)
+        return dcContext.getContact(id: currentUserId)
     }
 
     private var chatId: Int
@@ -93,7 +93,7 @@ class GroupChatDetailViewController: UIViewController {
         let header = ContactDetailHeader()
         header.updateDetails(
             title: chat.name,
-            subtitle: String.localizedStringWithFormat(String.localized("n_members"), chat.contactIds.count)
+            subtitle: String.localizedStringWithFormat(String.localized("n_members"), chat.getContactIds(dcContext).count)
         )
         if let img = chat.profileImage {
             header.setImage(img)
@@ -220,7 +220,7 @@ class GroupChatDetailViewController: UIViewController {
 
     // MARK: - update
     private func updateGroupMembers() {
-        groupMemberIds = chat.contactIds
+        groupMemberIds = chat.getContactIds(dcContext)
         tableView.reloadData()
     }
 
@@ -228,7 +228,7 @@ class GroupChatDetailViewController: UIViewController {
         groupHeader.updateDetails(
             title: chat.name,
             subtitle: chat.isMailinglist ?
-                nil : String.localizedStringWithFormat(String.localized("n_members"), chat.contactIds.count)
+                nil : String.localizedStringWithFormat(String.localized("n_members"), chat.getContactIds(dcContext).count)
         )
         if let img = chat.profileImage {
             groupHeader.setImage(img)
@@ -252,7 +252,7 @@ class GroupChatDetailViewController: UIViewController {
     private func toggleArchiveChat() {
         let archivedBefore = chat.isArchived
         if !archivedBefore {
-            NotificationManager.removeNotificationsForChat(chatId: chatId)
+            NotificationManager.removeNotificationsForChat(dcContext: dcContext, chatId: chatId)
         }
         dcContext.archiveChat(chatId: chat.id, archive: !archivedBefore)
         if archivedBefore {
@@ -277,11 +277,11 @@ class GroupChatDetailViewController: UIViewController {
     }
 
     private func showAddGroupMember(chatId: Int) {
-        let groupMemberViewController = AddGroupMembersViewController(chatId: chatId)
+        let groupMemberViewController = AddGroupMembersViewController(dcContext: dcContext, chatId: chatId)
         groupMemberViewController.onMembersSelected = { [weak self] (memberIds: Set<Int>) -> Void in
             guard let self = self else { return }
             let chat = self.dcContext.getChat(chatId: chatId)
-            var chatMembersToRemove = chat.contactIds
+            var chatMembersToRemove = chat.getContactIds(self.dcContext)
             chatMembersToRemove.removeAll(where: { memberIds.contains($0)})
             for contactId in chatMembersToRemove {
                 _ = self.dcContext.removeContactFromChat(chatId: chatId, contactId: contactId)
@@ -326,7 +326,7 @@ class GroupChatDetailViewController: UIViewController {
 
     private func deleteChat() {
         dcContext.deleteChat(chatId: chatId)
-        NotificationManager.removeNotificationsForChat(chatId: chatId)
+        NotificationManager.removeNotificationsForChat(dcContext: dcContext, chatId: chatId)
 
         // just pop to viewControllers - we've in chatlist or archive then
         // (no not use `navigationController?` here: popping self will make the reference becoming nil)
@@ -338,7 +338,7 @@ class GroupChatDetailViewController: UIViewController {
 
     private func showGroupAvatarIfNeeded() {
         if let url = chat.profileImageURL {
-            let previewController = PreviewController(type: .single(url))
+            let previewController = PreviewController(dcContext: dcContext, type: .single(url))
             previewController.customTitle = self.title
             present(previewController, animated: true, completion: nil)
         }
@@ -410,11 +410,12 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
                 break
             }
             let contactId: Int = getGroupMemberIdFor(row)
+            let contact = dcContext.getContact(id: contactId)
             let cellData = ContactCellData(
-                contactId: contactId,
+                contact: contact,
                 chatId: dcContext.getChatIdByContactIdOld(contactId)
             )
-            let cellViewModel = ContactCellViewModel(contactData: cellData)
+            let cellViewModel = ContactCellViewModel(dcContext: dcContext, contactData: cellData)
             contactCell.updateCell(cellViewModel: cellViewModel)
             return contactCell
         case .chatActions:
@@ -534,7 +535,7 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
     }
 
     private func getGroupMember(at row: Int) -> DcContact {
-        return DcContact(id: getGroupMemberIdFor(row))
+        return dcContext.getContact(id: getGroupMemberIdFor(row))
     }
 
     private func removeGroupMemberFromTableAt(_ indexPath: IndexPath) {

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

@@ -74,8 +74,8 @@ class GroupMembersViewController: UITableViewController {
         return emptySearchStateLabel.widthAnchor.constraint(equalTo: tableView.widthAnchor)
     }()
 
-    init() {
-        self.dcContext = DcContext.shared
+    init(dcContext: DcContext) {
+        self.dcContext = dcContext
         super.init(style: .grouped)
         hidesBottomBarWhenPushed = true
     }

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

@@ -41,7 +41,7 @@ class MailboxViewController: ChatViewController {
             let question = String.localizedStringWithFormat(String.localized("ask_show_mailing_list"), chat.name)
             return (question, String.localized("yes"), String.localized("block"))
         } else {
-            let contact = msg.fromContact
+            let contact = context.getContact(id: msg.fromContactId)
             let question = String.localizedStringWithFormat(String.localized("ask_start_chat_with"), contact.nameNAddr)
             return (question, String.localized("start_chat"), String.localized("menu_block_contact"))
         }
@@ -49,7 +49,7 @@ class MailboxViewController: ChatViewController {
 
     func askToChat(messageId: Int) {
         if handleUIMenu() { return }
-        let message = DcMsg(id: messageId)
+        let message = dcContext.getMessage(id: messageId)
         if message.isInfo {
             return
         }

+ 3 - 3
deltachat-ios/Controller/NewChatViewController.swift

@@ -52,7 +52,7 @@ class NewChatViewController: UITableViewController {
     }
 
     lazy var deviceContactHandler: DeviceContactsHandler = {
-        let handler = DeviceContactsHandler(dcContext: DcContext.shared)
+        let handler = DeviceContactsHandler(dcContext: dcContext)
         handler.contactListDelegate = self
         return handler
     }()
@@ -367,7 +367,7 @@ extension NewChatViewController: ContactListDelegate {
 // MARK: - alerts
 extension NewChatViewController {
     private func askToDeleteContact(contactId: Int, indexPath: IndexPath) {
-        let contact = DcContact(id: contactId)
+        let contact = dcContext.getContact(id: contactId)
         let alert = UIAlertController(
             title: String.localizedStringWithFormat(String.localized("ask_delete_contact"), contact.nameNAddr),
             message: nil,
@@ -388,7 +388,7 @@ extension NewChatViewController {
             self.dismiss(animated: true, completion: nil)
             self.showNewChat(contactId: contactId)
         } else {
-            let dcContact = DcContact(id: contactId)
+            let dcContact = dcContext.getContact(id: contactId)
             let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), dcContact.nameNAddr),
                                           message: nil,
                                           preferredStyle: .safeActionSheet)

+ 7 - 6
deltachat-ios/Controller/NewGroupController.swift

@@ -163,7 +163,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
         default:
             let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath)
             if let contactCell = cell as? ContactCell {
-                let contact = DcContact(id: groupContactIds[row])
+                let contact = dcContext.getContact(id: groupContactIds[row])
                 let displayName = contact.displayName
                 contactCell.titleLabel.text = displayName
                 contactCell.subtitleLabel.text = contact.email
@@ -240,7 +240,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
             let delete = UITableViewRowAction(style: .destructive, title: String.localized("remove_desktop")) { [weak self] _, indexPath in
                 guard let self = self else { return }
                 if self.groupChatId != 0,
-                    self.dcContext.getChat(chatId: self.groupChatId).contactIds.contains(self.groupContactIds[row]) {
+                   self.dcContext.getChat(chatId: self.groupChatId).getContactIds(self.dcContext).contains(self.groupContactIds[row]) {
                     let success = self.dcContext.removeContactFromChat(chatId: self.groupChatId, contactId: self.groupContactIds[row])
                     if success {
                         self.removeGroupContactFromList(at: indexPath)
@@ -297,7 +297,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     }
 
     func updateGroupContactIdsOnQRCodeInvite() {
-        for contactId in dcContext.getChat(chatId: groupChatId).contactIds {
+        for contactId in dcContext.getChat(chatId: groupChatId).getContactIds(dcContext) {
             contactIdsForGroup.insert(contactId)
         }
         groupContactIds = Array(contactIdsForGroup)
@@ -307,7 +307,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     func updateGroupContactIdsOnListSelection(_ members: Set<Int>) {
         if groupChatId != 0 {
             var members = members
-            for contactId in dcContext.getChat(chatId: groupChatId).contactIds {
+            for contactId in dcContext.getChat(chatId: groupChatId).getContactIds(dcContext) {
                 members.insert(contactId)
             }
         }
@@ -353,8 +353,9 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     }
 
     private func showAddMembers(preselectedMembers: Set<Int>, isVerified: Bool) {
-        let newGroupController = AddGroupMembersViewController(preselected: preselectedMembers,
-                                                                  isVerified: isVerified)
+        let newGroupController = AddGroupMembersViewController(dcContext: dcContext,
+                                                               preselected: preselectedMembers,
+                                                               isVerified: isVerified)
         newGroupController.onMembersSelected = { [weak self] (memberIds: Set<Int>) -> Void in
             guard let self = self else { return }
             self.updateGroupContactIdsOnListSelection(memberIds)

+ 4 - 2
deltachat-ios/Controller/PreviewController.swift

@@ -11,14 +11,16 @@ class PreviewController: QLPreviewController {
     let previewType: PreviewType
 
     var customTitle: String?
+    var dcContext: DcContext
 
     private lazy var doneButtonItem: UIBarButtonItem = {
         let button = UIBarButtonItem(title: String.localized("done"), style: .done, target: self, action: #selector(doneButtonPressed(_:)))
         return button
     }()
 
-    init(type: PreviewType) {
+    init(dcContext: DcContext, type: PreviewType) {
         self.previewType = type
+        self.dcContext = dcContext
         super.init(nibName: nil, bundle: nil)
         dataSource = self
         switch type {
@@ -64,7 +66,7 @@ extension PreviewController: QLPreviewControllerDataSource {
         case .single(let url):
             return PreviewItem(url: url, title: self.customTitle)
         case .multi(let msgIds, _):
-            let msg = DcMsg(id: msgIds[index])
+            let msg = dcContext.getMessage(id: msgIds[index])
             return PreviewItem(url: msg.fileURL, title: self.customTitle)
         }
     }

+ 4 - 4
deltachat-ios/Controller/QrPageController.swift

@@ -160,7 +160,7 @@ extension QrPageController: QrCodeReaderDelegate {
         let state = Int32(qrParsed.state)
         switch state {
         case DC_QR_ASK_VERIFYCONTACT:
-            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
+            let nameAndAddress = dcContext.getContact(id: qrParsed.id).nameNAddr
             joinSecureJoin(alertMessage: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), nameAndAddress), code: code)
 
         case DC_QR_ASK_VERIFYGROUP:
@@ -175,14 +175,14 @@ extension QrPageController: QrCodeReaderDelegate {
             present(alert, animated: true, completion: nil)
 
         case DC_QR_FPR_MISMATCH:
-            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
+            let nameAndAddress = dcContext.getContact(id: qrParsed.id).nameNAddr
             let msg = String.localizedStringWithFormat(String.localized("qrscan_fingerprint_mismatch"), nameAndAddress)
             let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
             alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default, handler: nil))
             present(alert, animated: true, completion: nil)
 
         case DC_QR_ADDR, DC_QR_FPR_OK:
-            let nameAndAddress = DcContact(id: qrParsed.id).nameNAddr
+            let nameAndAddress = dcContext.getContact(id: qrParsed.id).nameNAddr
             let msg = String.localizedStringWithFormat(String.localized(state==DC_QR_ADDR ? "ask_start_chat_with" : "qrshow_x_verified"), nameAndAddress)
             let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
             alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
@@ -277,7 +277,7 @@ extension QrPageController: QrCodeReaderDelegate {
                 let contactId = ui["contact_id"] as? Int {
                 self.progressAlert?.message = String.localizedStringWithFormat(
                     String.localized("qrscan_x_verified_introduce_myself"),
-                    DcContact(id: contactId).nameNAddr
+                    self.dcContext.getContact(id: contactId).nameNAddr
                 )
             }
         }

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

@@ -232,7 +232,7 @@ internal final class SettingsViewController: UITableViewController, ProgressAler
 
     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
-        addProgressAlertListener(progressName: dcNotificationImexProgress) { [weak self] in
+        addProgressAlertListener(dcContext: dcContext, progressName: dcNotificationImexProgress) { [weak self] in
             guard let self = self else { return }
             self.progressAlert?.dismiss(animated: true, completion: nil)
         }
@@ -323,7 +323,7 @@ internal final class SettingsViewController: UITableViewController, ProgressAler
             NotificationManager.removeAllNotifications()
         }
         UserDefaults.standard.synchronize()
-        NotificationManager.updateApplicationIconBadge(reset: !sender.isOn)
+        NotificationManager.updateApplicationIconBadge(dcContext: dcContext, reset: !sender.isOn)
     }
 
     @objc private func handleReceiptConfirmationToggle(_ sender: UISwitch) {
@@ -456,7 +456,7 @@ internal final class SettingsViewController: UITableViewController, ProgressAler
     }
 
     private func showBlockedContacts() {
-        let blockedContactsController = BlockedContactsViewController()
+        let blockedContactsController = BlockedContactsViewController(dcContext: dcContext)
         navigationController?.pushViewController(blockedContactsController, animated: true)
     }
 

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

@@ -113,7 +113,7 @@ class WelcomeViewController: UIViewController, ProgressAlertHandler {
     private func createAccountFromQRCode(qrCode: String) {
         let success = dcContext.setConfigFromQR(qrCode: qrCode)
         if success {
-            addProgressAlertListener(progressName: dcNotificationConfigureProgress, onSuccess: handleLoginSuccess)
+            addProgressAlertListener(dcContext: dcContext, progressName: dcNotificationConfigureProgress, onSuccess: handleLoginSuccess)
             showProgressAlert(title: String.localized("login_header"), dcContext: dcContext)
             dcContext.stopIo()
             dcContext.configure()
@@ -131,7 +131,7 @@ class WelcomeViewController: UIViewController, ProgressAlertHandler {
     }
 
     private func accountCreationErrorAlert() {
-        let title = DcContext.shared.lastErrorString ?? String.localized("error")
+        let title = dcContext.lastErrorString ?? String.localized("error")
         let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
         alert.addAction(UIAlertAction(title: String.localized("ok"), style: .default))
         present(alert, animated: true)

+ 1 - 1
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -111,7 +111,7 @@ class AppCoordinator {
         // the applicationIconBadgeNumber is remembered by the system even on reinstalls (just tested on ios 13.3.1),
         // to avoid appearing an old number of a previous installation, we reset the counter manually.
         // but even when this changes in ios, we need the reset as we allow account-deletion also in-app.
-        NotificationManager.updateApplicationIconBadge(reset: true)
+        NotificationManager.updateApplicationIconBadge(dcContext: dcContext, reset: true)
     }
 
     func presentTabBarController() {

+ 4 - 4
deltachat-ios/Handler/ProgressAlertHandler.swift

@@ -8,7 +8,7 @@ protocol ProgressAlertHandler: UIViewController {
     func updateProgressAlertValue(value: Int?)
     func updateProgressAlert(error: String?)
     func updateProgressAlertSuccess(completion: VoidFunction?)
-    func addProgressAlertListener(progressName: Notification.Name, onSuccess: @escaping VoidFunction)
+    func addProgressAlertListener(dcContext: DcContext, progressName: Notification.Name, onSuccess: @escaping VoidFunction)
 }
 
 extension ProgressAlertHandler {
@@ -63,7 +63,7 @@ extension ProgressAlertHandler {
         })
     }
 
-    func addProgressAlertListener(progressName: Notification.Name, onSuccess: @escaping VoidFunction) {
+    func addProgressAlertListener(dcContext: DcContext, progressName: Notification.Name, onSuccess: @escaping VoidFunction) {
         let nc = NotificationCenter.default
         progressObserver = nc.addObserver(
             forName: progressName,
@@ -73,10 +73,10 @@ extension ProgressAlertHandler {
             guard let self = self else { return }
             if let ui = notification.userInfo {
                 if ui["error"] as? Bool ?? false {
-                    DcContext.shared.maybeStartIo()
+                    dcContext.maybeStartIo()
                     self.updateProgressAlert(error: ui["errorMessage"] as? String)
                 } else if ui["done"] as? Bool ?? false {
-                    DcContext.shared.maybeStartIo()
+                    dcContext.maybeStartIo()
                     self.updateProgressAlertSuccess(completion: onSuccess)
                 } else {
                     self.updateProgressAlertValue(value: ui["progress"] as? Int)

+ 1 - 1
deltachat-ios/Helper/ImageFormat.swift

@@ -95,7 +95,7 @@ extension ImageFormat {
             try data.write(to: path)
             return path.relativePath
         } catch {
-            DcContext.shared.logger?.info(error.localizedDescription)
+            //DcContext.shared.logger?.info(error.localizedDescription)
             return nil
         }
     }

+ 26 - 20
deltachat-ios/Helper/NotificationManager.swift

@@ -8,16 +8,18 @@ public class NotificationManager {
     var incomingMsgObserver: NSObjectProtocol?
     var msgsNoticedObserver: NSObjectProtocol?
 
+    private var dcContext: DcContext
 
-    init() {
+    init(dcContext: DcContext) {
+        self.dcContext = dcContext
         initIncomingMsgsObserver()
         initMsgsNoticedObserver()
     }
 
-    public static func updateApplicationIconBadge(reset: Bool) {
+    public static func updateApplicationIconBadge(dcContext: DcContext, reset: Bool) {
         var unreadMessages = 0
         if !reset {
-            unreadMessages = DcContext.shared.getFreshMessages().count
+            unreadMessages = dcContext.getFreshMessages().count
         }
 
         DispatchQueue.main.async {
@@ -31,11 +33,11 @@ public class NotificationManager {
         nc.removeAllPendingNotificationRequests()
     }
     
-    public static func removeNotificationsForChat(chatId: Int) {
+    public static func removeNotificationsForChat(dcContext: DcContext, chatId: Int) {
         DispatchQueue.global(qos: .background).async {
-            NotificationManager.removePendingNotificationsFor(chatId: chatId)
-            NotificationManager.removeDeliveredNotificationsFor(chatId: chatId)
-            NotificationManager.updateApplicationIconBadge(reset: false)
+            NotificationManager.removePendingNotificationsFor(dcContext: dcContext, chatId: chatId)
+            NotificationManager.removeDeliveredNotificationsFor(dcContext: dcContext, chatId: chatId)
+            NotificationManager.updateApplicationIconBadge(dcContext: dcContext, reset: false)
         }
     }
 
@@ -51,21 +53,25 @@ public class NotificationManager {
                 logger.info("notification background task will end soon")
             }
 
-            DispatchQueue.global(qos: .background).async {
+            DispatchQueue.global(qos: .background).async { [weak self] in
+                guard let self = self else { return }
                 if let ui = notification.userInfo,
                    let chatId = ui["chat_id"] as? Int,
                    let messageId = ui["message_id"] as? Int,
                    !UserDefaults.standard.bool(forKey: "notifications_disabled") {
 
-                    NotificationManager.updateApplicationIconBadge(reset: false)
+                    NotificationManager.updateApplicationIconBadge(dcContext: self.dcContext, reset: false)
+
+                    let chat = self.dcContext.getChat(chatId: chatId)
 
-                    let chat = DcContext.shared.getChat(chatId: chatId)
                     if !chat.isMuted {
+                        let msg = self.dcContext.getMessage(id: messageId)
+                        let fromContact = self.dcContext.getContact(id: msg.fromContactId)
+                        let accountEmail = self.dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email
                         let content = UNMutableNotificationContent()
-                        let msg = DcMsg(id: messageId)
-                        content.title = chat.isGroup ? chat.name : msg.getSenderName(msg.fromContact)
+                        content.title = chat.isGroup ? chat.name : msg.getSenderName(fromContact)
                         content.body =  msg.summary(chars: 80) ?? ""
-                        content.subtitle = chat.isGroup ?  msg.getSenderName(msg.fromContact) : ""
+                        content.subtitle = chat.isGroup ?  msg.getSenderName(fromContact) : ""
                         content.userInfo = ui
                         content.sound = .default
 
@@ -86,7 +92,6 @@ public class NotificationManager {
                             }
                         }
                         let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
-                        let accountEmail = DcContact(id: Int(DC_CONTACT_ID_SELF)).email
                         if #available(iOS 12.0, *) {
                             content.threadIdentifier = "\(accountEmail)\(chatId)"
                         }
@@ -109,20 +114,21 @@ public class NotificationManager {
         msgsNoticedObserver =  NotificationCenter.default.addObserver(
             forName: dcMsgsNoticed,
             object: nil, queue: OperationQueue.main
-        ) { notification in
+        ) { [weak self] notification in
+            guard let self = self else { return }
             if !UserDefaults.standard.bool(forKey: "notifications_disabled"),
                let ui = notification.userInfo,
                let chatId = ui["chat_id"] as? Int {
-                NotificationManager.removeNotificationsForChat(chatId: chatId)
+                NotificationManager.removeNotificationsForChat(dcContext: self.dcContext, chatId: chatId)
             }
         }
     }
 
-    private static func removeDeliveredNotificationsFor(chatId: Int) {
+    private static func removeDeliveredNotificationsFor(dcContext: DcContext, chatId: Int) {
         var identifiers = [String]()
         let nc = UNUserNotificationCenter.current()
         nc.getDeliveredNotifications { notifications in
-            let accountEmail = DcContact(id: Int(DC_CONTACT_ID_SELF)).email
+            let accountEmail = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email
             for notification in notifications {
                 if !notification.request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty {
                     identifiers.append(notification.request.identifier)
@@ -132,11 +138,11 @@ public class NotificationManager {
         }
     }
 
-    private static func removePendingNotificationsFor(chatId: Int) {
+    private static func removePendingNotificationsFor(dcContext: DcContext, chatId: Int) {
         var identifiers = [String]()
         let nc = UNUserNotificationCenter.current()
         nc.getPendingNotificationRequests { notificationRequests in
-            let accountEmail = DcContact(id: Int(DC_CONTACT_ID_SELF)).email
+            let accountEmail = dcContext.getContact(id: Int(DC_CONTACT_ID_SELF)).email
             for request in notificationRequests {
                 if !request.identifier.containsExact(subSequence: "\(Constants.notificationIdentifier).\(accountEmail).\(chatId)").isEmpty {
                     identifiers.append(request.identifier)

+ 2 - 2
deltachat-ios/Model/GalleryItem.swift

@@ -44,8 +44,8 @@ class GalleryItem: ContextMenuItem {
         }
     }
 
-    init(msgId: Int) {
-        self.msg = DcMsg(id: msgId)
+    init(msg: DcMsg) {
+        self.msg = msg
         if let key = msg.fileURL?.absoluteString, let image = ThumbnailCache.shared.restoreImage(key: key) {
             self.thumbnailImage = image
         } else {

+ 9 - 10
deltachat-ios/View/ContactCell.swift

@@ -206,8 +206,8 @@ class ContactCell: UITableViewCell {
         ])
     }
 
-    func setVerified(isVerified: Bool) {
-        avatar.setVerified(isVerified)
+    func setVerified(isVerified: Bool?) {
+        avatar.setVerified(isVerified ?? false)
     }
 
     func setImage(_ img: UIImage) {
@@ -219,7 +219,7 @@ class ContactCell: UITableViewCell {
         avatar.setName("")
     }
 
-    func setBackupImage(name: String, color: UIColor) {
+    func setBackupImage(name: String, color: UIColor?) {
         avatar.setColor(color)
         avatar.setName(name)
     }
@@ -296,7 +296,7 @@ class ContactCell: UITableViewCell {
         case .deaddrop(let deaddropData):
             safe_assert(deaddropData.chatId == DC_CHAT_ID_DEADDROP)
             backgroundColor = DcColors.deaddropBackground
-            let contact = DcContact(id: DcMsg(id: deaddropData.msgId).fromContactId)
+            let contact = deaddropData.deaddropContact //DcContact(id: DcMsg(id: deaddropData.msgId).fromContactId)
             if let img = contact.profileImage {
                 resetBackupImage()
                 setImage(img)
@@ -307,7 +307,7 @@ class ContactCell: UITableViewCell {
             titleLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: titleLabel.font.pointSize)
 
         case .chat(let chatData):
-            let chat = DcContext.shared.getChat(chatId: chatData.chatId)
+            let chat = cellViewModel.dcContext.getChat(chatId: chatData.chatId)
 
             // text bold if chat contains unread messages - otherwise hightlight search results if needed
             if chatData.unreadMessages > 0 {
@@ -335,22 +335,21 @@ class ContactCell: UITableViewCell {
                                 isMuted: chat.isMuted)
 
         case .contact(let contactData):
-            let contact = DcContact(id: contactData.contactId)
             titleLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: titleLabel.font.pointSize)
-            if let profileImage = contact.profileImage {
+            if let profileImage = contactData.contact.profileImage {
                 avatar.setImage(profileImage)
             } else {
                 avatar.setName(cellViewModel.title)
-                avatar.setColor(contact.color)
+                avatar.setColor(contactData.contact.color)
             }
-            setVerified(isVerified: contact.isVerified)
+            setVerified(isVerified: contactData.contact.isVerified)
             setStatusIndicators(unreadCount: 0,
                                 status: 0,
                                 visibility: 0,
                                 isLocationStreaming: false,
                                 isMuted: false)
         case .profile:
-            let contact = DcContact(id: Int(DC_CONTACT_ID_SELF))
+            let contact = cellViewModel.dcContext.getContact(id: Int(DC_CONTACT_ID_SELF))
             titleLabel.text = cellViewModel.title
             subtitleLabel.text = cellViewModel.subtitle
             if let profileImage = contact.profileImage {

+ 1 - 1
deltachat-ios/View/ContactDetailHeader.swift

@@ -86,7 +86,7 @@ class ContactDetailHeader: UIView {
         avatar.setName("")
     }
 
-    func setBackupImage(name: String, color: UIColor) {
+    func setBackupImage(name: String, color: UIColor?) {
         avatar.setColor(color)
         avatar.setName(name)
     }

+ 11 - 6
deltachat-ios/ViewModel/ChatListViewModel.swift

@@ -177,14 +177,14 @@ class ChatListViewModel: NSObject {
 
     func deleteChat(chatId: Int) {
         dcContext.deleteChat(chatId: chatId)
-        NotificationManager.removeNotificationsForChat(chatId: chatId)
+        NotificationManager.removeNotificationsForChat(dcContext: dcContext, chatId: chatId)
     }
 
     func archiveChatToggle(chatId: Int) {
         let chat = dcContext.getChat(chatId: chatId)
         let isArchivedBefore = chat.isArchived
         if !isArchivedBefore {
-            NotificationManager.removeNotificationsForChat(chatId: chatId)
+            NotificationManager.removeNotificationsForChat(dcContext: dcContext, chatId: chatId)
         }
         dcContext.archiveChat(chatId: chatId, archive: !isArchivedBefore)
         updateChatList(notifyListener: false)
@@ -207,9 +207,14 @@ private extension ChatListViewModel {
         let chatId = list.getChatId(index: index)
         let summary = list.getSummary(index: index)
 
-
-        if let msgId = msgIdFor(row: index), chatId == DC_CHAT_ID_DEADDROP {
-            return ChatCellViewModel(dcContext: dcContext, deaddropCellData: DeaddropCellData(chatId: chatId, msgId: msgId, summary: summary))
+        if let msgId = msgIdFor(row: index),
+           chatId == DC_CHAT_ID_DEADDROP {
+            let message = dcContext.getMessage(id: msgId)
+            return ChatCellViewModel(dcContext: dcContext,
+                                     deaddropCellData: DeaddropCellData(chatId: chatId,
+                                                                        msgId: msgId,
+                                                                        summary: summary,
+                                                                        deaddropContact: dcContext.getContact(id: message.fromContactId)))
         }
 
         let chat = dcContext.getChat(chatId: chatId)
@@ -235,7 +240,7 @@ private extension ChatListViewModel {
     }
 
     func makeMessageCellViewModel(msgId: Int) -> AvatarCellViewModel {
-        let msg: DcMsg = DcMsg(id: msgId)
+        let msg: DcMsg = dcContext.getMessage(id: msgId)
         let chatId: Int = msg.chatId
         let chat: DcChat = dcContext.getChat(chatId: chatId)
         let summary: DcLot = msg.summary(chat: chat)

+ 17 - 6
deltachat-ios/ViewModel/ContactCellViewModel.swift

@@ -2,6 +2,7 @@ import Foundation
 import DcCore
 
 protocol AvatarCellViewModel {
+    var dcContext: DcContext { get }
     var type: CellModel { get }
     var title: String { get }
     var titleHighlightIndexes: [Int] { get }
@@ -17,7 +18,7 @@ enum CellModel {
 }
 
 struct ContactCellData {
-    let contactId: Int
+    let contact: DcContact
     let chatId: Int?
 }
 
@@ -32,11 +33,13 @@ struct DeaddropCellData {
     let chatId: Int
     let msgId: Int
     let summary: DcLot
+    let deaddropContact: DcContact
 }
 
 class ContactCellViewModel: AvatarCellViewModel {
 
     private let contact: DcContact
+    let dcContext: DcContext
 
     var type: CellModel
     var title: String {
@@ -53,15 +56,18 @@ class ContactCellViewModel: AvatarCellViewModel {
     var titleHighlightIndexes: [Int]
     var subtitleHighlightIndexes: [Int]
 
-    init(contactData: ContactCellData, titleHighlightIndexes: [Int] = [], subtitleHighlightIndexes: [Int] = []) {
+    init(dcContext: DcContext, contactData: ContactCellData, titleHighlightIndexes: [Int] = [], subtitleHighlightIndexes: [Int] = []) {
         type = CellModel.contact(contactData)
         self.titleHighlightIndexes = titleHighlightIndexes
         self.subtitleHighlightIndexes = subtitleHighlightIndexes
-        self.contact = DcContact(id: contactData.contactId)
+        self.contact = contactData.contact
+        self.dcContext = dcContext
     }
 }
 
 class ProfileViewModel: AvatarCellViewModel {
+
+    let dcContext: DcContext
     var type: CellModel {
         return CellModel.profile
     }
@@ -81,7 +87,8 @@ class ProfileViewModel: AvatarCellViewModel {
     }
 
     init(context: DcContext) {
-        contact = DcContact(id: Int(DC_CONTACT_ID_SELF))
+        self.dcContext = context
+        contact = context.getContact(id: Int(DC_CONTACT_ID_SELF))
         title = context.displayname ?? String.localized("pref_your_name")
         subtitle = context.addr ?? ""
     }
@@ -90,6 +97,7 @@ class ProfileViewModel: AvatarCellViewModel {
 class ChatCellViewModel: AvatarCellViewModel {
 
     private let chat: DcChat
+    let dcContext: DcContext
 
     private var summary: DcLot
 
@@ -119,6 +127,7 @@ class ChatCellViewModel: AvatarCellViewModel {
         self.subtitleHighlightIndexes = subtitleHighlightIndexes
         self.summary = chatData.summary
         self.chat = dcContext.getChat(chatId: chatData.chatId)
+        self.dcContext = dcContext
     }
 
     init(dcContext: DcContext, deaddropCellData cellData: DeaddropCellData) {
@@ -127,19 +136,21 @@ class ChatCellViewModel: AvatarCellViewModel {
         self.subtitleHighlightIndexes = []
         self.chat = dcContext.getChat(chatId: cellData.chatId)
         self.summary = cellData.summary
+        self.dcContext = dcContext
     }
 }
 
 extension ContactCellViewModel {
     static func make(contactId: Int, searchText: String?, dcContext: DcContext) -> ContactCellViewModel {
-        let contact = DcContact(id: contactId)
+        let contact = dcContext.getContact(id: contactId)
         let nameIndexes = contact.displayName.containsExact(subSequence: searchText)
         let emailIndexes = contact.email.containsExact(subSequence: searchText)
         let chatId: Int? = dcContext.getChatIdByContactIdOld(contactId)
             // contact contains searchText
         let viewModel = ContactCellViewModel(
+            dcContext: dcContext,
             contactData: ContactCellData(
-                contactId: contact.id,
+                contact: contact,
                 chatId: chatId
             ),
             titleHighlightIndexes: nameIndexes,

+ 11 - 3
deltachat-ios/ViewModel/ContactDetailViewModel.swift

@@ -31,7 +31,7 @@ class ContactDetailViewModel {
 
     // TODO: check if that is too inefficient (each bit read from contact, results in a database-query)
     var contact: DcContact {
-        return DcContact(id: contactId)
+        return context.getContact(id: contactId)
     }
 
     let chatId: Int
@@ -58,7 +58,7 @@ class ContactDetailViewModel {
         sections.append(.chatOptions)
 
         if !self.isSavedMessages {
-            let dcContact = DcContact(id: contactId)
+            let dcContact = context.getContact(id: contactId)
             if !dcContact.status.isEmpty {
                 sections.append(.statusArea)
             }
@@ -179,9 +179,17 @@ class ContactDetailViewModel {
         }
         let isArchivedBefore = chatIsArchived
         if !isArchivedBefore {
-            NotificationManager.removeNotificationsForChat(chatId: chatId)
+            NotificationManager.removeNotificationsForChat(dcContext: context, chatId: chatId)
         }
         context.archiveChat(chatId: chatId, archive: !isArchivedBefore)
         return chatIsArchived
     }
+
+    public func blockContact() {
+        context.blockContact(id: contact.id)
+    }
+
+    public func unblockContact() {
+        context.unblockContact(id: contact.id)
+    }
 }