소스 검색

Merge pull request #696 from deltachat/simplify-more

simplify
cyBerta 5 년 전
부모
커밋
28b661c6aa
26개의 변경된 파일567개의 추가작업 그리고 1203개의 파일을 삭제
  1. 0 1
      DcCore/DcCore/DC/events.swift
  2. 0 4
      deltachat-ios.xcodeproj/project.pbxproj
  3. 27 36
      deltachat-ios/AppDelegate.swift
  4. 34 13
      deltachat-ios/Controller/AccountSetupController.swift
  5. 29 21
      deltachat-ios/Controller/ChatListController.swift
  6. 69 15
      deltachat-ios/Controller/ChatViewController.swift
  7. 57 10
      deltachat-ios/Controller/ContactDetailViewController.swift
  8. 1 1
      deltachat-ios/Controller/EditContactController.swift
  9. 13 10
      deltachat-ios/Controller/EditGroupViewController.swift
  10. 5 9
      deltachat-ios/Controller/EditSettingsController.swift
  11. 79 19
      deltachat-ios/Controller/GroupChatDetailViewController.swift
  12. 9 9
      deltachat-ios/Controller/GroupMembersViewController.swift
  13. 1 1
      deltachat-ios/Controller/MailboxViewController.swift
  14. 37 19
      deltachat-ios/Controller/NewChatViewController.swift
  15. 9 11
      deltachat-ios/Controller/NewContactController.swift
  16. 44 14
      deltachat-ios/Controller/NewGroupController.swift
  17. 6 6
      deltachat-ios/Controller/ProfileInfoViewController.swift
  18. 0 14
      deltachat-ios/Controller/QrCodeReaderController.swift
  19. 16 5
      deltachat-ios/Controller/QrPageController.swift
  20. 49 9
      deltachat-ios/Controller/SettingsController.swift
  21. 37 48
      deltachat-ios/Controller/WelcomeViewController.swift
  22. 27 819
      deltachat-ios/Coordinator/AppCoordinator.swift
  23. 16 16
      deltachat-ios/Helper/MediaPicker.swift
  24. 0 10
      deltachat-ios/Helper/Protocols.swift
  25. 0 82
      deltachat-ios/NewGroupMemberChoiceController.swift
  26. 2 1
      deltachat-ios/ViewModel/ContactDetailViewModel.swift

+ 0 - 1
DcCore/DcCore/DC/events.swift

@@ -10,7 +10,6 @@ public let dcNotificationSecureInviterProgress = Notification.Name(rawValue: "Mr
 public let dcNotificationViewChat = Notification.Name(rawValue: "MrEventViewChat")
 public let dcNotificationContactChanged = Notification.Name(rawValue: "MrEventContactsChanged")
 public let dcNotificationChatModified = Notification.Name(rawValue: "dcNotificationChatModified")
-public let dcNotificationChatDeletedInChatDetail = Notification.Name(rawValue: "ChatDeletedInChatDetail")
 
 @_silgen_name("callbackSwift")
 

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

@@ -99,7 +99,6 @@
 		6795F63A82E94FF7CD2CEC0F /* Pods_deltachat_iosTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F7009234DB9408201A6CDCB /* Pods_deltachat_iosTests.framework */; };
 		7070FB9B2101ECBB000DC258 /* NewGroupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7070FB9A2101ECBB000DC258 /* NewGroupController.swift */; };
 		7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7092474020B3869500AF8799 /* ContactDetailViewController.swift */; };
-		70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */; };
 		70B8882E2091B8550074812E /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B8882D2091B8550074812E /* ContactCell.swift */; };
 		7837B64021E54DC600CDE126 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 7837B63F21E54DC600CDE126 /* .swiftlint.yml */; };
 		785BE16821E247F1003BE98C /* MessageInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785BE16721E247F1003BE98C /* MessageInfoViewController.swift */; };
@@ -344,7 +343,6 @@
 		6241BE1534A653E79AD5D01D /* Pods_deltachat_ios.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_deltachat_ios.framework; sourceTree = BUILT_PRODUCTS_DIR; };
 		7070FB9A2101ECBB000DC258 /* NewGroupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupController.swift; sourceTree = "<group>"; };
 		7092474020B3869500AF8799 /* ContactDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewController.swift; sourceTree = "<group>"; };
-		70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupMemberChoiceController.swift; sourceTree = "<group>"; };
 		70B8882D2091B8550074812E /* ContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; };
 		7837B63F21E54DC600CDE126 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = "<group>"; };
 		785BE16721E247F1003BE98C /* MessageInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInfoViewController.swift; sourceTree = "<group>"; };
@@ -685,7 +683,6 @@
 				7A9FB14A1FB061E2001FEA36 /* Assets.xcassets */,
 				7A9FB14C1FB061E2001FEA36 /* LaunchScreen.storyboard */,
 				7A9FB14F1FB061E2001FEA36 /* Info.plist */,
-				70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */,
 			);
 			path = "deltachat-ios";
 			sourceTree = "<group>";
@@ -1180,7 +1177,6 @@
 				3040F460234F419400FA34D5 /* BasicAudioController.swift in Sources */,
 				305962082346125100C80F33 /* MediaMessageSizeCalculator.swift in Sources */,
 				AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */,
-				70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */,
 				AE18F294228C602A0007B1BE /* SecuritySettingsController.swift in Sources */,
 				305961FD2346125100C80F33 /* TypingBubble.swift in Sources */,
 				305961D72346125100C80F33 /* MessageKit+Availability.swift in Sources */,

+ 27 - 36
deltachat-ios/AppDelegate.swift

@@ -20,40 +20,18 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     var appCoordinator: AppCoordinator!
     var relayHelper: RelayHelper!
     var locationManager: LocationManager!
-    // static let appCoordinatorDeprecated = AppCoordinatorDeprecated()
-    static var progress: Float = 0 // TODO: delete
     private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
-
     var reachability = Reachability()!
     var window: UIWindow?
-
     var state = ApplicationState.stopped
 
-    func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
-        // gets here when app returns from oAuth2-Setup process - the url contains the provided token
-        if let params = url.queryParameters, let token = params["code"] {
-            NotificationCenter.default.post(name: NSNotification.Name("oauthLoginApproved"), object: nil, userInfo: ["token": token])
-        }
-
-        // Hack to format url properly
-        let urlString = url.absoluteString
-                       .replacingOccurrences(of: "openpgp4fpr", with: "OPENPGP4FPR", options: .literal, range: nil)
-                       .replacingOccurrences(of: "%23", with: "#", options: .literal, range: nil)
-
-        self.appCoordinator.handleQRCode(urlString)
-        return true
-    }
-
     func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
-
+        // main()
         let console = ConsoleDestination()
         logger.addDestination(console)
         dcContext.logger = DcLogger()
-
         logger.info("launching")
 
-        // Override point for customization after application launch.
-
         window = UIWindow(frame: UIScreen.main.bounds)
         guard let window = window else {
             fatalError("window was nil in app delegate")
@@ -63,23 +41,36 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         } else {
             window.backgroundColor = UIColor.white
         }
-        // setup deltachat core context
-        //       - second param remains nil (user data for more than one mailbox)
-        open()
+
+        openDatabase()
         RelayHelper.setup(dcContext)
         appCoordinator = AppCoordinator(window: window, dcContext: dcContext)
-        appCoordinator.start()
         locationManager = LocationManager(context: dcContext)
         UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
-        start()
+        startThreads()
         setStockTranslations()
         return true
     }
 
+    func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
+        // gets here when app returns from oAuth2-Setup process - the url contains the provided token
+        if let params = url.queryParameters, let token = params["code"] {
+            NotificationCenter.default.post(name: NSNotification.Name("oauthLoginApproved"), object: nil, userInfo: ["token": token])
+        }
+
+        // Hack to format url properly
+        let urlString = url.absoluteString
+                       .replacingOccurrences(of: "openpgp4fpr", with: "OPENPGP4FPR", options: .literal, range: nil)
+                       .replacingOccurrences(of: "%23", with: "#", options: .literal, range: nil)
+
+        self.appCoordinator.handleQRCode(urlString)
+        return true
+    }
+
     func application(_: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
         logger.info("---- background-fetch ----")
 
-        start {
+        startThreads {
             // TODO: actually set the right value depending on if we found sth
             completionHandler(.newData)
         }
@@ -87,7 +78,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
     func applicationWillEnterForeground(_: UIApplication) {
         logger.info("---- foreground ----")
-        start()
+        startThreads()
     }
 
     func applicationDidEnterBackground(_: UIApplication) {
@@ -108,7 +99,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
                 // only need to do sth in the background
                 return
             } else if app.backgroundTimeRemaining < 10 {
-                self.stop()
+                self.stopThreads()
             } else {
                 self.maybeStop()
             }
@@ -117,13 +108,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
 
     func applicationWillTerminate(_: UIApplication) {
         logger.info("---- terminate ----")
-        close()
+        closeDatabase()
 
         reachability.stopNotifier()
         NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
     }
 
-    func open() {
+    func openDatabase() {
         guard let databaseLocation = DatabaseHelper().updateDatabaseLocation() else {
             fatalError("Database could not be opened")
         }
@@ -166,17 +157,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
         dcContext.setStockTranslation(id: DC_STR_DEVICE_MESSAGES, localizationKey: "device_talk")
     }
 
-    func stop() {
+    func stopThreads() {
         state = .background
         dcContext.interruptIdle()
     }
 
-    func close() {
+    func closeDatabase() {
         state = .stopped
         dcContext.closeDatabase()
     }
 
-    func start(_ completion: (() -> Void)? = nil) {
+    func startThreads(_ completion: (() -> Void)? = nil) {
         logger.info("---- start ----")
 
         if state == .running {

+ 34 - 13
deltachat-ios/Controller/AccountSetupController.swift

@@ -3,14 +3,12 @@ import UIKit
 import DcCore
 
 class AccountSetupController: UITableViewController, ProgressAlertHandler {
-
-    weak var coordinator: AccountSetupCoordinator?
-
     private let dcContext: DcContext
     private var skipOauth = false
     private var backupProgressObserver: Any?
     var progressObserver: Any?
     var onProgressSuccess: VoidFunction? // not needed here
+    var onLoginSuccess: (() -> Void)?
 
     private var oauth2Observer: Any?
 
@@ -484,11 +482,11 @@ class AccountSetupController: UITableViewController, ProgressAlertHandler {
         case tagAdvancedCell:
             toggleAdvancedSection()
         case tagImapSecurityCell:
-            coordinator?.showImapSecurityOptions()
+            showImapSecurityOptions()
         case tagSmtpSecurityCell:
-            coordinator?.showSmptpSecurityOptions()
+            showSmptpSecurityOptions()
         case tagCertCheckCell:
-            coordinator?.showCertCheckOptions()
+            showCertCheckOptions()
         default:
             break
         }
@@ -780,11 +778,11 @@ class AccountSetupController: UITableViewController, ProgressAlertHandler {
             preferredStyle: .safeActionSheet)
 
         alert.addAction(UIAlertAction(title: String.localized("delete_account"), style: .destructive, handler: { _ in
-            appDelegate.stop()
-            appDelegate.close()
+            appDelegate.stopThreads()
+            appDelegate.closeDatabase()
             DatabaseHelper().clearAccountData()
-            appDelegate.open()
-            appDelegate.start()
+            appDelegate.openDatabase()
+            appDelegate.startThreads()
             appDelegate.appCoordinator.presentWelcomeController()
         }))
         alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel))
@@ -796,10 +794,10 @@ class AccountSetupController: UITableViewController, ProgressAlertHandler {
         let appDelegate = UIApplication.shared.delegate as! AppDelegate
         appDelegate.registerForPushNotifications()
         initSelectionCells();
-        if let onLoginSuccess = self.coordinator?.onLoginSuccess {
+        if let onLoginSuccess = self.onLoginSuccess {
             onLoginSuccess()
         } else {
-            self.coordinator?.navigateBack()
+            navigationController?.popViewController(animated: true)
         }
     }
 
@@ -827,7 +825,7 @@ class AccountSetupController: UITableViewController, ProgressAlertHandler {
         guard let provider = provider else {
             return
         }
-        coordinator?.openProviderInfo(provider: provider)
+        openProviderInfo(provider: provider)
     }
 
     func resignCell(cell: UITableViewCell) {
@@ -846,6 +844,29 @@ class AccountSetupController: UITableViewController, ProgressAlertHandler {
         }
     }
 
+    // MARK: - coordinator
+    private func showCertCheckOptions() {
+        let certificateCheckController = CertificateCheckController(dcContext: dcContext, sectionTitle: String.localized("login_certificate_checks"))
+        navigationController?.pushViewController(certificateCheckController, animated: true)
+    }
+
+    private func showImapSecurityOptions() {
+        let securitySettingsController = SecuritySettingsController(dcContext: dcContext, title: String.localized("login_imap_security"),
+                                                                      type: SecurityType.IMAPSecurity)
+        navigationController?.pushViewController(securitySettingsController, animated: true)
+    }
+
+    private func showSmptpSecurityOptions() {
+        let securitySettingsController = SecuritySettingsController(dcContext: dcContext,
+                                                                    title: String.localized("login_imap_security"),
+                                                                    type: SecurityType.SMTPSecurity)
+        navigationController?.pushViewController(securitySettingsController, animated: true)
+    }
+
+    private func openProviderInfo(provider: DcProvider) {
+        guard let url = URL(string: provider.getOverviewPage) else { return }
+        UIApplication.shared.open(url)
+    }
 }
 
 // MARK: - UITextFieldDelegate

+ 29 - 21
deltachat-ios/Controller/ChatListController.swift

@@ -2,7 +2,6 @@ import UIKit
 import DcCore
 
 class ChatListController: UITableViewController {
-    weak var coordinator: ChatListCoordinator?
     let viewModel: ChatListViewModelProtocol
     let dcContext: DcContext
 
@@ -13,7 +12,6 @@ class ChatListController: UITableViewController {
     private var msgChangedObserver: Any?
     private var incomingMsgObserver: Any?
     private var viewChatObserver: Any?
-    private var deleteChatObserver: Any?
 
     private lazy var searchController: UISearchController = {
         let searchController = UISearchController(searchResultsController: nil)
@@ -99,15 +97,7 @@ class ChatListController: UITableViewController {
             object: nil,
             queue: nil) { notification in
                 if let chatId = notification.userInfo?["chat_id"] as? Int {
-                    self.coordinator?.showChat(chatId: chatId)
-                }
-        }
-        deleteChatObserver = nc.addObserver(
-            forName: dcNotificationChatDeletedInChatDetail,
-            object: nil,
-            queue: nil) { notification in
-                if let chatId = notification.userInfo?["chat_id"] as? Int {
-                    self.deleteChat(chatId: chatId, animated: true)
+                    self.showChat(chatId: chatId)
                 }
         }
     }
@@ -125,10 +115,6 @@ class ChatListController: UITableViewController {
         if let viewChatObserver = self.viewChatObserver {
             nc.removeObserver(viewChatObserver)
         }
-
-        if let deleteChatObserver = self.deleteChatObserver {
-            nc.removeObserver(deleteChatObserver)
-        }
     }
 
     // MARK: - configuration
@@ -141,7 +127,7 @@ class ChatListController: UITableViewController {
 
     // MARK: - actions
     @objc func didPressNewChat() {
-        coordinator?.showNewChatController()
+        showNewChatController()
     }
 
     @objc func cancelButtonPressed() {
@@ -214,14 +200,14 @@ class ChatListController: UITableViewController {
         case .chat(let chatData):
             let chatId = chatData.chatId
             if chatId == DC_CHAT_ID_ARCHIVED_LINK {
-                coordinator?.showArchive()
+                showArchive()
             } else {
-                coordinator?.showChat(chatId: chatId)
+                showChat(chatId: chatId)
             }
         case .contact(let contactData):
             let contactId = contactData.contactId
             if let chatId = contactData.chatId {
-                coordinator?.showChat(chatId: chatId)
+                showChat(chatId: chatId)
             } else {
                 self.askToChatWith(contactId: contactId)
             }
@@ -318,7 +304,7 @@ class ChatListController: UITableViewController {
         let alert = UIAlertController(title: title, message: nil, preferredStyle: .safeActionSheet)
         alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
             let chat = self.dcContext.createChatByMessageId(msgId)
-            self.coordinator?.showChat(chatId: chat.id)
+            self.showChat(chatId: chat.id)
         }))
         alert.addAction(UIAlertAction(title: String.localized("not_now"), style: .default, handler: { _ in
             dcContact.marknoticed()
@@ -340,7 +326,7 @@ class ChatListController: UITableViewController {
             title: String.localized("start_chat"),
             style: .default,
             handler: { _ in
-                self.coordinator?.showNewChat(contactId: contactId)
+                self.showNewChat(contactId: contactId)
         }))
         alert.addAction(UIAlertAction(
             title: String.localized("cancel"),
@@ -360,6 +346,28 @@ class ChatListController: UITableViewController {
         let row = viewModel.deleteChat(chatId: chatId)
         tableView.deleteRows(at: [IndexPath(row: row, section: 0)], with: .fade)
     }
+
+    // MARK: - coordinator
+    private func showNewChatController() {
+        let newChatVC = NewChatViewController(dcContext: dcContext)
+        navigationController?.pushViewController(newChatVC, animated: true)
+    }
+
+    func showChat(chatId: Int, animated: Bool = true) {
+        let chatVC = ChatViewController(dcContext: dcContext, chatId: chatId)
+        navigationController?.pushViewController(chatVC, animated: animated)
+    }
+
+    private func showArchive() {
+        let viewModel = ChatListViewModel(dcContext: dcContext, isArchive: true)
+        let controller = ChatListController(dcContext: dcContext, viewModel: viewModel)
+        navigationController?.pushViewController(controller, animated: true)
+    }
+
+    private func showNewChat(contactId: Int) {
+        let chatId = dcContext.createChatByContactId(contactId: contactId)
+        showChat(chatId: Int(chatId))
+    }
 }
 
 // MARK: - uisearchbardelegate

+ 69 - 15
deltachat-ios/Controller/ChatViewController.swift

@@ -39,13 +39,9 @@ extension ChatViewController: MediaPickerDelegate {
 }
 
 class ChatViewController: MessagesViewController {
-
     var dcContext: DcContext
-    weak var coordinator: ChatViewCoordinator?
-
     let outgoingAvatarOverlap: CGFloat = 17.5
     let loadCount = 30
-
     let chatId: Int
     let refreshControl = UIRefreshControl()
     var messageList: [DcMsg] = []
@@ -67,6 +63,10 @@ class ChatViewController: MessagesViewController {
     var showCustomNavBar = true
     var previewView: UIView?
 
+    private lazy var mediaPicker: MediaPicker? = {
+        return MediaPicker(navigationController: navigationController)
+    }()
+
     var emptyStateView: PaddingLabel = {
         let view =  PaddingLabel()
         view.backgroundColor = DcColors.systemMessageBackgroundColor
@@ -498,7 +498,9 @@ class ChatViewController: MessagesViewController {
     }
 
     @objc private func chatProfilePressed() {
-        coordinator?.showChatDetail(chatId: chatId)
+        if chatId != DC_CHAT_ID_DEADDROP {
+            showChatDetail(chatId: chatId)
+        }
     }
 
     // MARK: - UICollectionViewDataSource
@@ -576,7 +578,7 @@ class ChatViewController: MessagesViewController {
         case NSSelectorFromString("messageForward:"):
             let msg = messageList[indexPath.section]
             RelayHelper.sharedInstance.setForwardMessage(messageId: msg.id)
-            coordinator?.navigateBack()
+            navigationController?.popViewController(animated: true)
         default:
             super.collectionView(collectionView, performAction: action, forItemAt: indexPath, withSender: sender)
         }
@@ -599,14 +601,14 @@ class ChatViewController: MessagesViewController {
         if dcContext.getChatIdByContactId(contactId: contactId) != 0 {
             self.dismiss(animated: true, completion: nil)
             let chatId = self.dcContext.createChatByContactId(contactId: contactId)
-            self.coordinator?.showChat(chatId: chatId)
+            self.showChat(chatId: chatId)
         } else {
             confirmationAlert(title: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), email),
                               actionTitle: String.localized("start_chat"),
                               actionHandler: { _ in
                                 self.dismiss(animated: true, completion: nil)
                                 let chatId = self.dcContext.createChatByContactId(contactId: contactId)
-                                self.coordinator?.showChat(chatId: chatId)})
+                                self.showChat(chatId: chatId)})
         }
     }
 
@@ -629,9 +631,61 @@ class ChatViewController: MessagesViewController {
                             self.dismiss(animated: true, completion: nil)},
                           cancelHandler: { _ in
                             self.dismiss(animated: false, completion: nil)
-                            self.coordinator?.navigateBack()})
+                            self.navigationController?.popViewController(animated: true)})
         }
     }
+
+    // MARK: - coordinator
+    private func showChatDetail(chatId: Int) {
+        let chat = dcContext.getChat(chatId: chatId)
+        switch chat.chatType {
+        case .SINGLE:
+            if let contactId = chat.contactIds.first {
+                let viewModel = ContactDetailViewModel(contactId: contactId, chatId: chatId, context: dcContext)
+                let contactDetailController = ContactDetailViewController(viewModel: viewModel)
+                navigationController?.pushViewController(contactDetailController, animated: true)
+            }
+        case .GROUP, .VERIFIEDGROUP:
+            let groupChatDetailViewController = GroupChatDetailViewController(chatId: chatId, dcContext: dcContext)
+            navigationController?.pushViewController(groupChatDetailViewController, animated: true)
+        }
+    }
+
+    private func showContactDetail(of contactId: Int, in chatOfType: ChatType, chatId: Int?) {
+        let viewModel = ContactDetailViewModel(contactId: contactId, chatId: chatId, context: dcContext )
+        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
+        navigationController?.pushViewController(contactDetailController, animated: true)
+    }
+
+    func showChat(chatId: Int) {
+        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+            navigationController?.popToRootViewController(animated: false)
+            appDelegate.appCoordinator.showChat(chatId: chatId)
+        }
+    }
+
+    private func showDocumentLibrary(delegate: MediaPickerDelegate) {
+        mediaPicker?.showDocumentLibrary(delegate: delegate)
+    }
+
+    private func showVoiceMessageRecorder(delegate: MediaPickerDelegate) {
+        mediaPicker?.showVoiceRecorder(delegate: delegate)
+    }
+
+    private func showCameraViewController(delegate: MediaPickerDelegate) {
+        mediaPicker?.showCamera(delegate: delegate, allowCropping: false)
+    }
+
+    private func showPhotoVideoLibrary(delegate: MediaPickerDelegate) {
+        mediaPicker?.showPhotoVideoLibrary(delegate: delegate)
+    }
+
+    private func showMediaGallery(currentIndex: Int, mediaUrls urls: [URL]) {
+        let betterPreviewController = PreviewController(currentIndex: currentIndex, urls: urls)
+        let nav = UINavigationController(rootViewController: betterPreviewController)
+        nav.modalPresentationStyle = .fullScreen
+        navigationController?.present(nav, animated: true)
+    }
 }
 
 // MARK: - MessagesDataSource
@@ -1119,19 +1173,19 @@ extension ChatViewController: MessagesLayoutDelegate {
     }
 
     private func documentActionPressed(_ action: UIAlertAction) {
-        coordinator?.showDocumentLibrary(delegate: self)
+        showDocumentLibrary(delegate: self)
     }
 
     private func voiceMessageButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showVoiceMessageRecorder(delegate: self)
+        showVoiceMessageRecorder(delegate: self)
     }
 
     private func cameraButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showCameraViewController(delegate: self)
+        showCameraViewController(delegate: self)
     }
 
     private func galleryButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showPhotoVideoLibrary(delegate: self)
+        showPhotoVideoLibrary(delegate: self)
     }
 
     private func locationStreamingButtonPressed(_ action: UIAlertAction) {
@@ -1181,7 +1235,7 @@ extension ChatViewController: MessageCellDelegate {
 
                 // these are the files user will be able to swipe trough
                 let mediaUrls: [URL] = previousUrls + [url] + nextUrls
-                coordinator?.showMediaGallery(currentIndex: previousUrls.count, mediaUrls: mediaUrls)
+                showMediaGallery(currentIndex: previousUrls.count, mediaUrls: mediaUrls)
             }
         }
     }
@@ -1229,7 +1283,7 @@ extension ChatViewController: MessageCellDelegate {
         if let indexPath = messagesCollectionView.indexPath(for: cell) {
             let message = messageList[indexPath.section]
             let chat = dcContext.getChat(chatId: chatId)
-            coordinator?.showContactDetail(of: message.fromContact.id, in: chat.chatType, chatId: chatId)
+            showContactDetail(of: message.fromContact.id, in: chat.chatType, chatId: chatId)
         }
     }
 

+ 57 - 10
deltachat-ios/Controller/ContactDetailViewController.swift

@@ -1,8 +1,8 @@
 import UIKit
+import DcCore
 
 // this is also used as ChatDetail for SingleChats
 class ContactDetailViewController: UITableViewController {
-    weak var coordinator: ContactDetailCoordinatorProtocol?
     private let viewModel: ContactDetailViewModelProtocol
 
     private lazy var headerCell: ContactDetailHeader = {
@@ -150,7 +150,7 @@ class ContactDetailViewController: UITableViewController {
             chatWith(contactId: contactId)
         case .sharedChats:
             let chatId = viewModel.getSharedChatIdAt(indexPath: indexPath)
-            coordinator?.showChat(chatId: chatId)
+            showChat(chatId: chatId)
         }
     }
 
@@ -200,9 +200,9 @@ class ContactDetailViewController: UITableViewController {
         let action = viewModel.attachmentActionFor(row: index)
         switch action {
         case .documents:
-            coordinator?.showDocuments()
+            showDocuments()
         case .gallery:
-            coordinator?.showGallery()
+            showGallery()
         }
     }
 
@@ -222,12 +222,11 @@ class ContactDetailViewController: UITableViewController {
 
 
     @objc private func editButtonPressed() {
-        coordinator?.showEditContact(contactId: viewModel.contactId)
+        showEditContact(contactId: viewModel.contactId)
     }
-}
 
-// MARK: alerts
-extension ContactDetailViewController {
+    // MARK: alerts
+
     private func showDeleteChatConfirmationAlert() {
         let alert = UIAlertController(
             title: nil,
@@ -235,7 +234,7 @@ extension ContactDetailViewController {
             preferredStyle: .safeActionSheet
         )
         alert.addAction(UIAlertAction(title: String.localized("menu_delete_chat"), style: .destructive, handler: { _ in
-            self.coordinator?.deleteChat()
+            self.deleteChat()
         }))
         alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
         self.present(alert, animated: true, completion: nil)
@@ -273,7 +272,55 @@ extension ContactDetailViewController {
 
     private func chatWith(contactId: Int) {
         let chatId = self.viewModel.context.createChatByContactId(contactId: contactId)
-        self.coordinator?.showChat(chatId: chatId)
+        self.showChat(chatId: chatId)
+    }
+
+    // MARK: - coordinator
+    private func showChat(chatId: Int) {
+        if let chatlistViewController = navigationController?.viewControllers[0] {
+            let chatViewController = ChatViewController(dcContext: viewModel.context, chatId: chatId)
+            navigationController?.setViewControllers([chatlistViewController, chatViewController], animated: true)
+        }
     }
 
+    private func showEditContact(contactId: Int) {
+        let editContactController = EditContactController(dcContext: viewModel.context, contactIdForUpdate: contactId)
+        navigationController?.pushViewController(editContactController, animated: true)
+    }
+
+    private func showDocuments() {
+        presentPreview(for: DC_MSG_FILE, messageType2: DC_MSG_AUDIO, messageType3: 0)
+    }
+
+    private func showGallery() {
+        presentPreview(for: DC_MSG_IMAGE, messageType2: DC_MSG_GIF, messageType3: DC_MSG_VIDEO)
+    }
+
+    private func presentPreview(for messageType: Int32, messageType2: Int32, messageType3: Int32) {
+        guard let chatId = viewModel.chatId ?? viewModel.context.getChatIdByContactId(viewModel.contactId) else { return }
+        let messageIds = viewModel.context.getChatMedia(chatId: chatId, messageType: messageType, messageType2: messageType2, messageType3: messageType3)
+        var mediaUrls: [URL] = []
+        for messageId in messageIds {
+            let message = DcMsg.init(id: messageId)
+            if let url = message.fileURL {
+                mediaUrls.insert(url, at: 0)
+            }
+        }
+        let previewController = PreviewController(currentIndex: 0, urls: mediaUrls)
+        navigationController?.pushViewController(previewController, animated: true)
+    }
+
+    private func deleteChat() {
+        guard let chatId = viewModel.chatId else {
+            return
+        }
+        viewModel.context.deleteChat(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)
+        if let navigationController = navigationController {
+            navigationController.popViewController(animated: false)
+            navigationController.popViewController(animated: true)
+        }
+    }
 }

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

@@ -30,7 +30,7 @@ class EditContactController: NewContactController {
 
     @objc override func saveContactButtonPressed() {
         _ = dcContext.createContact(name: model.name, email: model.email)
-        coordinator?.navigateBack()
+        navigationController?.popViewController(animated: true)
     }
 
 }

+ 13 - 10
deltachat-ios/Controller/EditGroupViewController.swift

@@ -2,9 +2,7 @@ import UIKit
 import DcCore
 
 class EditGroupViewController: UITableViewController, MediaPickerDelegate {
-
-    weak var coordinator: EditGroupCoordinator?
-
+    private let dcContext: DcContext
     private let chat: DcChat
     private var groupImage: UIImage?
 
@@ -13,6 +11,10 @@ class EditGroupViewController: UITableViewController, MediaPickerDelegate {
 
     var avatarSelectionCell: AvatarSelectionCell
 
+    private lazy var mediaPicker: MediaPicker? = {
+        return MediaPicker(navigationController: navigationController)
+    }()
+
     lazy var groupNameCell: TextFieldCell = {
         let cell = TextFieldCell(description: String.localized("group_name"), placeholder: self.chat.name)
         cell.setText(text: self.chat.name)
@@ -31,7 +33,8 @@ class EditGroupViewController: UITableViewController, MediaPickerDelegate {
         return button
     }()
 
-    init(chat: DcChat) {
+    init(dcContext: DcContext, chat: DcChat) {
+        self.dcContext = dcContext
         self.chat = chat
         self.avatarSelectionCell = AvatarSelectionCell(chat: chat)
         super.init(style: .grouped)
@@ -75,15 +78,15 @@ class EditGroupViewController: UITableViewController, MediaPickerDelegate {
     
     @objc func saveContactButtonPressed() {
         let newName = groupNameCell.getText()
-        if let groupImage = groupImage, let dcContext = coordinator?.dcContext {
+        if let groupImage = groupImage {
             AvatarHelper.saveChatAvatar(dcContext: dcContext, image: groupImage, for: Int(chat.id))
         }
-        _ = DcContext.shared.setChatName(chatId: chat.id, name: newName ?? "")
-        coordinator?.navigateBack()
+        _ = dcContext.setChatName(chatId: chat.id, name: newName ?? "")
+        navigationController?.popViewController(animated: true)
     }
 
     @objc func cancelButtonPressed() {
-        coordinator?.navigateBack()
+        navigationController?.popViewController(animated: true)
     }
 
     private func groupNameEdited(_ textField: UITextField) {
@@ -102,11 +105,11 @@ class EditGroupViewController: UITableViewController, MediaPickerDelegate {
     }
 
     private func galleryButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showPhotoPicker(delegate: self)
+        mediaPicker?.showPhotoGallery(delegate: self)
     }
 
     private func cameraButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showCamera(delegate: self)
+        mediaPicker?.showCamera(delegate: self)
     }
 
     func onImageSelected(image: UIImage) {

+ 5 - 9
deltachat-ios/Controller/EditSettingsController.swift

@@ -2,9 +2,7 @@ import UIKit
 import DcCore
 
 class EditSettingsController: UITableViewController, MediaPickerDelegate {
-
     private let dcContext: DcContext
-    weak var coordinator: EditSettingsCoordinator?
     private var displayNameBackup: String?
     private var statusCellBackup: String?
 
@@ -22,7 +20,9 @@ class EditSettingsController: UITableViewController, MediaPickerDelegate {
 
     private let tagAccountSettingsCell = 1
 
-    private var childCoordinators: Coordinator?
+    private lazy var mediaPicker: MediaPicker? = {
+        return MediaPicker(navigationController: navigationController)
+    }()
 
     private lazy var statusCell: MultilineTextFieldCell = {
         let cell = MultilineTextFieldCell(description: String.localized("pref_default_status_label"),
@@ -131,20 +131,17 @@ class EditSettingsController: UITableViewController, MediaPickerDelegate {
             tableView.deselectRow(at: indexPath, animated: true)
             guard let nc = navigationController else { return }
             let accountSetupVC = AccountSetupController(dcContext: dcContext, editView: true)
-            let coordinator = AccountSetupCoordinator(dcContext: dcContext, navigationController: nc)
-            self.childCoordinators = coordinator
-            accountSetupVC.coordinator = coordinator
             nc.pushViewController(accountSetupVC, animated: true)
         }
     }
 
     // MARK: - actions
     private func galleryButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showPhotoPicker(delegate: self)
+        mediaPicker?.showPhotoGallery(delegate: self)
     }
 
     private func cameraButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showCamera(delegate: self)
+        mediaPicker?.showCamera(delegate: self)
     }
 
     private func deleteProfileIconPressed(_ action: UIAlertAction) {
@@ -189,4 +186,3 @@ class EditSettingsController: UITableViewController, MediaPickerDelegate {
     }
 
 }
-

+ 79 - 19
deltachat-ios/Controller/GroupChatDetailViewController.swift

@@ -18,19 +18,19 @@ class GroupChatDetailViewController: UIViewController {
     private let chatActionsRowLeaveGroup = 1
     private let chatActionsRowDeleteChat = 2
 
-    private let context: DcContext
-    weak var coordinator: GroupChatDetailCoordinator?
+    private let dcContext: DcContext
 
     private let sections: [ProfileSections] = [.attachments, .members, .chatActions]
 
     private var currentUser: DcContact? {
-        let myId = groupMemberIds.filter { DcContact(id: $0).email == context.addr }.first
+        let myId = groupMemberIds.filter { DcContact(id: $0).email == dcContext.addr }.first
         guard let currentUserId = myId else {
             return nil
         }
         return DcContact(id: currentUserId)
     }
 
+    private var chatId: Int
     fileprivate var chat: DcChat
 
     // stores contactIds
@@ -106,9 +106,10 @@ class GroupChatDetailViewController: UIViewController {
         return cell
     }()
 
-    init(chatId: Int, context: DcContext) {
-        self.context = context
-        chat = context.getChat(chatId: chatId)
+    init(chatId: Int, dcContext: DcContext) {
+        self.dcContext = dcContext
+        self.chatId = chatId
+        chat = dcContext.getChat(chatId: chatId)
         super.init(nibName: nil, bundle: nil)
         setupSubviews()
     }
@@ -138,7 +139,7 @@ class GroupChatDetailViewController: UIViewController {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         //update chat object, maybe chat name was edited
-        chat = context.getChat(chatId: chat.id)
+        chat = dcContext.getChat(chatId: chat.id)
         updateGroupMembers()
         tableView.reloadData() // to display updates
         editBarButtonItem.isEnabled = currentUser != nil
@@ -166,18 +167,18 @@ class GroupChatDetailViewController: UIViewController {
 
     // MARK: - actions
     @objc func editButtonPressed() {
-        coordinator?.showGroupChatEdit(chat: chat)
+        showGroupChatEdit(chat: chat)
     }
 
     private func toggleArchiveChat() {
         let archivedBefore = chat.isArchived
-        context.archiveChat(chatId: chat.id, archive: !archivedBefore)
+        dcContext.archiveChat(chatId: chat.id, archive: !archivedBefore)
         if archivedBefore {
             archiveChatCell.actionTitle = String.localized("menu_archive_chat")
         } else {
             self.navigationController?.popToRootViewController(animated: false)
         }
-        self.chat = context.getChat(chatId: chat.id)
+        self.chat = dcContext.getChat(chatId: chat.id)
      }
 
     private func getGroupMemberIdFor(_ row: Int) -> Int {
@@ -187,6 +188,65 @@ class GroupChatDetailViewController: UIViewController {
     private func isMemberManagementRow(row: Int) -> Bool {
         return row < memberManagementRows
     }
+
+    // MARK: - coordinator
+    private func showSingleChatEdit(contactId: Int) {
+        let editContactController = EditContactController(dcContext: dcContext, contactIdForUpdate: contactId)
+        navigationController?.pushViewController(editContactController, animated: true)
+    }
+
+    private func showAddGroupMember(chatId: Int) {
+        let groupMemberViewController = AddGroupMembersViewController(chatId: chatId)
+        navigationController?.pushViewController(groupMemberViewController, animated: true)
+    }
+
+    private func showQrCodeInvite(chatId: Int) {
+        let qrInviteCodeController = QrInviteViewController(dcContext: dcContext, chatId: chatId)
+        navigationController?.pushViewController(qrInviteCodeController, animated: true)
+    }
+
+    private func showGroupChatEdit(chat: DcChat) {
+        let editGroupViewController = EditGroupViewController(dcContext: dcContext, chat: chat)
+        navigationController?.pushViewController(editGroupViewController, animated: true)
+    }
+
+    private func showContactDetail(of contactId: Int) {
+        let viewModel = ContactDetailViewModel(contactId: contactId, chatId: nil, context: dcContext)
+        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
+        navigationController?.pushViewController(contactDetailController, animated: true)
+    }
+
+    private func showDocuments() {
+        presentPreview(for: DC_MSG_FILE, messageType2: DC_MSG_AUDIO, messageType3: 0)
+    }
+
+    private func showGallery() {
+        presentPreview(for: DC_MSG_IMAGE, messageType2: DC_MSG_GIF, messageType3: DC_MSG_VIDEO)
+    }
+
+    private func presentPreview(for messageType: Int32, messageType2: Int32, messageType3: Int32) {
+        let messageIds = dcContext.getChatMedia(chatId: chatId, messageType: messageType, messageType2: messageType2, messageType3: messageType3)
+        var mediaUrls: [URL] = []
+        for messageId in messageIds {
+            let message = DcMsg.init(id: messageId)
+            if let url = message.fileURL {
+                mediaUrls.insert(url, at: 0)
+            }
+        }
+        let previewController = PreviewController(currentIndex: 0, urls: mediaUrls)
+        navigationController?.pushViewController(previewController, animated: true)
+    }
+
+    private func deleteChat() {
+        dcContext.deleteChat(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)
+        if let navigationController = navigationController {
+            navigationController.popViewController(animated: false)
+            navigationController.popViewController(animated: true)
+        }
+    }
 }
 
 // MARK: - UITableViewDelegate, UITableViewDataSource
@@ -257,7 +317,7 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
             let contactId: Int = getGroupMemberIdFor(row)
             let cellData = ContactCellData(
                 contactId: contactId,
-                chatId: context.getChatIdByContactId(contactId)
+                chatId: dcContext.getChatIdByContactId(contactId)
             )
             let cellViewModel = ContactCellViewModel(contactData: cellData)
             contactCell.updateCell(cellViewModel: cellViewModel)
@@ -282,18 +342,18 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
         switch sectionType {
         case .attachments:
             if row == attachmentsRowGallery {
-                coordinator?.showGallery()
+                showGallery()
             } else if row == attachmentsRowDocuments {
-                coordinator?.showDocuments()
+                showDocuments()
             }
         case .members:
             if row == membersRowAddMembers {
-                coordinator?.showAddGroupMember(chatId: chat.id)
+                showAddGroupMember(chatId: chat.id)
             } else if row == membersRowQrInvite {
-                coordinator?.showQrCodeInvite(chatId: chat.id)
+                showQrCodeInvite(chatId: chat.id)
             } else {
                 let member = getGroupMember(at: row)
-                coordinator?.showContactDetail(of: member.id)
+                showContactDetail(of: member.id)
             }
         case .chatActions:
             if row == chatActionsRowArchiveChat {
@@ -347,7 +407,7 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
                 let title = String.localizedStringWithFormat(String.localized("ask_remove_members"), contact.nameNAddr)
                 let alert = UIAlertController(title: title, message: nil, preferredStyle: .safeActionSheet)
                 alert.addAction(UIAlertAction(title: String.localized("remove_desktop"), style: .destructive, handler: { _ in
-                    let success = self.context.removeContactFromChat(chatId: self.chat.id, contactId: contact.id)
+                    let success = self.dcContext.removeContactFromChat(chatId: self.chat.id, contactId: contact.id)
                     if success {
                         self.removeGroupMemberFromTableAt(indexPath)
                     }
@@ -382,7 +442,7 @@ extension GroupChatDetailViewController {
             preferredStyle: .safeActionSheet
         )
         alert.addAction(UIAlertAction(title: String.localized("menu_delete_chat"), style: .destructive, handler: { _ in
-            self.coordinator?.deleteChat()
+            self.deleteChat()
         }))
         alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
         self.present(alert, animated: true, completion: nil)
@@ -392,7 +452,7 @@ extension GroupChatDetailViewController {
         if let userId = currentUser?.id {
             let alert = UIAlertController(title: String.localized("ask_leave_group"), message: nil, preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("menu_leave_group"), style: .destructive, handler: { _ in
-                _ = self.context.removeContactFromChat(chatId: self.chat.id, contactId: userId)
+                _ = self.dcContext.removeContactFromChat(chatId: self.chat.id, contactId: userId)
                 self.editBarButtonItem.isEnabled = false
                 self.updateGroupMembers()
             }))

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

@@ -2,8 +2,6 @@ import UIKit
 import DcCore
 
 class NewGroupAddMembersViewController: GroupMembersViewController {
-    weak var coordinator: NewGroupAddMembersCoordinator?
-
     var onMembersSelected: ((Set<Int>) -> Void)?
     let isVerifiedGroup: Bool
 
@@ -41,10 +39,6 @@ class NewGroupAddMembersViewController: GroupMembersViewController {
         super.viewWillAppear(animated)
     }
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-    }
-
     @objc func cancelButtonPressed() {
         navigationController?.popViewController(animated: true)
     }
@@ -61,7 +55,6 @@ class NewGroupAddMembersViewController: GroupMembersViewController {
 }
 
 class AddGroupMembersViewController: GroupMembersViewController {
-    weak var coordinator: AddGroupMembersCoordinator?
     private var chatId: Int?
     private let sectionNewContact = 0
     private let sectionMemberList = 1
@@ -80,7 +73,7 @@ class AddGroupMembersViewController: GroupMembersViewController {
 
     private lazy var chat: DcChat? = {
         if let chatId = chatId {
-            return coordinator?.dcContext.getChat(chatId: chatId)
+            return dcContext.getChat(chatId: chatId)
         }
         return nil
     }()
@@ -182,7 +175,7 @@ class AddGroupMembersViewController: GroupMembersViewController {
         switch indexPath.section {
         case sectionNewContact:
             tableView.deselectRow(at: indexPath, animated: true)
-            coordinator?.showNewContactController()
+            showNewContactController()
         case sectionMemberList:
             didSelectContactCell(at: indexPath)
         default:
@@ -224,6 +217,13 @@ class AddGroupMembersViewController: GroupMembersViewController {
 
         return cell
     }
+
+    // MARK: - coordinator
+    private func showNewContactController() {
+        let newContactController = NewContactController(dcContext: dcContext)
+        newContactController.openChatOnSave = false
+        navigationController?.pushViewController(newContactController, animated: true)
+    }
 }
 
 class BlockedContactsViewController: GroupMembersViewController, GroupMemberSelectionDelegate {

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

@@ -49,7 +49,7 @@ class MailboxViewController: ChatViewController {
             let alert = UIAlertController(title: title, message: nil, preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
                 let chat = self.dcContext.createChatByMessageId(message.id)
-                self.coordinator?.showChat(chatId: chat.id)
+                self.showChat(chatId: chat.id)
             }))
             alert.addAction(UIAlertAction(title: String.localized("menu_block_contact"), style: .destructive, handler: { _ in
                 dcContact.block()

+ 37 - 19
deltachat-ios/Controller/NewChatViewController.swift

@@ -4,8 +4,6 @@ import UIKit
 import DcCore
 
 class NewChatViewController: UITableViewController {
-    weak var coordinator: NewChatCoordinator?
-
     private let dcContext: DcContext
 
     private let sectionNew = 0
@@ -124,11 +122,6 @@ class NewChatViewController: UITableViewController {
         dismiss(animated: true, completion: nil)
     }
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
-    }
-
     // MARK: - Table view data source
 
     override func numberOfSections(in _: UITableView) -> Int {
@@ -214,11 +207,11 @@ class NewChatViewController: UITableViewController {
 
         if section == sectionNew {
             if row == sectionNewRowNewGroup {
-                coordinator?.showNewGroupController(isVerified: false)
+                showNewGroupController(isVerified: false)
             } else if row == sectionNewRowNewVerifiedGroup {
-                coordinator?.showNewGroupController(isVerified: true)
+                showNewGroupController(isVerified: true)
             } else if row == sectionNewRowNewContact {
-                coordinator?.showNewContactController()
+                showNewContactController()
             }
         } else if section == sectionImportedContacts {
             if deviceContactAccessGranted {
@@ -238,19 +231,16 @@ class NewChatViewController: UITableViewController {
             let edit = UITableViewRowAction(style: .normal, title: String.localized("info")) { [unowned self] _, _ in
                 if self.searchController.isActive {
                     self.searchController.dismiss(animated: false) {
-                        self.coordinator?.showContactDetail(contactId: contactId)
+                        self.showContactDetail(contactId: contactId)
                     }
                 } else {
-                    self.coordinator?.showContactDetail(contactId: contactId)
+                    self.showContactDetail(contactId: contactId)
                 }
             }
 
             let delete = UITableViewRowAction(style: .destructive, title: String.localized("delete")) { [unowned self] _, _ in
-                //handle delete
-                if let dcContext = self.coordinator?.dcContext {
-                    let contactId = self.contactIdByRow(indexPath.row)
-                    self.askToDeleteContact(contactId: contactId, context: dcContext)
-                }
+                let contactId = self.contactIdByRow(indexPath.row)
+                self.askToDeleteContact(contactId: contactId, context: self.dcContext)
             }
 
             edit.backgroundColor = DcColors.primary
@@ -295,7 +285,7 @@ class NewChatViewController: UITableViewController {
     private func askToChatWith(contactId: Int) {
         if dcContext.getChatIdByContactId(contactId: contactId) != 0 {
             self.dismiss(animated: true, completion: nil)
-            self.coordinator?.showNewChat(contactId: contactId)
+            self.showNewChat(contactId: contactId)
         } else {
             let dcContact = DcContact(id: contactId)
             let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), dcContact.nameNAddr),
@@ -303,7 +293,7 @@ class NewChatViewController: UITableViewController {
                                           preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
                 self.dismiss(animated: true, completion: nil)
-                self.coordinator?.showNewChat(contactId: contactId)
+                self.showNewChat(contactId: contactId)
             }))
             alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: { _ in
                 self.reactivateSearchBarIfNeeded()
@@ -336,6 +326,34 @@ class NewChatViewController: UITableViewController {
         tableView.reloadData()
         tableView.scrollToTop()
     }
+
+    // MARK: - coordinator
+    private func showNewGroupController(isVerified: Bool) {
+        let newGroupController = NewGroupController(dcContext: dcContext, isVerified: isVerified)
+        navigationController?.pushViewController(newGroupController, animated: true)
+    }
+
+    private func showNewContactController() {
+        let newContactController = NewContactController(dcContext: dcContext)
+        navigationController?.pushViewController(newContactController, animated: true)
+    }
+
+    private func showNewChat(contactId: Int) {
+        let chatId = dcContext.createChatByContactId(contactId: contactId)
+        showChat(chatId: Int(chatId))
+    }
+
+    private func showChat(chatId: Int) {
+        let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
+        navigationController?.pushViewController(chatViewController, animated: true)
+        navigationController?.viewControllers.remove(at: 1)
+    }
+
+    private func showContactDetail(contactId: Int) {
+        let viewModel = ContactDetailViewModel(contactId: contactId, chatId: nil, context: dcContext)
+        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
+        navigationController?.pushViewController(contactDetailController, animated: true)
+    }
 }
 
 extension NewChatViewController: ContactListDelegate {

+ 9 - 11
deltachat-ios/Controller/NewContactController.swift

@@ -4,7 +4,6 @@ import DcCore
 class NewContactController: UITableViewController {
 
     let dcContext: DcContext
-    weak var coordinator: EditContactCoordinatorProtocol?
     var openChatOnSave = true
 
     let emailCell = TextFieldCell.makeEmailCell()
@@ -62,10 +61,6 @@ class NewContactController: UITableViewController {
         }
     }
 
-    override func viewWillAppear(_: Bool) {
-        navigationController?.setNavigationBarHidden(false, animated: false)
-    }
-
     @objc func emailTextChanged() {
         let emailText = emailCell.textField.text ?? ""
         model.email = emailText
@@ -80,14 +75,14 @@ class NewContactController: UITableViewController {
         let contactId = dcContext.createContact(name: model.name, email: model.email)
         let chatId = dcContext.createChatByContactId(contactId: contactId)
         if openChatOnSave {
-            coordinator?.showChat(chatId: chatId)
+            showChat(chatId: chatId)
         } else {
-            coordinator?.navigateBack()
+            navigationController?.popViewController(animated: true)
         }
     }
 
     @objc func cancelButtonPressed() {
-        coordinator?.navigateBack()
+        navigationController?.popViewController(animated: true)
     }
 
     required init?(coder _: NSCoder) {
@@ -104,9 +99,12 @@ class NewContactController: UITableViewController {
         return cells[row]
     }
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
+    // MARK: - coordinator
+    private func showChat(chatId: Int) {
+        if let chatlistViewController = navigationController?.viewControllers[0] {
+            let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
+            navigationController?.setViewControllers([chatlistViewController, chatViewController], animated: true)
+        }
     }
 }
 

+ 44 - 14
deltachat-ios/Controller/NewGroupController.swift

@@ -2,9 +2,6 @@ import UIKit
 import DcCore
 
 class NewGroupController: UITableViewController, MediaPickerDelegate {
-
-    weak var coordinator: NewGroupCoordinator?
-
     var groupName: String = ""
     var groupChatId: Int = 0
 
@@ -28,6 +25,10 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     private lazy var countSectionInvite: Int = 2
     private let sectionGroupMembers = 2
 
+    private lazy var mediaPicker: MediaPicker? = {
+        return MediaPicker(navigationController: navigationController)
+    }()
+
     lazy var groupNameCell: TextFieldCell = {
         let cell = TextFieldCell(description: String.localized("group_name"), placeholder: String.localized("group_name"))
         cell.onTextFieldChange = self.updateGroupName
@@ -123,7 +124,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
         for contactId in contactIdsForGroup {
             let success = dcContext.addContactToChat(chatId: groupChatId, contactId: contactId)
 
-            if let groupImage = groupImage, let dcContext = coordinator?.dcContext {
+            if let groupImage = groupImage {
                     AvatarHelper.saveChatAvatar(dcContext: dcContext, image: groupImage, for: Int(groupChatId))
             }
 
@@ -134,12 +135,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
             }
         }
 
-        coordinator?.showGroupChat(chatId: Int(groupChatId))
-    }
-
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
+        showGroupChat(chatId: Int(groupChatId))
     }
 
     override func numberOfSections(in _: UITableView) -> Int {
@@ -244,10 +240,10 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
             if row == sectionInviteRowAddMembers {
                 var contactsWithoutSelf = contactIdsForGroup
                 contactsWithoutSelf.remove(Int(DC_CONTACT_ID_SELF))
-                coordinator?.showAddMembers(preselectedMembers: contactsWithoutSelf, isVerified: self.isVerifiedGroup)
+                showAddMembers(preselectedMembers: contactsWithoutSelf, isVerified: self.isVerifiedGroup)
             } else {
                 self.groupChatId = dcContext.createGroupChat(verified: isVerifiedGroup, name: groupName)
-                coordinator?.showQrCodeInvite(chatId: Int(self.groupChatId))
+                showQrCodeInvite(chatId: Int(self.groupChatId))
             }
         }
     }
@@ -295,11 +291,11 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     }
 
     private func galleryButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showPhotoPicker(delegate: self)
+        showPhotoPicker(delegate: self)
     }
 
     private func cameraButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showCamera(delegate: self)
+        showCamera(delegate: self)
     }
 
     func onImageSelected(image: UIImage) {
@@ -341,4 +337,38 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
         self.groupContactIds.remove(at: row)
         tableView.deleteRows(at: [indexPath], with: .fade)
     }
+
+    // MARK: - coordinator
+    private func showGroupChat(chatId: Int) {
+        if let chatlistViewController = navigationController?.viewControllers[0] {
+            let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
+            navigationController?.setViewControllers([chatlistViewController, chatViewController], animated: true)
+        }
+    }
+
+    private func showPhotoPicker(delegate: MediaPickerDelegate) {
+        mediaPicker?.showPhotoGallery(delegate: delegate)
+    }
+
+    private func showCamera(delegate: MediaPickerDelegate) {
+        mediaPicker?.showCamera(delegate: delegate)
+    }
+
+    private func showQrCodeInvite(chatId: Int) {
+        let qrInviteCodeController = QrInviteViewController(dcContext: dcContext, chatId: chatId)
+        qrInviteCodeController.onDismissed = { [unowned self] in
+            self.updateGroupContactIdsOnQRCodeInvite()
+        }
+        navigationController?.pushViewController(qrInviteCodeController, animated: true)
+    }
+
+    private func showAddMembers(preselectedMembers: Set<Int>, isVerified: Bool) {
+        let newGroupController = NewGroupAddMembersViewController(preselected: preselectedMembers,
+                                                                  isVerified: isVerified)
+        newGroupController.onMembersSelected = { [unowned self] (memberIds: Set<Int>) -> Void in
+            self.updateGroupContactIdsOnListSelection(memberIds)
+            self.navigationController?.popViewController(animated: true)
+        }
+        navigationController?.pushViewController(newGroupController, animated: true)
+    }
 }

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

@@ -2,14 +2,14 @@ import UIKit
 import DcCore
 
 class ProfileInfoViewController: UITableViewController {
-
-    weak var coordinator: EditSettingsCoordinator?
     var onClose: VoidFunction?
-
     private let dcContext: DcContext
-
     private var displayName: String?
 
+    private lazy var mediaPicker: MediaPicker? = {
+        return MediaPicker(navigationController: navigationController)
+    }()
+
     private lazy var doneButtonItem: UIBarButtonItem = {
         return UIBarButtonItem(
             title: String.localized("done"),
@@ -136,11 +136,11 @@ class ProfileInfoViewController: UITableViewController {
     }
 
     private func galleryButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showPhotoPicker(delegate: self)
+        mediaPicker?.showPhotoGallery(delegate: self)
     }
 
     private func cameraButtonPressed(_ action: UIAlertAction) {
-        coordinator?.showCamera(delegate: self)
+        mediaPicker?.showCamera(delegate: self)
     }
 }
 

+ 0 - 14
deltachat-ios/Controller/QrCodeReaderController.swift

@@ -24,10 +24,6 @@ class QrCodeReaderController: UIViewController {
            return label
     }()
 
-    private lazy var closeButton: UIBarButtonItem = {
-        return UIBarButtonItem(title: String.localized("cancel"), style: .done, target: self, action: #selector(closeButtonPressed(_:)))
-    }()
-
     private let supportedCodeTypes = [
         AVMetadataObject.ObjectType.qr
     ]
@@ -37,7 +33,6 @@ class QrCodeReaderController: UIViewController {
         super.viewDidLoad()
         self.edgesForExtendedLayout = []
         title = String.localized("qrscan_title")
-        navigationItem.leftBarButtonItem = closeButton
 
         guard let captureDevice = AVCaptureDevice.DiscoverySession.init(
             deviceTypes: [AVCaptureDevice.DeviceType.builtInWideAngleCamera],
@@ -83,11 +78,6 @@ class QrCodeReaderController: UIViewController {
         captureSession.stopRunning()
     }
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
-    }
-
     // MARK: - setup
     private func setupSubviews() {
         view.layer.addSublayer(videoPreviewLayer)
@@ -124,10 +114,6 @@ class QrCodeReaderController: UIViewController {
     }
 
     // MARK: - actions
-    @objc private func closeButtonPressed(_ sender: UIBarButtonItem) {
-        self.dismiss(animated: true, completion: nil)
-    }
-
     func startSession() {
         captureSession.startRunning()
     }

+ 16 - 5
deltachat-ios/Controller/QrPageController.swift

@@ -2,8 +2,6 @@ import UIKit
 import DcCore
 
 class QrPageController: UIPageViewController, ProgressAlertHandler {
-
-    weak var coordinator: QrViewCoordinator?
     private let dcContext: DcContext
     weak var progressAlert: UIAlertController?
     var progressObserver: Any?
@@ -80,6 +78,19 @@ class QrPageController: UIPageViewController, ProgressAlertHandler {
         qrReader.delegate = self
         return qrReader
     }
+
+    // MARK: - coordinator
+    private func showChats() {
+        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+            appDelegate.appCoordinator.showTab(index: appDelegate.appCoordinator.chatsTab)
+        }
+    }
+
+    private func showChat(chatId: Int) {
+        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+            appDelegate.appCoordinator.showChat(chatId: chatId)
+        }
+    }
 }
 
 // MARK: - UIPageViewControllerDataSource, UIPageViewControllerDelegate
@@ -113,7 +124,7 @@ extension QrPageController: UIPageViewControllerDataSource, UIPageViewController
 extension QrPageController: QrCodeReaderDelegate {
 
     func handleQrCode(_ code: String) {
-        self.coordinator?.showChats()
+        self.showChats()
         let qrParsed: DcLot = self.dcContext.checkQR(qrCode: code)
         let state = Int32(qrParsed.state)
         switch state {
@@ -145,7 +156,7 @@ extension QrPageController: QrCodeReaderDelegate {
             let alert = UIAlertController(title: msg, message: nil, preferredStyle: .alert)
             alert.addAction(UIAlertAction(title: String.localized("start_chat"), style: .default, handler: { _ in
                 let chatId = self.dcContext.createChatByContactId(contactId: qrParsed.id)
-                self.coordinator?.showChat(chatId: chatId)
+                self.showChat(chatId: chatId)
             }))
             alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .default, handler: nil))
             present(alert, animated: true, completion: nil)
@@ -205,7 +216,7 @@ extension QrPageController: QrCodeReaderDelegate {
                 DispatchQueue.main.async {
                     self.progressAlert?.dismiss(animated: true, completion: nil)
                     if chatId != 0 {
-                        self.coordinator?.showChat(chatId: chatId)
+                        self.showChat(chatId: chatId)
                     } else if errorString != nil {
                         self.showErrorAlert(error: errorString!)
                     }

+ 49 - 9
deltachat-ios/Controller/SettingsController.swift

@@ -1,6 +1,7 @@
 import JGProgressHUD
 import UIKit
 import DcCore
+import DBDebugToolkit
 
 internal final class SettingsViewController: UITableViewController {
 
@@ -25,8 +26,6 @@ internal final class SettingsViewController: UITableViewController {
         case autodel = 11
     }
 
-    weak var coordinator: SettingsCoordinator?
-
     private var dcContext: DcContext
 
     private let externalPathDescr = "File Sharing/Delta Chat"
@@ -302,18 +301,18 @@ internal final class SettingsViewController: UITableViewController {
         tableView.deselectRow(at: indexPath, animated: false) // to achieve highlight effect
 
         switch cellTag {
-        case .profile: self.coordinator?.showEditSettingsController()
-        case .contactRequest: self.coordinator?.showContactRequests()
-        case .showEmails: coordinator?.showClassicMail()
-        case .blockedContacts: coordinator?.showBlockedContacts()
-        case .autodel: coordinator?.showAutodelOptions()
+        case .profile: showEditSettingsController()
+        case .contactRequest: showContactRequests()
+        case .showEmails: showClassicMail()
+        case .blockedContacts: showBlockedContacts()
+        case .autodel: showAutodelOptions()
         case .notifications: break
         case .receiptConfirmation: break
         case .autocryptPreferences: break
         case .sendAutocryptMessage: sendAutocryptSetupMessage()
         case .exportBackup: createBackup()
         case .advanced: showAdvancedDialog()
-        case .help: coordinator?.showHelp()
+        case .help: showHelp()
         }
     }
 
@@ -412,7 +411,7 @@ internal final class SettingsViewController: UITableViewController {
         }))
 
         let logAction = UIAlertAction(title: String.localized("pref_view_log"), style: .default, handler: { [unowned self] _ in
-            self.coordinator?.showDebugToolkit()
+            self.showDebugToolkit()
         })
         alert.addAction(logAction)
         alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
@@ -442,4 +441,45 @@ internal final class SettingsViewController: UITableViewController {
 
         autodelCell.detailTextLabel?.text = autodelSummary()
     }
+
+    // MARK: - coordinator
+    private func showEditSettingsController() {
+        let editController = EditSettingsController(dcContext: dcContext)
+        navigationController?.pushViewController(editController, animated: true)
+    }
+
+    private func showClassicMail() {
+        let settingsClassicViewController = SettingsClassicViewController(dcContext: dcContext)
+        navigationController?.pushViewController(settingsClassicViewController, animated: true)
+    }
+
+    private func showBlockedContacts() {
+        let blockedContactsController = BlockedContactsViewController()
+        navigationController?.pushViewController(blockedContactsController, animated: true)
+    }
+
+    private func showAutodelOptions() {
+        let settingsAutodelOverviewController = SettingsAutodelOverviewController(dcContext: dcContext)
+        navigationController?.pushViewController(settingsAutodelOverviewController, animated: true)
+    }
+
+    private func showContactRequests() {
+        let deaddropViewController = MailboxViewController(dcContext: dcContext, chatId: Int(DC_CHAT_ID_DEADDROP))
+        navigationController?.pushViewController(deaddropViewController, animated: true)
+    }
+
+    private func showHelp() {
+        navigationController?.pushViewController(HelpViewController(), animated: true)
+    }
+
+    private func showDebugToolkit() {
+        DBDebugToolkit.setup(with: [])  // emtpy array will override default device shake trigger
+        DBDebugToolkit.setupCrashReporting()
+        let info: [DBCustomVariable] = dcContext.getInfo().map { kv in
+            let value = kv.count > 1 ? kv[1] : ""
+            return DBCustomVariable(name: kv[0], value: value)
+        }
+        DBDebugToolkit.add(info)
+        DBDebugToolkit.showMenu()
+    }
 }

+ 37 - 48
deltachat-ios/Controller/WelcomeViewController.swift

@@ -2,10 +2,7 @@ import UIKit
 import DcCore
 
 class WelcomeViewController: UIViewController, ProgressAlertHandler {
-
-    weak var coordinator: WelcomeCoordinator?
     private let dcContext: DcContext
-    private var scannedQrCode: String?
     var progressObserver: Any?
     var onProgressSuccess: VoidFunction?
 
@@ -18,24 +15,40 @@ class WelcomeViewController: UIViewController, ProgressAlertHandler {
     private lazy var welcomeView: WelcomeContentView = {
         let view = WelcomeContentView()
         view.onLogin = { [unowned self] in
-            self.coordinator?.presentLogin()
+            let accountSetupController = AccountSetupController(dcContext: self.dcContext, editView: false)
+            accountSetupController.onLoginSuccess = {
+                [unowned self] in
+                if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+                    appDelegate.appCoordinator.presentTabBarController()
+                }
+            }
+            self.navigationController?.pushViewController(accountSetupController, animated: true)
         }
         view.onScanQRCode  = { [unowned self] in
-            self.showQRReader()
+            let qrReader = QrCodeReaderController()
+            qrReader.delegate = self
+            self.qrCodeReader = qrReader
+            self.navigationController?.pushViewController(qrReader, animated: true)
         }
         view.translatesAutoresizingMaskIntoConstraints = false
         return view
     }()
 
     private var qrCodeReader: QrCodeReaderController?
-    private var qrCodeReaderNav: UINavigationController?
     weak var progressAlert: UIAlertController?
 
     init(dcContext: DcContext) {
         self.dcContext = dcContext
         super.init(nibName: nil, bundle: nil)
-        onProgressSuccess = {[unowned self] in
-            self.coordinator?.handleQRAccountCreationSuccess()
+        self.navigationItem.title = String.localized("welcome_desktop")
+        onProgressSuccess = { [unowned self] in
+            let profileInfoController = ProfileInfoViewController(context: dcContext)
+            profileInfoController.onClose = {
+                if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
+                    appDelegate.appCoordinator.presentTabBarController()
+                }
+            }
+            self.navigationController?.setViewControllers([profileInfoController], animated: true)
         }
     }
 
@@ -92,30 +105,10 @@ class WelcomeViewController: UIViewController, ProgressAlertHandler {
         frameGuide.widthAnchor.constraint(equalTo: contentGuide.widthAnchor).isActive = true
     }
 
-    // MARK: - factory
-    private func makeQRReader() -> QrCodeReaderController {
-        let controller = QrCodeReaderController()
-        controller.delegate = self
-        return controller
-    }
-
     // MARK: - actions
 
-    private func showQRReader() {
-        let qrReader = makeQRReader()
-        self.qrCodeReader = qrReader
-        let nav = UINavigationController(rootViewController: qrReader)
-        nav.modalPresentationStyle = .fullScreen
-        self.qrCodeReaderNav = nav
-        present(nav, animated: true)
-    }
-
-    private func createAccountFromQRCode() {
-        guard let code = scannedQrCode else {
-            return
-        }
-        let success = dcContext.setConfigFromQR(qrCode: code)
-        scannedQrCode = nil
+    private func createAccountFromQRCode(qrCode: String) {
+        let success = dcContext.setConfigFromQR(qrCode: qrCode)
         if success {
             addProgressAlertListener(onSuccess: handleLoginSuccess)
             showProgressAlert(title: String.localized("login_header"), dcContext: dcContext)
@@ -141,14 +134,13 @@ extension WelcomeViewController: QrCodeReaderDelegate {
     func handleQrCode(_ code: String) {
         let lot = dcContext.checkQR(qrCode: code)
         if let domain = lot.text1, lot.state == DC_QR_ACCOUNT {
-            self.scannedQrCode = code
-            confirmAccountCreationAlert(accountDomain: domain)
+            confirmAccountCreationAlert(accountDomain: domain, qrCode: code)
         } else {
             qrErrorAlert()
         }
     }
 
-    private func confirmAccountCreationAlert(accountDomain domain: String) {
+    private func confirmAccountCreationAlert(accountDomain domain: String, qrCode: String) {
         let title = String.localizedStringWithFormat(NSLocalizedString("qraccount_ask_create_and_login", comment: ""), domain)
         let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
 
@@ -157,7 +149,7 @@ extension WelcomeViewController: QrCodeReaderDelegate {
             style: .default,
             handler: { [unowned self] _ in
                 self.dismissQRReader()
-                self.createAccountFromQRCode()
+                self.createAccountFromQRCode(qrCode: qrCode)
             }
         )
 
@@ -171,7 +163,7 @@ extension WelcomeViewController: QrCodeReaderDelegate {
 
         alert.addAction(okAction)
         alert.addAction(qrCancelAction)
-        qrCodeReaderNav?.present(alert, animated: true)
+        qrCodeReader?.present(alert, animated: true)
     }
 
     private func qrErrorAlert() {
@@ -185,15 +177,12 @@ extension WelcomeViewController: QrCodeReaderDelegate {
             }
         )
         alert.addAction(okAction)
-        qrCodeReaderNav?.present(alert, animated: true, completion: nil)
+        qrCodeReader?.present(alert, animated: true, completion: nil)
     }
 
     private func dismissQRReader() {
-        self.qrCodeReaderNav?.dismiss(animated: false) {
-            self.qrCodeReaderNav = nil
-            self.qrCodeReader = nil
-            self.scannedQrCode = nil
-        }
+        self.navigationController?.popViewController(animated: true)
+        self.qrCodeReader = nil
     }
 }
 
@@ -247,6 +236,13 @@ class WelcomeContentView: UIView {
         return label
     }()
 
+    private lazy var buttonStack: UIStackView = {
+        let stack = UIStackView(arrangedSubviews: [loginButton, qrCodeButton /*, importBackupButton */])
+        stack.axis = .vertical
+        stack.spacing = 15
+        return stack
+    }()
+
     private lazy var loginButton: UIButton = {
         let button = UIButton(type: .roundedRect)
         let title = String.localized("login_header").uppercased()
@@ -262,13 +258,6 @@ class WelcomeContentView: UIView {
         return button
     }()
 
-    private lazy var buttonStack: UIStackView = {
-        let stack = UIStackView(arrangedSubviews: [loginButton, qrCodeButton /*, importBackupButton */])
-        stack.axis = .vertical
-        stack.spacing = 15
-        return stack
-    }()
-
     private lazy var qrCodeButton: UIButton = {
         let button = UIButton()
         let title = String.localized("qrscan_title")

+ 27 - 819
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -3,10 +3,9 @@ import KK_ALCameraViewController
 import Photos
 import MobileCoreServices
 import DcCore
-import DBDebugToolkit
 
 // MARK: - AppCoordinator
-class AppCoordinator: NSObject, Coordinator {
+class AppCoordinator {
 
     private let window: UIWindow
     private let dcContext: DcContext
@@ -16,91 +15,64 @@ class AppCoordinator: NSObject, Coordinator {
 
     private let appStateRestorer = AppStateRestorer.shared
 
-    private var childCoordinators: [Coordinator] = []
+    // MARK: - login view handling
+    private lazy var loginNavController: UINavigationController = {
+        let nav = UINavigationController() // we change the root, therefore do not set on implicit creation
+        return nav
+    }()
 
+    // MARK: - tabbar view handling
     private lazy var tabBarController: UITabBarController = {
         let tabBarController = UITabBarController()
         tabBarController.delegate = appStateRestorer
-        tabBarController.viewControllers = [qrPageController, chatListController, settingsController]
+        tabBarController.viewControllers = [qrNavController, chatsNavController, settingsNavController]
         tabBarController.tabBar.tintColor = DcColors.primary
         return tabBarController
     }()
 
-    private lazy var loginController: UIViewController = {
-        let accountSetupController = AccountSetupController(dcContext: dcContext, editView: false)
-        let nav = UINavigationController(rootViewController: accountSetupController)
-        let coordinator = AccountSetupCoordinator(dcContext: dcContext, navigationController: nav)
-        coordinator.onLoginSuccess = {
-            [unowned self] in
-            self.loginController.dismiss(animated: true) {
-                self.presentTabBarController()
-            }
-        }
-        childCoordinators.append(coordinator)
-        accountSetupController.coordinator = coordinator
-        return nav
-    }()
-
-    // MARK: viewControllers
-
-    private lazy var qrPageController: UINavigationController = {
-        let pageController = QrPageController(dcContext: dcContext)
-        let nav = UINavigationController(rootViewController: pageController)
-        let coordinator = QrViewCoordinator(navigationController: nav)
-        self.childCoordinators.append(coordinator)
-        pageController.coordinator = coordinator
+    private lazy var qrNavController: UINavigationController = {
+        let root = QrPageController(dcContext: dcContext)
+        let nav = UINavigationController(rootViewController: root)
         let settingsImage = UIImage(named: "qr_code")
         nav.tabBarItem = UITabBarItem(title: String.localized("qr_code"), image: settingsImage, tag: qrTab)
         return nav
     }()
 
-    private lazy var chatListController: UIViewController = {
+    private lazy var chatsNavController: UINavigationController = {
         let viewModel = ChatListViewModel(dcContext: dcContext, isArchive: false)
-        let controller = ChatListController(dcContext: dcContext, viewModel: viewModel)
-        let nav = UINavigationController(rootViewController: controller)
+        let root = ChatListController(dcContext: dcContext, viewModel: viewModel)
+        let nav = UINavigationController(rootViewController: root)
         let settingsImage = UIImage(named: "ic_chat")
         nav.tabBarItem = UITabBarItem(title: String.localized("pref_chats"), image: settingsImage, tag: chatsTab)
-        let coordinator = ChatListCoordinator(dcContext: dcContext, navigationController: nav)
-        self.childCoordinators.append(coordinator)
-        controller.coordinator = coordinator
         return nav
     }()
 
-    private lazy var settingsController: UIViewController = {
-        let controller = SettingsViewController(dcContext: dcContext)
-        let nav = UINavigationController(rootViewController: controller)
+    private lazy var settingsNavController: UINavigationController = {
+        let root = SettingsViewController(dcContext: dcContext)
+        let nav = UINavigationController(rootViewController: root)
         let settingsImage = UIImage(named: "settings")
         nav.tabBarItem = UITabBarItem(title: String.localized("menu_settings"), image: settingsImage, tag: settingsTab)
-        let coordinator = SettingsCoordinator(dcContext: dcContext, navigationController: nav)
-        self.childCoordinators.append(coordinator)
-        controller.coordinator = coordinator
         return nav
     }()
 
-
-    private var welcomeController: WelcomeViewController?
-    private var profileInfoNavigationController: UINavigationController?
-
+    // MARK: - misc
     init(window: UIWindow, dcContext: DcContext) {
         self.window = window
         self.dcContext = dcContext
-        super.init()
 
         if dcContext.isConfigured() {
             presentTabBarController()
         } else {
             presentWelcomeController()
         }
-    }
 
-    public func start() {
         let lastActiveTab = appStateRestorer.restoreLastActiveTab()
         if lastActiveTab == -1 {
             // no stored tab
             showTab(index: chatsTab)
         } else {
             showTab(index: lastActiveTab)
-            if let lastActiveChatId = appStateRestorer.restoreLastActiveChatId(), lastActiveTab == 1 {
+            if let lastActiveChatId = appStateRestorer.restoreLastActiveChatId(), lastActiveTab == chatsTab {
                 // as getChat() returns an empty object for invalid chatId,
                 // check that the returned object is actually set up.
                 if dcContext.getChat(chatId: lastActiveChatId).id == lastActiveChatId {
@@ -116,797 +88,33 @@ class AppCoordinator: NSObject, Coordinator {
 
     func showChat(chatId: Int, animated: Bool = true) {
         showTab(index: chatsTab)
-        guard let navController = self.chatListController as? UINavigationController else {
-            assertionFailure("huh? why no nav controller?")
-            return
-        }
-
-        if let rootController = navController.viewControllers.first as? ChatListController {
-            rootController.coordinator?.showChat(chatId: chatId)
+        if let rootController = self.chatsNavController.viewControllers.first as? ChatListController {
+            rootController.showChat(chatId: chatId, animated: animated)
         }
     }
 
     func handleQRCode(_ code: String) {
         showTab(index: qrTab)
-        if let topViewController = qrPageController.topViewController,
+        if let topViewController = qrNavController.topViewController,
             let qrPageController = topViewController as? QrPageController {
             qrPageController.handleQrCode(code)
         }
     }
 
     func presentWelcomeController() {
+        loginNavController.setViewControllers([WelcomeViewController(dcContext: dcContext)], animated: true)
+        window.rootViewController = loginNavController
+        window.makeKeyAndVisible()
+
         // 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.
         UIApplication.shared.applicationIconBadgeNumber = 0
-
-        let wc = makeWelcomeController()
-        self.welcomeController = wc
-        window.rootViewController = wc
-        window.makeKeyAndVisible()
     }
 
     func presentTabBarController() {
         window.rootViewController = tabBarController
-        welcomeController = nil
-        window.makeKeyAndVisible()
         showTab(index: chatsTab)
+        window.makeKeyAndVisible()
     }
-
-    private func makeWelcomeController() -> WelcomeViewController {
-        let welcomeController = WelcomeViewController(dcContext: dcContext)
-        welcomeController.coordinator = self
-        return welcomeController
-    }
-}
-
-// MARK: - WelcomeCoordinator
-extension AppCoordinator: WelcomeCoordinator {
-
-    func presentLogin() {
-        // add cancel button item to accountSetupController
-        if let nav = loginController as? UINavigationController, let loginController = nav.topViewController as? AccountSetupController {
-            loginController.navigationItem.leftBarButtonItem = UIBarButtonItem(
-                title: String.localized("cancel"),
-                style: .done,
-                target: self, action: #selector(cancelButtonPressed(_:))
-            )
-            loginController.coordinator?.onLoginSuccess = handleLoginSuccess
-        }
-        loginController.modalPresentationStyle = .fullScreen
-        welcomeController?.present(loginController, animated: true, completion: nil)
-    }
-
-    func handleQRAccountCreationSuccess() {
-        let profileInfoController = ProfileInfoViewController(context: dcContext)
-        let profileInfoNav = UINavigationController(rootViewController: profileInfoController)
-        profileInfoNav.modalPresentationStyle = .fullScreen
-        let coordinator = EditSettingsCoordinator(dcContext: dcContext, navigationController: profileInfoNav)
-        profileInfoController.coordinator = coordinator
-        childCoordinators.append(coordinator)
-        profileInfoController.onClose = handleLoginSuccess
-        welcomeController?.present(profileInfoNav, animated: true, completion: nil)
-    }
-
-    func handleLoginSuccess() {
-        presentTabBarController()
-    }
-
-    @objc private func cancelButtonPressed(_ sender: UIBarButtonItem) {
-        loginController.dismiss(animated: true, completion: nil)
-    }
-}
-
-// since mailbox and chatView -tab both use ChatViewController we want to be able to assign different functionality via coordinators -> therefore we override unneeded functions such as showChatDetail -> maybe find better solution in longterm
-class MailboxCoordinator: ChatViewCoordinator {
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        super.init(dcContext: dcContext, navigationController: navigationController, chatId: -1)
-    }
-
-    override func showChatDetail(chatId _: Int) {
-        // ignore for now
-    }
-
-    override func showCameraViewController(delegate: MediaPickerDelegate) {
-        // ignore
-    }
-
-    override func showChat(chatId: Int) {
-        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
-            navigationController.popToRootViewController(animated: false)
-            appDelegate.appCoordinator.showChat(chatId: chatId)
-        }
-    }
-}
-
-class QrViewCoordinator: Coordinator {
-    var navigationController: UINavigationController
-    init(navigationController: UINavigationController) {
-        self.navigationController = navigationController
-    }
-
-    func showChats() {
-        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
-            appDelegate.appCoordinator.showTab(index: appDelegate.appCoordinator.chatsTab)
-        }
-    }
-
-    func showChat(chatId: Int) {
-        if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
-            appDelegate.appCoordinator.showChat(chatId: chatId)
-        }
-    }
-}
-
-class ChatListCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-
-    var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-
-    func showNewChatController() {
-        let newChatVC = NewChatViewController(dcContext: dcContext)
-        let coordinator = NewChatCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        newChatVC.coordinator = coordinator
-        navigationController.pushViewController(newChatVC, animated: true)
-    }
-
-    func showChat(chatId: Int) {
-        let chatVC = ChatViewController(dcContext: dcContext, chatId: chatId)
-        let coordinator = ChatViewCoordinator(dcContext: dcContext, navigationController: navigationController, chatId: chatId)
-        childCoordinators.append(coordinator)
-        chatVC.coordinator = coordinator
-        navigationController.pushViewController(chatVC, animated: true)
-    }
-
-    func showArchive() {
-        let viewModel = ChatListViewModel(dcContext: dcContext, isArchive: true)
-        let controller = ChatListController(dcContext: dcContext, viewModel: viewModel)
-        let coordinator = ChatListCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        controller.coordinator = coordinator
-        navigationController.pushViewController(controller, animated: true)
-    }
-
-    func showNewChat(contactId: Int) {
-        let chatId = dcContext.createChatByContactId(contactId: contactId)
-        showChat(chatId: Int(chatId))
-    }
-}
-
-// MARK: - SettingsCoordinator
-class SettingsCoordinator: Coordinator {
-    let dcContext: DcContext
-    let navigationController: UINavigationController
-
-    var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-
-    func showEditSettingsController() {
-        let editController = EditSettingsController(dcContext: dcContext)
-        let coordinator = EditSettingsCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        editController.coordinator = coordinator
-        navigationController.pushViewController(editController, animated: true)
-    }
-
-    func showClassicMail() {
-        let settingsClassicViewController = SettingsClassicViewController(dcContext: dcContext)
-        navigationController.pushViewController(settingsClassicViewController, animated: true)
-    }
-
-    func showBlockedContacts() {
-        let blockedContactsController = BlockedContactsViewController()
-        navigationController.pushViewController(blockedContactsController, animated: true)
-    }
-
-    func showAutodelOptions() {
-        let settingsAutodelOverviewController = SettingsAutodelOverviewController(dcContext: dcContext)
-        navigationController.pushViewController(settingsAutodelOverviewController, animated: true)
-    }
-
-    func showContactRequests() {
-        let deaddropViewController = MailboxViewController(dcContext: dcContext, chatId: Int(DC_CHAT_ID_DEADDROP))
-        let deaddropCoordinator = MailboxCoordinator(dcContext: dcContext, navigationController: navigationController)
-        deaddropViewController.coordinator = deaddropCoordinator
-        childCoordinators.append(deaddropCoordinator)
-        navigationController.pushViewController(deaddropViewController, animated: true)
-    }
-
-    func showHelp() {
-        let helpViewController = HelpViewController()
-        navigationController.pushViewController(helpViewController, animated: true)
-    }
-
-    func showDebugToolkit() {
-        DBDebugToolkit.setup(with: [])  // emtpy array will override default device shake trigger
-        DBDebugToolkit.setupCrashReporting()
-        let info: [DBCustomVariable] = dcContext.getInfo().map { kv in
-            let value = kv.count > 1 ? kv[1] : ""
-            return DBCustomVariable(name: kv[0], value: value)
-        }
-        DBDebugToolkit.add(info)
-        DBDebugToolkit.showMenu()
-    }
-}
-
-// MARK: - EditSettingsCoordinator
-class EditSettingsCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-    let mediaPicker: MediaPicker
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-        self.mediaPicker = MediaPicker(navigationController: navigationController)
-    }
-
-    func showPhotoPicker(delegate: MediaPickerDelegate) {
-        mediaPicker.showPhotoGallery(delegate: delegate)
-    }
-
-    func showCamera(delegate: MediaPickerDelegate) {
-        mediaPicker.showCamera(delegate: delegate)
-    }
-}
-
-// MARK: - AccountSetupCoordinator
-class AccountSetupCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-    var onLoginSuccess: (() -> Void)?
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-
-    func showCertCheckOptions() {
-        let certificateCheckController = CertificateCheckController(dcContext: dcContext, sectionTitle: String.localized("login_certificate_checks"))
-        navigationController.pushViewController(certificateCheckController, animated: true)
-    }
-
-    func showImapSecurityOptions() {
-        let securitySettingsController = SecuritySettingsController(dcContext: dcContext, title: String.localized("login_imap_security"),
-                                                                      type: SecurityType.IMAPSecurity)
-        navigationController.pushViewController(securitySettingsController, animated: true)
-    }
-
-    func showSmptpSecurityOptions() {
-        let securitySettingsController = SecuritySettingsController(dcContext: dcContext,
-                                                                    title: String.localized("login_imap_security"),
-                                                                    type: SecurityType.SMTPSecurity)
-        navigationController.pushViewController(securitySettingsController, animated: true)
-    }
-
-    func openProviderInfo(provider: DcProvider) {
-        guard let url = URL(string: provider.getOverviewPage) else { return }
-        UIApplication.shared.open(url)
-    }
-
-    func navigateBack() {
-        navigationController.popViewController(animated: true)
-    }
-}
-
-// MARK: - NewChatCoordinator
-class NewChatCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-
-    private var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-
-    func showNewGroupController(isVerified: Bool) {
-        let newGroupController = NewGroupController(dcContext: dcContext, isVerified: isVerified)
-        let coordinator = NewGroupCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        newGroupController.coordinator = coordinator
-        navigationController.pushViewController(newGroupController, animated: true)
-    }
-
-    func showNewContactController() {
-        let newContactController = NewContactController(dcContext: dcContext)
-        let coordinator = EditContactCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        newContactController.coordinator = coordinator
-        navigationController.pushViewController(newContactController, animated: true)
-    }
-
-    func showNewChat(contactId: Int) {
-        let chatId = dcContext.createChatByContactId(contactId: contactId)
-        showChat(chatId: Int(chatId))
-    }
-
-    func showChat(chatId: Int) {
-        let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
-        let coordinator = ChatViewCoordinator(dcContext: dcContext, navigationController: navigationController, chatId: chatId)
-        childCoordinators.append(coordinator)
-        chatViewController.coordinator = coordinator
-        navigationController.pushViewController(chatViewController, animated: true)
-        navigationController.viewControllers.remove(at: 1)
-    }
-
-    func showContactDetail(contactId: Int) {
-        let viewModel = ContactDetailViewModel(contactId: contactId, chatId: nil, context: dcContext)
-        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
-        let coordinator = ContactDetailCoordinator(dcContext: dcContext, chatId: nil, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        contactDetailController.coordinator = coordinator
-        navigationController.pushViewController(contactDetailController, animated: true)
-    }
-    
-}
-
-// MARK: - GroupChatDetailCoordinator
-class GroupChatDetailCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-    let chatId: Int
-
-    private var childCoordinators: [Coordinator] = []
-    private var previewController: PreviewController?
-
-    init(dcContext: DcContext, chatId: Int, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.chatId = chatId
-        self.navigationController = navigationController
-    }
-
-    func showSingleChatEdit(contactId: Int) {
-        let editContactController = EditContactController(dcContext: dcContext, contactIdForUpdate: contactId)
-        let coordinator = EditContactCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        editContactController.coordinator = coordinator
-        navigationController.pushViewController(editContactController, animated: true)
-    }
-
-    func showAddGroupMember(chatId: Int) {
-        let groupMemberViewController = AddGroupMembersViewController(chatId: chatId)
-        let coordinator = AddGroupMembersCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        groupMemberViewController.coordinator = coordinator
-        navigationController.pushViewController(groupMemberViewController, animated: true)
-    }
-
-    func showQrCodeInvite(chatId: Int) {
-        let qrInviteCodeController = QrInviteViewController(dcContext: dcContext, chatId: chatId)
-        navigationController.pushViewController(qrInviteCodeController, animated: true)
-    }
-
-    func showGroupChatEdit(chat: DcChat) {
-        let editGroupViewController = EditGroupViewController(chat: chat)
-        let coordinator = EditGroupCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        editGroupViewController.coordinator = coordinator
-        navigationController.pushViewController(editGroupViewController, animated: true)
-    }
-
-    func showContactDetail(of contactId: Int) {
-        let viewModel = ContactDetailViewModel(contactId: contactId, chatId: nil, context: dcContext)
-        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
-        let coordinator = ContactDetailCoordinator(dcContext: dcContext, chatId: nil, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        contactDetailController.coordinator = coordinator
-        navigationController.pushViewController(contactDetailController, animated: true)
-    }
-
-    func showDocuments() {
-        presentPreview(for: DC_MSG_FILE, messageType2: DC_MSG_AUDIO, messageType3: 0)
-    }
-
-    func showGallery() {
-        presentPreview(for: DC_MSG_IMAGE, messageType2: DC_MSG_GIF, messageType3: DC_MSG_VIDEO)
-    }
-
-    private func presentPreview(for messageType: Int32, messageType2: Int32, messageType3: Int32) {
-        let messageIds = dcContext.getChatMedia(chatId: chatId, messageType: messageType, messageType2: messageType2, messageType3: messageType3)
-        var mediaUrls: [URL] = []
-        for messageId in messageIds {
-            let message = DcMsg.init(id: messageId)
-            if let url = message.fileURL {
-                mediaUrls.insert(url, at: 0)
-            }
-        }
-        previewController = PreviewController(currentIndex: 0, urls: mediaUrls)
-        if let previewController = previewController {
-            navigationController.pushViewController(previewController, animated: true)
-        }
-    }
-
-    func deleteChat() {
-        /*
-        app will navigate to chatlist or archive and delete the chat there
-        notify chatList/archiveList to delete chat AFTER is is visible
-        */
-        func notifyToDeleteChat() {
-            NotificationCenter.default.post(name: dcNotificationChatDeletedInChatDetail, object: nil, userInfo: ["chat_id": self.chatId])
-        }
-
-        func showArchive() {
-            self.navigationController.popToRootViewController(animated: false) // in main ChatList now
-            let viewModel = ChatListViewModel(dcContext: dcContext, isArchive: true)
-            let controller = ChatListController(dcContext: dcContext, viewModel: viewModel)
-            let coordinator = ChatListCoordinator(dcContext: dcContext, navigationController: navigationController)
-            childCoordinators.append(coordinator)
-            controller.coordinator = coordinator
-            navigationController.pushViewController(controller, animated: false)
-        }
-
-        CATransaction.begin()
-        CATransaction.setCompletionBlock(notifyToDeleteChat)
-
-        let chat = dcContext.getChat(chatId: chatId)
-        if chat.isArchived {
-            showArchive()
-        } else {
-            self.navigationController.popToRootViewController(animated: true) // in main ChatList now
-        }
-        CATransaction.commit()
-    }
-}
-
-// MARK: - ChatViewCoordinator
-class ChatViewCoordinator: NSObject, Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-    let chatId: Int
-    var chatViewController: ChatViewController!
-
-    var childCoordinators: [Coordinator] = []
-    let mediaPicker: MediaPicker
-
-    init(dcContext: DcContext, navigationController: UINavigationController, chatId: Int) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-        self.chatId = chatId
-        self.mediaPicker = MediaPicker(navigationController: self.navigationController)
-    }
-
-    func navigateBack() {
-        navigationController.popViewController(animated: true)
-    }
-
-    func showChatDetail(chatId: Int) {
-        let chat = dcContext.getChat(chatId: chatId)
-        switch chat.chatType {
-        case .SINGLE:
-            if let contactId = chat.contactIds.first {
-                let viewModel = ContactDetailViewModel(contactId: contactId, chatId: chatId, context: dcContext)
-                let contactDetailController = ContactDetailViewController(viewModel: viewModel)
-                let coordinator = ContactDetailCoordinator(dcContext: dcContext, chatId: chatId, navigationController: navigationController)
-                childCoordinators.append(coordinator)
-                contactDetailController.coordinator = coordinator
-                navigationController.pushViewController(contactDetailController, animated: true)
-            }
-        case .GROUP, .VERIFIEDGROUP:
-            let groupChatDetailViewController = GroupChatDetailViewController(chatId: chatId, context: dcContext) // inherits from ChatDetailViewController
-            let coordinator = GroupChatDetailCoordinator(dcContext: dcContext, chatId: chatId, navigationController: navigationController)
-            childCoordinators.append(coordinator)
-            groupChatDetailViewController.coordinator = coordinator
-            navigationController.pushViewController(groupChatDetailViewController, animated: true)
-        }
-    }
-
-    func showContactDetail(of contactId: Int, in chatOfType: ChatType, chatId: Int?) {
-        let viewModel = ContactDetailViewModel(contactId: contactId, chatId: chatId, context: dcContext )
-        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
-        let coordinator = ContactDetailCoordinator(dcContext: dcContext, chatId: chatId, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        contactDetailController.coordinator = coordinator
-        navigationController.pushViewController(contactDetailController, animated: true)
-    }
-
-    func showChat(chatId: Int) {
-        let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
-        let coordinator = ChatViewCoordinator(dcContext: dcContext, navigationController: navigationController, chatId: chatId)
-        childCoordinators.append(coordinator)
-        chatViewController.coordinator = coordinator
-        navigationController.popToRootViewController(animated: false)
-        navigationController.pushViewController(chatViewController, animated: true)
-    }
-
-    func showDocumentLibrary(delegate: MediaPickerDelegate) {
-        mediaPicker.showDocumentLibrary(delegate: delegate)
-    }
-
-    func showVoiceMessageRecorder(delegate: MediaPickerDelegate) {
-        mediaPicker.showVoiceRecorder(delegate: delegate)
-    }
-
-    func showCameraViewController(delegate: MediaPickerDelegate) {
-        mediaPicker.showCamera(delegate: delegate, allowCropping: false)
-    }
-
-    func showPhotoVideoLibrary(delegate: MediaPickerDelegate) {
-        mediaPicker.showPhotoVideoLibrary(delegate: delegate)
-    }
-
-    func showMediaGallery(currentIndex: Int, mediaUrls urls: [URL]) {
-        let betterPreviewController = PreviewController(currentIndex: currentIndex, urls: urls)
-        let nav = UINavigationController(rootViewController: betterPreviewController)
-        nav.modalPresentationStyle = .fullScreen
-        navigationController.present(nav, animated: true)
-    }
-}
-
-// MARK: - NewGroupAddMembersCoordinator
-class NewGroupAddMembersCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-
-    private var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-}
-
-// MARK: - AddGroupMembersCoordinator
-class AddGroupMembersCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-
-    private var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-
-    func showNewContactController() {
-        let newContactController = NewContactController(dcContext: dcContext)
-        newContactController.openChatOnSave = false
-        let coordinator = EditContactCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        newContactController.coordinator = coordinator
-        navigationController.pushViewController(newContactController, animated: true)
-    }
-}
-
-// MARK: - NewGroupCoordinator
-class NewGroupCoordinator: Coordinator {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-    let mediaPicker: MediaPicker
-
-    private var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-        self.mediaPicker = MediaPicker(navigationController: self.navigationController)
-    }
-
-    func showGroupChat(chatId: Int) {
-        let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
-        let coordinator = ChatViewCoordinator(dcContext: dcContext, navigationController: navigationController, chatId: chatId)
-        childCoordinators.append(coordinator)
-        chatViewController.coordinator = coordinator
-        navigationController.popToRootViewController(animated: false)
-        navigationController.pushViewController(chatViewController, animated: true)
-    }
-
-    func showPhotoPicker(delegate: MediaPickerDelegate) {
-        mediaPicker.showPhotoGallery(delegate: delegate)
-    }
-
-    func showCamera(delegate: MediaPickerDelegate) {
-        mediaPicker.showCamera(delegate: delegate)
-    }
-
-    func showQrCodeInvite(chatId: Int) {
-        let qrInviteCodeController = QrInviteViewController(dcContext: dcContext, chatId: chatId)
-        qrInviteCodeController.onDismissed = onQRInviteCodeControllerDismissed
-        navigationController.pushViewController(qrInviteCodeController, animated: true)
-    }
-
-    func showAddMembers(preselectedMembers: Set<Int>, isVerified: Bool) {
-        let newGroupController = NewGroupAddMembersViewController(preselected: preselectedMembers,
-                                                                  isVerified: isVerified)
-        let coordinator = NewGroupAddMembersCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        newGroupController.coordinator = coordinator
-        newGroupController.onMembersSelected = onGroupMembersSelected(_:)
-        navigationController.pushViewController(newGroupController, animated: true)
-    }
-
-    func onQRInviteCodeControllerDismissed() {
-        if let groupNameController = navigationController.topViewController as? NewGroupController {
-            groupNameController.updateGroupContactIdsOnQRCodeInvite()
-        }
-    }
-
-    func onGroupMembersSelected(_ memberIds: Set<Int>) {
-        navigationController.popViewController(animated: true)
-        if let groupNameController = navigationController.topViewController as? NewGroupController {
-            groupNameController.updateGroupContactIdsOnListSelection(memberIds)
-        }
-    }
-}
-
-// MARK: - ContactDetailCoordinator
-class ContactDetailCoordinator: Coordinator, ContactDetailCoordinatorProtocol {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-    var previewController: PreviewController?
-    let chatId: Int?
-
-    private var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, chatId: Int?, navigationController: UINavigationController) {
-        self.chatId = chatId
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-
-    func showChat(chatId: Int) {
-        let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
-        let coordinator = ChatViewCoordinator(dcContext: dcContext, navigationController: navigationController, chatId: chatId)
-        childCoordinators.append(coordinator)
-        chatViewController.coordinator = coordinator
-        navigationController.popToRootViewController(animated: false)
-        navigationController.pushViewController(chatViewController, animated: true)
-    }
-
-    func showEditContact(contactId: Int) {
-        let editContactController = EditContactController(dcContext: dcContext, contactIdForUpdate: contactId)
-        let coordinator = EditContactCoordinator(dcContext: dcContext, navigationController: navigationController)
-        childCoordinators.append(coordinator)
-        editContactController.coordinator = coordinator
-        navigationController.pushViewController(editContactController, animated: true)
-    }
-
-    func showDocuments() {
-        presentPreview(for: DC_MSG_FILE, messageType2: DC_MSG_AUDIO, messageType3: 0)
-    }
-
-    func showGallery() {
-        presentPreview(for: DC_MSG_IMAGE, messageType2: DC_MSG_GIF, messageType3: DC_MSG_VIDEO)
-    }
-
-    private func presentPreview(for messageType: Int32, messageType2: Int32, messageType3: Int32) {
-        guard let chatId = self.chatId else { return }
-        let messageIds = dcContext.getChatMedia(chatId: chatId, messageType: messageType, messageType2: messageType2, messageType3: messageType3)
-        var mediaUrls: [URL] = []
-        for messageId in messageIds {
-            let message = DcMsg.init(id: messageId)
-            if let url = message.fileURL {
-                mediaUrls.insert(url, at: 0)
-            }
-        }
-        previewController = PreviewController(currentIndex: 0, urls: mediaUrls)
-        if let previewController = previewController {
-            navigationController.pushViewController(previewController, animated: true)
-        }
-    }
-
-
-    func deleteChat() {
-        guard let chatId = chatId else {
-            return
-        }
-
-        /*
-        app will navigate to chatlist or archive and delete the chat there
-        notify chatList/archiveList to delete chat AFTER is is visible
-        */
-        func notifyToDeleteChat() {
-            NotificationCenter.default.post(name: dcNotificationChatDeletedInChatDetail, object: nil, userInfo: ["chat_id": chatId])
-        }
-
-        func showArchive() {
-            self.navigationController.popToRootViewController(animated: false) // in main ChatList now
-            let viewModel = ChatListViewModel(dcContext: dcContext, isArchive: true)
-            let controller = ChatListController(dcContext: dcContext, viewModel: viewModel)
-            let coordinator = ChatListCoordinator(dcContext: dcContext, navigationController: navigationController)
-            childCoordinators.append(coordinator)
-            controller.coordinator = coordinator
-            navigationController.pushViewController(controller, animated: false)
-        }
-
-        CATransaction.begin()
-        CATransaction.setCompletionBlock(notifyToDeleteChat)
-
-        let chat = dcContext.getChat(chatId: chatId)
-        if chat.isArchived {
-            showArchive()
-        } else {
-            self.navigationController.popToRootViewController(animated: true) // in main ChatList now
-        }
-        CATransaction.commit()
-    }
-
-}
-
-// MARK: - EditGroupCoordinator
-class EditGroupCoordinator: Coordinator {
-    let navigationController: UINavigationController
-    let dcContext: DcContext
-    let mediaPicker: MediaPicker
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-        mediaPicker = MediaPicker(navigationController: self.navigationController)
-    }
-
-    func showPhotoPicker(delegate: MediaPickerDelegate) {
-        mediaPicker.showPhotoGallery(delegate: delegate)
-    }
-
-    func showCamera(delegate: MediaPickerDelegate) {
-        mediaPicker.showCamera(delegate: delegate)
-    }
-
-    func navigateBack() {
-        navigationController.popViewController(animated: true)
-    }
-}
-
-// MARK: - EditContactCoordinator
-class EditContactCoordinator: Coordinator, EditContactCoordinatorProtocol {
-    var dcContext: DcContext
-    let navigationController: UINavigationController
-
-    var childCoordinators: [Coordinator] = []
-
-    init(dcContext: DcContext, navigationController: UINavigationController) {
-        self.dcContext = dcContext
-        self.navigationController = navigationController
-    }
-
-    func navigateBack() {
-        navigationController.popViewController(animated: true)
-    }
-
-    func showChat(chatId: Int) {
-        let chatViewController = ChatViewController(dcContext: dcContext, chatId: chatId)
-        let coordinator = ChatViewCoordinator(dcContext: dcContext, navigationController: navigationController, chatId: chatId)
-        coordinator.chatViewController = chatViewController
-        childCoordinators.append(coordinator)
-        chatViewController.coordinator = coordinator
-        navigationController.popToRootViewController(animated: false)
-        navigationController.pushViewController(chatViewController, animated: true)
-    }
-}
-
-/*
- boilerplate - I tend to remove that interface (cyberta)
- */
-// MARK: - coordinator protocols
-protocol ContactDetailCoordinatorProtocol: class {
-    func showEditContact(contactId: Int)
-    func showChat(chatId: Int)
-    func deleteChat()
-    func showDocuments()
-    func showGallery()
-}
-
-protocol EditContactCoordinatorProtocol: class {
-    func navigateBack()
-    func showChat(chatId: Int)
-}
-
-protocol WelcomeCoordinator: class {
-    func presentLogin()
-    func handleLoginSuccess()
-    func handleQRAccountCreationSuccess()
 }

+ 16 - 16
deltachat-ios/Helper/MediaPicker.swift

@@ -28,14 +28,14 @@ extension MediaPickerDelegate {
 
 class MediaPicker: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate, AudioRecorderControllerDelegate, UIDocumentPickerDelegate {
 
-    private let navigationController: UINavigationController
+    private weak var navigationController: UINavigationController?
     private weak var delegate: MediaPickerDelegate?
 
-    init(navigationController: UINavigationController) {
+    init(navigationController: UINavigationController?) {
+        // it does not make sense to give nil here, but it makes construction easier
         self.navigationController = navigationController
     }
 
-
     func showVoiceRecorder(delegate: MediaPickerDelegate) {
         self.delegate = delegate
         let audioRecorderController = AudioRecorderController()
@@ -43,8 +43,8 @@ class MediaPicker: NSObject, UINavigationControllerDelegate, UIImagePickerContro
         //audioRecorderController.maximumRecordDuration = 1200
         let audioRecorderNavController = UINavigationController(rootViewController: audioRecorderController)
 
-        navigationController.present(audioRecorderNavController, animated: true, completion: nil)
- }
+        navigationController?.present(audioRecorderNavController, animated: true, completion: nil)
+    }
 
     func showPhotoVideoLibrary(delegate: MediaPickerDelegate) {
         if PHPhotoLibrary.authorizationStatus() != .authorized {
@@ -71,7 +71,7 @@ class MediaPicker: NSObject, UINavigationControllerDelegate, UIImagePickerContro
         documentPicker.allowsMultipleSelection = false
         documentPicker.modalPresentationStyle = .formSheet
         self.delegate = delegate
-        navigationController.present(documentPicker, animated: true, completion: nil)
+        navigationController?.present(documentPicker, animated: true, completion: nil)
     }
 
     func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
@@ -91,7 +91,7 @@ class MediaPicker: NSObject, UINavigationControllerDelegate, UIImagePickerContro
                 kUTTypeImage as String
             ]
             self.delegate = delegate
-            navigationController.present(videoPicker, animated: true, completion: nil)
+            navigationController?.present(videoPicker, animated: true, completion: nil)
         }
     }
 
@@ -109,10 +109,10 @@ class MediaPicker: NSObject, UINavigationControllerDelegate, UIImagePickerContro
                 if let image = image {
                     self?.delegate?.onImageSelected(image: image)
                 }
-                self?.navigationController.dismiss(animated: true, completion: nil)})
+                self?.navigationController?.dismiss(animated: true, completion: nil)})
         self.delegate = delegate
         controller.modalPresentationStyle = .fullScreen
-        navigationController.present(controller, animated: true, completion: nil)
+        navigationController?.present(controller, animated: true, completion: nil)
     }
 
     func showCamera(delegate: MediaPickerDelegate, allowCropping: Bool) {
@@ -134,17 +134,17 @@ class MediaPicker: NSObject, UINavigationControllerDelegate, UIImagePickerContro
                     if let image = image {
                         self?.delegate?.onImageSelected(image: image)
                     }
-                    self?.navigationController.dismiss(animated: true, completion: nil)}
+                    self?.navigationController?.dismiss(animated: true, completion: nil)}
             )
             self.delegate = delegate
             cameraViewController.modalPresentationStyle = .fullScreen
-            navigationController.present(cameraViewController, animated: true, completion: nil)
+            navigationController?.present(cameraViewController, animated: true, completion: nil)
         } else {
             let alert = UIAlertController(title: String.localized("chat_camera_unavailable"), message: nil, preferredStyle: .alert)
             alert.addAction(UIAlertAction(title: String.localized("ok"), style: .cancel, handler: { _ in
-                self.navigationController.dismiss(animated: true, completion: nil)
+                self.navigationController?.dismiss(animated: true, completion: nil)
             }))
-            navigationController.present(alert, animated: true, completion: nil)
+            navigationController?.present(alert, animated: true, completion: nil)
         }
     }
 
@@ -161,15 +161,15 @@ class MediaPicker: NSObject, UINavigationControllerDelegate, UIImagePickerContro
                     logger.error(error.localizedDescription)
                     let alert = UIAlertController(title: String.localized("error"), message: nil, preferredStyle: .alert)
                     alert.addAction(UIAlertAction(title: String.localized("ok"), style: .cancel, handler: { _ in
-                        self.navigationController.dismiss(animated: true, completion: nil)
+                        self.navigationController?.dismiss(animated: true, completion: nil)
                     }))
-                    self.navigationController.present(alert, animated: true, completion: nil)
+                    self.navigationController?.present(alert, animated: true, completion: nil)
                 }
             })
         } else if let imageUrl = info[UIImagePickerController.InfoKey.imageURL] as? NSURL {
             self.delegate?.onImageSelected(url: imageUrl)
         }
-        navigationController.dismiss(animated: true, completion: nil)
+        navigationController?.dismiss(animated: true, completion: nil)
     }
 
     func didFinishAudioAtPath(path: String) {

+ 0 - 10
deltachat-ios/Helper/Protocols.swift

@@ -1,10 +1,5 @@
 import UIKit
 
-protocol Coordinator: class {
-    // var rootViewController: UIViewController { get }
-    // func start()
-}
-
 protocol QrCodeReaderDelegate: class {
     func handleQrCode(_ code: String)
 }
@@ -14,8 +9,3 @@ protocol ContactListDelegate: class {
     func accessDenied()
     func deviceContactsImported()
 }
-
-protocol ChatDisplayer: class {
-    func displayNewChat(contactId: Int)
-    func displayChatForId(chatId: Int)
-}

+ 0 - 82
deltachat-ios/NewGroupMemberChoiceController.swift

@@ -1,82 +0,0 @@
-import UIKit
-
-/*
- class ViewController: UIViewController {
- override func viewDidLoad() {
-   super.viewDidLoad()
-
-   let n: CGFloat = 150
-   let l: CGFloat = 40
-   let generalView = UIView()
-   let square = UIView()
-   square.layer.cornerRadius = n / 2
-   let nameLabel = UILabel()
-   nameLabel.text = "Alic Doe"
-   square.translatesAutoresizingMaskIntoConstraints = false
-   nameLabel.translatesAutoresizingMaskIntoConstraints = false
-   generalView.translatesAutoresizingMaskIntoConstraints = false
-
-   view.addSubview(generalView)
-   view.addSubview(square)
-   view.addSubview(nameLabel)
-   generalView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
-   generalView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
-   square.centerXAnchor.constraint(equalTo: generalView.centerXAnchor).isActive = true
-   square.centerYAnchor.constraint(equalTo: generalView.centerYAnchor).isActive = true
-   nameLabel.topAnchor.constraint(equalTo: square.bottomAnchor).isActive = true
-   nameLabel.leadingAnchor.constraint(equalTo: square.leadingAnchor).isActive = true
-
-   square.widthAnchor.constraint(equalToConstant: n).isActive = true
-   square.heightAnchor.constraint(equalToConstant: n).isActive = true
-   nameLabel.widthAnchor.constraint(equalToConstant: n).isActive = true
-   nameLabel.heightAnchor.constraint(equalToConstant: l).isActive = true
-   generalView.widthAnchor.constraint(equalToConstant: n).isActive = true
-   generalView.heightAnchor.constraint(equalToConstant: n + l).isActive = true
-   square.backgroundColor = UIColor.blue
-   nameLabel.backgroundColor = UIColor.green
-   generalView.backgroundColor = UIColor.cyan
-   nameLabel.textColor = UIColor.white
-   nameLabel.font = UIFont.systemFont(ofSize: 14)
-
-   let deleteButton = UIButton()
-   deleteButton.translatesAutoresizingMaskIntoConstraints = false
-
-   let sin45: CGFloat = 0.7071
-   let squareRadius: CGFloat = n / 2
-   let deltaX: CGFloat = sin45 * squareRadius
-   let deltaY: CGFloat = squareRadius - deltaX
-   let deleteButtonWidth: CGFloat = deltaX
-   let deleteButtonHeight: CGFloat = deltaX
-   deleteButton.layer.cornerRadius = deleteButtonWidth / 2
-
-   deleteButton.widthAnchor.constraint(equalToConstant: deleteButtonWidth).isActive = true
-   deleteButton.heightAnchor.constraint(equalToConstant: deleteButtonHeight).isActive = true
-   deleteButton.backgroundColor = UIColor.gray
-   deleteButton.clipsToBounds = true
-
-   deleteButton.layer.borderWidth = 3
-   deleteButton.layer.borderColor = UIColor.white.cgColor
-   deleteButton.setTitle("✕", for: .normal)
-   deleteButton.titleLabel?.font = UIFont.systemFont(ofSize: 30)
-
-   deleteButton.addTarget(self, action: #selector(didPressDeleteButton), for: .touchUpInside)
-
-   square.addSubview(deleteButton)
-   deleteButton.centerYAnchor.constraint(equalTo: square.topAnchor, constant: deltaY).isActive = true
-   deleteButton.centerXAnchor.constraint(equalTo: square.centerXAnchor, constant: deltaX).isActive = true
- }
-
- @objc func didPressDeleteButton() {
-   if view.backgroundColor == UIColor.red {
-     view.backgroundColor = UIColor.white
-   } else {
-     view.backgroundColor = UIColor.red
-   }
- }
-
- override func didReceiveMemoryWarning() {
-   super.didReceiveMemoryWarning()
- }
- }
-
- */

+ 2 - 1
deltachat-ios/ViewModel/ContactDetailViewModel.swift

@@ -4,6 +4,7 @@ import DcCore
 protocol ContactDetailViewModelProtocol {
     var context: DcContext { get }
     var contactId: Int { get }
+    var chatId: Int? { get }
     var contact: DcContact { get }
     var numberOfSections: Int { get }
     var chatIsArchived: Bool { get }
@@ -45,7 +46,7 @@ class ContactDetailViewModel: ContactDetailViewModelProtocol {
         return DcContact(id: contactId)
     }
 
-    private let chatId: Int?
+    let chatId: Int?
     private let sharedChats: DcChatlist
     private var sections: [ProfileSections] = []
     private var chatActions: [ChatAction] = [] // chatDetail: archive, block, delete - else: block