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

run 'swiftformat --rules indent *'

B. Petersen 5 жил өмнө
parent
commit
85f190afb9
50 өөрчлөгдсөн 8050 нэмэгдсэн , 8050 устгасан
  1. 248 248
      deltachat-ios/AppDelegate.swift
  2. 155 155
      deltachat-ios/Controller/AccountSetup/PortSettingsController.swift
  3. 146 146
      deltachat-ios/Controller/AccountSetup/SecuritySettingsController.swift
  4. 662 662
      deltachat-ios/Controller/AccountSetupController.swift
  5. 178 178
      deltachat-ios/Controller/ChatListController.swift
  6. 1920 1920
      deltachat-ios/Controller/ChatViewController.swift
  7. 144 144
      deltachat-ios/Controller/ContactDetailViewController.swift
  8. 235 235
      deltachat-ios/Controller/ContactListController.swift
  9. 51 51
      deltachat-ios/Controller/DCNavigationController.swift
  10. 29 29
      deltachat-ios/Controller/EditContactController.swift
  11. 51 51
      deltachat-ios/Controller/EditGroupViewController.swift
  12. 61 61
      deltachat-ios/Controller/EditSettingsController.swift
  13. 213 213
      deltachat-ios/Controller/GroupChatDetailViewController.swift
  14. 123 123
      deltachat-ios/Controller/GroupMembersViewController.swift
  15. 81 81
      deltachat-ios/Controller/GroupNameController.swift
  16. 11 11
      deltachat-ios/Controller/MailboxViewController.swift
  17. 91 91
      deltachat-ios/Controller/MessageInfoViewController.swift
  18. 327 327
      deltachat-ios/Controller/NewChatViewController.swift
  19. 102 102
      deltachat-ios/Controller/NewContactController.swift
  20. 14 14
      deltachat-ios/Controller/PreviewController.swift
  21. 121 121
      deltachat-ios/Controller/ProfileViewController.swift
  22. 63 63
      deltachat-ios/Controller/QrCodeReaderController.swift
  23. 219 219
      deltachat-ios/Controller/SettingsController.swift
  24. 551 551
      deltachat-ios/Coordinator/AppCoordinator.swift
  25. 803 803
      deltachat-ios/DC/Wrapper.swift
  26. 162 162
      deltachat-ios/DC/events.swift
  27. 58 58
      deltachat-ios/Handler/DeviceContactsHandler.swift
  28. 54 54
      deltachat-ios/Handler/HudHandler.swift
  29. 38 38
      deltachat-ios/Helper/Colors.swift
  30. 13 13
      deltachat-ios/Helper/Constants.swift
  31. 194 194
      deltachat-ios/Helper/Extensions.swift
  32. 8 8
      deltachat-ios/Helper/Protocols.swift
  33. 18 18
      deltachat-ios/Helper/UIImage+Extension.swift
  34. 156 156
      deltachat-ios/Helper/Utils.swift
  35. 6 6
      deltachat-ios/Model/Location.swift
  36. 13 13
      deltachat-ios/Model/Media.swift
  37. 39 39
      deltachat-ios/Model/Message.swift
  38. 37 37
      deltachat-ios/View/ActionCell.swift
  39. 47 47
      deltachat-ios/View/ChatTitleView.swift
  40. 219 219
      deltachat-ios/View/ContactCell.swift
  41. 14 14
      deltachat-ios/View/ContactDetailHeader.swift
  42. 32 32
      deltachat-ios/View/CustomMessageCell.swift
  43. 59 59
      deltachat-ios/View/GroupNameCell.swift
  44. 44 44
      deltachat-ios/View/InitialsBadge.swift
  45. 3 3
      deltachat-ios/View/PhotoPickerAlertAction.swift
  46. 36 36
      deltachat-ios/View/ProgressHud.swift
  47. 35 35
      deltachat-ios/View/QrCodeView.swift
  48. 107 107
      deltachat-ios/View/TextFieldCell.swift
  49. 23 23
      deltachat-ios/View/TextFieldTableViewCell.swift
  50. 36 36
      deltachat-iosTests/deltachat_iosTests.swift

+ 248 - 248
deltachat-ios/AppDelegate.swift

@@ -9,302 +9,302 @@ var mailboxPointer: OpaquePointer!
 let logger = SwiftyBeaver.self
 
 enum ApplicationState {
-  case stopped
-  case running
-  case background
-  case backgroundFetch
+    case stopped
+    case running
+    case background
+    case backgroundFetch
 }
 
 @UIApplicationMain
 class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {
-  var appCoordinator: AppCoordinator!
-  // static let appCoordinatorDeprecated = AppCoordinatorDeprecated()
-  static var progress: Float = 0 // TODO: delete
-  static var lastErrorDuringConfig: String?
-  private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
-
-  var reachability = Reachability()!
-  var window: UIWindow?
-
-  var state = ApplicationState.stopped
-
-  private func getCoreInfo() -> [[String]] {
-    if let cInfo = dc_get_info(mailboxPointer) {
-      let info = String(cString: cInfo)
-      logger.info(info)
-      return info.components(separatedBy: "\n").map { val in
-        val.components(separatedBy: "=")
-      }
-    }
+    var appCoordinator: AppCoordinator!
+    // static let appCoordinatorDeprecated = AppCoordinatorDeprecated()
+    static var progress: Float = 0 // TODO: delete
+    static var lastErrorDuringConfig: String?
+    private var backgroundTask: UIBackgroundTaskIdentifier = .invalid
+
+    var reachability = Reachability()!
+    var window: UIWindow?
+
+    var state = ApplicationState.stopped
+
+    private func getCoreInfo() -> [[String]] {
+        if let cInfo = dc_get_info(mailboxPointer) {
+            let info = String(cString: cInfo)
+            logger.info(info)
+            return info.components(separatedBy: "\n").map { val in
+                val.components(separatedBy: "=")
+            }
+        }
 
-    return []
-  }
+        return []
+    }
 
-  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])
+    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])
+        }
+        return true
     }
-    return true
-  }
 
-  func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
-    DBDebugToolkit.setup()
-    DBDebugToolkit.setupCrashReporting()
+    func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+        DBDebugToolkit.setup()
+        DBDebugToolkit.setupCrashReporting()
 
-    let console = ConsoleDestination()
-    logger.addDestination(console)
+        let console = ConsoleDestination()
+        logger.addDestination(console)
 
-    logger.info("launching")
+        logger.info("launching")
 
-    // Override point for customization after application launch.
+        // 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")
-    }
-    // setup deltachat core context
-    //       - second param remains nil (user data for more than one mailbox)
-    open()
-    let isConfigured = dc_is_configured(mailboxPointer) != 0
-    appCoordinator = AppCoordinator(window: window)
-    appCoordinator.start()
-    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
-    start()
-    registerForPushNotifications()
-    if !isConfigured {
-      appCoordinator.presentLoginController()
+        window = UIWindow(frame: UIScreen.main.bounds)
+        guard let window = window else {
+            fatalError("window was nil in app delegate")
+        }
+        // setup deltachat core context
+        //       - second param remains nil (user data for more than one mailbox)
+        open()
+        let isConfigured = dc_is_configured(mailboxPointer) != 0
+        appCoordinator = AppCoordinator(window: window)
+        appCoordinator.start()
+        UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
+        start()
+        registerForPushNotifications()
+        if !isConfigured {
+            appCoordinator.presentLoginController()
+        }
+        return true
     }
-    return true
-  }
 
-  func application(_: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
-    logger.info("---- background-fetch ----")
+    func application(_: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+        logger.info("---- background-fetch ----")
 
-    start {
-      // TODO: actually set the right value depending on if we found sth
-      completionHandler(.newData)
+        start {
+            // TODO: actually set the right value depending on if we found sth
+            completionHandler(.newData)
+        }
     }
-  }
-
-  func applicationWillEnterForeground(_: UIApplication) {
-    logger.info("---- foreground ----")
-    start()
-  }
-
-  func applicationDidEnterBackground(_: UIApplication) {
-    logger.info("---- background ----")
-
-    reachability.stopNotifier()
-    NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
-
-    maybeStop()
-  }
-
-  private func maybeStop() {
-    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
-      let app = UIApplication.shared
-      logger.info("state: \(app.applicationState) time remaining \(app.backgroundTimeRemaining)")
-
-      if app.applicationState != .background {
-        // only need to do sth in the background
-        return
-      } else if app.backgroundTimeRemaining < 10 {
-        self.stop()
-      } else {
-        self.maybeStop()
-      }
+
+    func applicationWillEnterForeground(_: UIApplication) {
+        logger.info("---- foreground ----")
+        start()
     }
-  }
 
-  func applicationWillTerminate(_: UIApplication) {
-    logger.info("---- terminate ----")
-    close()
+    func applicationDidEnterBackground(_: UIApplication) {
+        logger.info("---- background ----")
 
-    reachability.stopNotifier()
-    NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
-  }
+        reachability.stopNotifier()
+        NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
 
-  func dbfile() -> String {
-    let paths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
-    let documentsPath = paths[0]
+        maybeStop()
+    }
 
-    return documentsPath + "/messenger.db"
-  }
+    private func maybeStop() {
+        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
+            let app = UIApplication.shared
+            logger.info("state: \(app.applicationState) time remaining \(app.backgroundTimeRemaining)")
+
+            if app.applicationState != .background {
+                // only need to do sth in the background
+                return
+            } else if app.backgroundTimeRemaining < 10 {
+                self.stop()
+            } else {
+                self.maybeStop()
+            }
+        }
+    }
 
-  func open() {
-    logger.info("open: \(dbfile())")
+    func applicationWillTerminate(_: UIApplication) {
+        logger.info("---- terminate ----")
+        close()
 
-    if mailboxPointer == nil {
-      mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
-      guard mailboxPointer != nil else {
-        fatalError("Error: dc_context_new returned nil")
-      }
+        reachability.stopNotifier()
+        NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
     }
-    _ = dc_open(mailboxPointer, dbfile(), nil)
-  }
-
-  func stop() {
-    state = .background
 
-    dc_interrupt_imap_idle(mailboxPointer)
-    dc_interrupt_smtp_idle(mailboxPointer)
-    dc_interrupt_mvbox_idle(mailboxPointer)
-    dc_interrupt_sentbox_idle(mailboxPointer)
-  }
+    func dbfile() -> String {
+        let paths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
+        let documentsPath = paths[0]
 
-  func close() {
-    state = .stopped
-    dc_close(mailboxPointer)
-    mailboxPointer = nil
-  }
+        return documentsPath + "/messenger.db"
+    }
 
-  func start(_ completion: (() -> Void)? = nil) {
-    logger.info("---- start ----")
+    func open() {
+        logger.info("open: \(dbfile())")
 
-    if state == .running {
-      return
+        if mailboxPointer == nil {
+            mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
+            guard mailboxPointer != nil else {
+                fatalError("Error: dc_context_new returned nil")
+            }
+        }
+        _ = dc_open(mailboxPointer, dbfile(), nil)
     }
 
-    state = .running
-
-    DispatchQueue.global(qos: .background).async {
-      self.registerBackgroundTask()
-      while self.state == .running {
-        dc_perform_imap_jobs(mailboxPointer)
-      	dc_perform_imap_fetch(mailboxPointer)
-        dc_perform_imap_idle(mailboxPointer)
-      }
-      if self.backgroundTask != .invalid {
-        completion?()
-        self.endBackgroundTask()
-      }
+    func stop() {
+        state = .background
+
+        dc_interrupt_imap_idle(mailboxPointer)
+        dc_interrupt_smtp_idle(mailboxPointer)
+        dc_interrupt_mvbox_idle(mailboxPointer)
+        dc_interrupt_sentbox_idle(mailboxPointer)
     }
 
-    DispatchQueue.global(qos: .utility).async {
-      self.registerBackgroundTask()
-      while self.state == .running {
-        dc_perform_smtp_jobs(mailboxPointer)
-        dc_perform_smtp_idle(mailboxPointer)
-      }
-      if self.backgroundTask != .invalid {
-        self.endBackgroundTask()
-      }
+    func close() {
+        state = .stopped
+        dc_close(mailboxPointer)
+        mailboxPointer = nil
     }
 
-    if MRConfig.sentboxWatch {
-      DispatchQueue.global(qos: .background).async {
-        while self.state == .running {
-          dc_perform_sentbox_fetch(mailboxPointer)
-          dc_perform_sentbox_idle(mailboxPointer)
+    func start(_ completion: (() -> Void)? = nil) {
+        logger.info("---- start ----")
+
+        if state == .running {
+            return
+        }
+
+        state = .running
+
+        DispatchQueue.global(qos: .background).async {
+            self.registerBackgroundTask()
+            while self.state == .running {
+                dc_perform_imap_jobs(mailboxPointer)
+                dc_perform_imap_fetch(mailboxPointer)
+                dc_perform_imap_idle(mailboxPointer)
+            }
+            if self.backgroundTask != .invalid {
+                completion?()
+                self.endBackgroundTask()
+            }
         }
-      }
-    }
 
-    if MRConfig.mvboxWatch {
-      DispatchQueue.global(qos: .background).async {
-        while self.state == .running {
-          dc_perform_mvbox_fetch(mailboxPointer)
-          dc_perform_mvbox_idle(mailboxPointer)
+        DispatchQueue.global(qos: .utility).async {
+            self.registerBackgroundTask()
+            while self.state == .running {
+                dc_perform_smtp_jobs(mailboxPointer)
+                dc_perform_smtp_idle(mailboxPointer)
+            }
+            if self.backgroundTask != .invalid {
+                self.endBackgroundTask()
+            }
         }
-      }
+
+        if MRConfig.sentboxWatch {
+            DispatchQueue.global(qos: .background).async {
+                while self.state == .running {
+                    dc_perform_sentbox_fetch(mailboxPointer)
+                    dc_perform_sentbox_idle(mailboxPointer)
+                }
+            }
+        }
+
+        if MRConfig.mvboxWatch {
+            DispatchQueue.global(qos: .background).async {
+                while self.state == .running {
+                    dc_perform_mvbox_fetch(mailboxPointer)
+                    dc_perform_mvbox_idle(mailboxPointer)
+                }
+            }
+        }
+
+        NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
+        do {
+            try reachability.startNotifier()
+        } catch {
+            logger.info("could not start reachability notifier")
+        }
+
+        let info: [DBCustomVariable] = getCoreInfo().map { kv in
+            let value = kv.count > 1 ? kv[1] : ""
+            return DBCustomVariable(name: kv[0], value: value)
+        }
+
+        DBDebugToolkit.add(info)
     }
 
-    NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
-    do {
-      try reachability.startNotifier()
-    } catch {
-      logger.info("could not start reachability notifier")
+    @objc private func reachabilityChanged(note: Notification) {
+        let reachability = note.object as! Reachability
+
+        switch reachability.connection {
+        case .wifi, .cellular:
+            logger.info("network: reachable", reachability.connection.description)
+            dc_maybe_network(mailboxPointer)
+
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(name: dcNotificationStateChanged,
+                        object: nil,
+                        userInfo: ["state": "online"])
+            }
+        case .none:
+            logger.info("network: not reachable")
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(name: dcNotificationStateChanged,
+                        object: nil,
+                        userInfo: ["state": "offline"])
+            }
+        }
     }
 
-    let info: [DBCustomVariable] = getCoreInfo().map { kv in
-      let value = kv.count > 1 ? kv[1] : ""
-      return DBCustomVariable(name: kv[0], value: value)
+    // MARK: - BackgroundTask
+
+    private func registerBackgroundTask() {
+        logger.info("background task registered")
+        backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
+            self?.endBackgroundTask()
+        }
+        assert(backgroundTask != .invalid)
     }
 
-    DBDebugToolkit.add(info)
-  }
-
-  @objc private func reachabilityChanged(note: Notification) {
-    let reachability = note.object as! Reachability
-
-    switch reachability.connection {
-    case .wifi, .cellular:
-      logger.info("network: reachable", reachability.connection.description)
-      dc_maybe_network(mailboxPointer)
-
-      let nc = NotificationCenter.default
-      DispatchQueue.main.async {
-        nc.post(name: dcNotificationStateChanged,
-                object: nil,
-                userInfo: ["state": "online"])
-      }
-    case .none:
-      logger.info("network: not reachable")
-      let nc = NotificationCenter.default
-      DispatchQueue.main.async {
-        nc.post(name: dcNotificationStateChanged,
-                object: nil,
-                userInfo: ["state": "offline"])
-      }
+    private func endBackgroundTask() {
+        logger.info("background task ended")
+        UIApplication.shared.endBackgroundTask(backgroundTask)
+        backgroundTask = .invalid
     }
-  }
 
-  // MARK: - BackgroundTask
+    // MARK: - PushNotifications
+
+    private func registerForPushNotifications() {
+        UNUserNotificationCenter.current().delegate = self
 
-  private func registerBackgroundTask() {
-    logger.info("background task registered")
-    backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
-      self?.endBackgroundTask()
+        UNUserNotificationCenter.current()
+            .requestAuthorization(options: [.alert, .sound, .badge]) {
+                granted, _ in
+                logger.info("permission granted: \(granted)")
+                guard granted else { return }
+                self.getNotificationSettings()
+            }
     }
-    assert(backgroundTask != .invalid)
-  }
-
-  private func endBackgroundTask() {
-    logger.info("background task ended")
-    UIApplication.shared.endBackgroundTask(backgroundTask)
-    backgroundTask = .invalid
-  }
-
-  // MARK: - PushNotifications
-
-  private func registerForPushNotifications() {
-    UNUserNotificationCenter.current().delegate = self
-
-    UNUserNotificationCenter.current()
-      .requestAuthorization(options: [.alert, .sound, .badge]) {
-        granted, _ in
-        logger.info("permission granted: \(granted)")
-        guard granted else { return }
-        self.getNotificationSettings()
-      }
-  }
-
-  private func getNotificationSettings() {
-    UNUserNotificationCenter.current().getNotificationSettings { settings in
-      logger.info("Notification settings: \(settings)")
+
+    private func getNotificationSettings() {
+        UNUserNotificationCenter.current().getNotificationSettings { settings in
+            logger.info("Notification settings: \(settings)")
+        }
     }
-  }
-
-  private func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
-    logger.info("forground notification")
-    completionHandler([.alert, .sound])
-  }
-
-  private func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
-    if response.notification.request.identifier == Constants.notificationIdentifier {
-      logger.info("handling notifications")
-      let userInfo = response.notification.request.content.userInfo
-      let nc = NotificationCenter.default
-      DispatchQueue.main.async {
-        nc.post(
-          name: dcNotificationViewChat,
-          object: nil,
-          userInfo: userInfo
-        )
-      }
+
+    private func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+        logger.info("forground notification")
+        completionHandler([.alert, .sound])
     }
 
-    completionHandler()
-  }
+    private func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+        if response.notification.request.identifier == Constants.notificationIdentifier {
+            logger.info("handling notifications")
+            let userInfo = response.notification.request.content.userInfo
+            let nc = NotificationCenter.default
+            DispatchQueue.main.async {
+                nc.post(
+                    name: dcNotificationViewChat,
+                    object: nil,
+                    userInfo: userInfo
+                )
+            }
+        }
+
+        completionHandler()
+    }
 }

+ 155 - 155
deltachat-ios/Controller/AccountSetup/PortSettingsController.swift

@@ -2,161 +2,161 @@ import UIKit
 
 class PortSettingsController: UITableViewController {
 
-	var ports: [Int]
-
-	private var sectionTitle: String?
-
-	var resetButton: UIBarButtonItem!
-
-	var onDismiss:((String)->Void)?
-
-	var currentPort: Int {
-		didSet {
-			// activate resetButton once something was changed
-			resetButton.isEnabled = true
-		}
-	}
-
-	var selectedIndex: Int?
-
-	let backupValue: Int
-
-	var staticCells: [UITableViewCell] {
-		return ports.map({
-			let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
-			cell.textLabel?.text = "\($0)"
-			cell.selectionStyle = .none
-			return cell
-		})
-	}
-
-	lazy var customCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "", placeholder: "\(self.currentPort)")
-		cell.selectionStyle = .none
-		cell.textLabel?.text = nil
-		cell.textField.keyboardType = .numberPad
-		cell.onTextFieldChange = textFieldDidChange
-		return cell
-	}()
-
-	init(sectionTitle: String?, ports: [Int], currentPort: Int?) {
-		self.ports = ports
-		self.sectionTitle = sectionTitle
-		self.currentPort = currentPort ?? 0
-		for (index, port) in ports.enumerated() where currentPort == port {
-			selectedIndex = index
-		}
-		backupValue = self.currentPort
-		super.init(style: .grouped)
-		self.title = sectionTitle
-	}
-
-	required init?(coder aDecoder: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	override func viewDidLoad() {
-		super.viewDidLoad()
-
-		resetButton = UIBarButtonItem(title: "Reset", style: .plain, target: self, action: #selector(resetButtonPressed))
-		navigationItem.rightBarButtonItem = resetButton
-		resetButton.isEnabled = false
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		onDismiss?("\(currentPort)")
-	}
-
-	// MARK: - Table view data source
-
-	override func numberOfSections(in tableView: UITableView) -> Int {
-		// #warning Incomplete implementation, return the number of sections
-		return 2
-	}
-
-	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-		if section == 0 {
-			return ports.count
-		} else {
-			return 1
-		}
-	}
-
-	override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-		if indexPath.section == 0 {
-			let row = indexPath.row
-			selectItem(at: row)
-		}
-	}
-
-	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-
-		let section = indexPath.section
-		let row = indexPath.row
-		if section == 0 {
-			let cell = staticCells[row]
-			if row == selectedIndex || cell.textLabel?.text == "\(currentPort)" {
-				cell.accessoryType = .checkmark
-			} else {
-				cell.accessoryType = .none
-			}
-			return cell
-		} else {
-			// section == 1
-			return customCell
-		}
-	}
-
-	override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
-		if section == 0 {
-			return sectionTitle
-		} else {
-			return "Custom Port"
-		}
-	}
-
-	private func textFieldDidChange(_ textField: UITextField) {
-		selectItem(at: nil)
-		if let text = textField.text, let port = Int(text) {
-			self.currentPort = port
-		}
-	}
-
-	private func selectItem(at index: Int? ) {
-		// unselect old cell
-		// select new cell
-		// update port
-		if let oldIndex = selectedIndex {
-			let cell = tableView.cellForRow(at: IndexPath.init(row: oldIndex, section: 0))
-			cell?.accessoryType = .none
-		}
-		if let newIndex = index {
-			// activate accesoryType on selected cell
-			let cell = tableView.cellForRow(at: IndexPath.init(row: newIndex, section: 0))
-			cell?.accessoryType = .checkmark
-			currentPort = ports[newIndex]
-			// update customCell
-			customCell.textField.placeholder = "\(currentPort)"
-			customCell.textField.resignFirstResponder()
-			customCell.textField.text = nil // will display currentValue as placeholder
-		}
-		selectedIndex = index
-	}
-
-	@objc private func resetButtonPressed() {
-
-		if let index = ports.index(of: backupValue) {
-			selectItem(at: index)
-		} else {
-			selectItem(at: nil)
-		}
-		self.currentPort = backupValue
-		customCell.textField.placeholder = "\(currentPort)"
-		customCell.textField.resignFirstResponder()
-		customCell.textField.text = nil // will display currentValue as placeholder
-
-		tableView.reloadData()
-	}
+    var ports: [Int]
+
+    private var sectionTitle: String?
+
+    var resetButton: UIBarButtonItem!
+
+    var onDismiss:((String)->Void)?
+
+    var currentPort: Int {
+        didSet {
+            // activate resetButton once something was changed
+            resetButton.isEnabled = true
+        }
+    }
+
+    var selectedIndex: Int?
+
+    let backupValue: Int
+
+    var staticCells: [UITableViewCell] {
+        return ports.map({
+            let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
+            cell.textLabel?.text = "\($0)"
+            cell.selectionStyle = .none
+            return cell
+        })
+    }
+
+    lazy var customCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "", placeholder: "\(self.currentPort)")
+        cell.selectionStyle = .none
+        cell.textLabel?.text = nil
+        cell.textField.keyboardType = .numberPad
+        cell.onTextFieldChange = textFieldDidChange
+        return cell
+    }()
+
+    init(sectionTitle: String?, ports: [Int], currentPort: Int?) {
+        self.ports = ports
+        self.sectionTitle = sectionTitle
+        self.currentPort = currentPort ?? 0
+        for (index, port) in ports.enumerated() where currentPort == port {
+            selectedIndex = index
+        }
+        backupValue = self.currentPort
+        super.init(style: .grouped)
+        self.title = sectionTitle
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+
+        resetButton = UIBarButtonItem(title: "Reset", style: .plain, target: self, action: #selector(resetButtonPressed))
+        navigationItem.rightBarButtonItem = resetButton
+        resetButton.isEnabled = false
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        onDismiss?("\(currentPort)")
+    }
+
+    // MARK: - Table view data source
+
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        // #warning Incomplete implementation, return the number of sections
+        return 2
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if section == 0 {
+            return ports.count
+        } else {
+            return 1
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if indexPath.section == 0 {
+            let row = indexPath.row
+            selectItem(at: row)
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+        let section = indexPath.section
+        let row = indexPath.row
+        if section == 0 {
+            let cell = staticCells[row]
+            if row == selectedIndex || cell.textLabel?.text == "\(currentPort)" {
+                cell.accessoryType = .checkmark
+            } else {
+                cell.accessoryType = .none
+            }
+            return cell
+        } else {
+            // section == 1
+            return customCell
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+        if section == 0 {
+            return sectionTitle
+        } else {
+            return "Custom Port"
+        }
+    }
+
+    private func textFieldDidChange(_ textField: UITextField) {
+        selectItem(at: nil)
+        if let text = textField.text, let port = Int(text) {
+            self.currentPort = port
+        }
+    }
+
+    private func selectItem(at index: Int? ) {
+        // unselect old cell
+        // select new cell
+        // update port
+        if let oldIndex = selectedIndex {
+            let cell = tableView.cellForRow(at: IndexPath.init(row: oldIndex, section: 0))
+            cell?.accessoryType = .none
+        }
+        if let newIndex = index {
+            // activate accesoryType on selected cell
+            let cell = tableView.cellForRow(at: IndexPath.init(row: newIndex, section: 0))
+            cell?.accessoryType = .checkmark
+            currentPort = ports[newIndex]
+            // update customCell
+            customCell.textField.placeholder = "\(currentPort)"
+            customCell.textField.resignFirstResponder()
+            customCell.textField.text = nil // will display currentValue as placeholder
+        }
+        selectedIndex = index
+    }
+
+    @objc private func resetButtonPressed() {
+
+        if let index = ports.index(of: backupValue) {
+            selectItem(at: index)
+        } else {
+            selectItem(at: nil)
+        }
+        self.currentPort = backupValue
+        customCell.textField.placeholder = "\(currentPort)"
+        customCell.textField.resignFirstResponder()
+        customCell.textField.text = nil // will display currentValue as placeholder
+
+        tableView.reloadData()
+    }
 
 }
 

+ 146 - 146
deltachat-ios/Controller/AccountSetup/SecuritySettingsController.swift

@@ -2,162 +2,162 @@ import UIKit
 
 class SecuritySettingsController: UITableViewController {
 
-	private var options: [String]
-	private var selectedIndex: Int {
-		didSet {
-			print(selectedIndex)
-		}
-	}
-
-	private var backupIndex: Int
-
-	var onDismiss: ((String) -> Void)?
-
-	private var resetButton: UIBarButtonItem!
-
-	private var staticCells: [UITableViewCell] {
-		return options.map {
-			let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
-			cell.textLabel?.text = $0
-			cell.selectionStyle = .none
-			return cell
-		}
-	}
-
-	init(title: String, options: [String], selectedOption: String) {
-		self.options = options
-		selectedIndex = options.index(of: selectedOption)!
-		backupIndex = selectedIndex
-		super.init(style: .grouped)
-		self.title = title
-	}
-
-	required init?(coder aDecoder: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		resetButton = UIBarButtonItem(title: "Reset", style: .done, target: self, action: #selector(resetButtonPressed))
-		resetButton.isEnabled = false
-		navigationItem.rightBarButtonItem = resetButton
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		let selectedOption = options[selectedIndex]
-		onDismiss?(selectedOption)
-	}
-
-	// MARK: - Table view data source
-
-	override func numberOfSections(in tableView: UITableView) -> Int {
-		return 1
-	}
-
-	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-		return options.count
-	}
-
-	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-		let cell = staticCells[indexPath.row]
-		if selectedIndex == indexPath.row {
-			cell.accessoryType = .checkmark
-		} else {
-			cell.accessoryType = .none
-		}
-		return cell
-	}
-
-	override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-		// uselect old
-		if let cell = tableView.cellForRow(at: IndexPath(item: selectedIndex, section: 0)) {
-			cell.accessoryType = .none
-		}
-		// select new
-		if let cell = tableView.cellForRow(at: indexPath) {
-			cell.accessoryType = .checkmark
-		}
-		selectedIndex = indexPath.row
-		resetButton.isEnabled = true
-	}
-
-	@objc func resetButtonPressed() {
-		selectedIndex = backupIndex
-		tableView.reloadData()
-	}
+    private var options: [String]
+    private var selectedIndex: Int {
+        didSet {
+            print(selectedIndex)
+        }
+    }
+
+    private var backupIndex: Int
+
+    var onDismiss: ((String) -> Void)?
+
+    private var resetButton: UIBarButtonItem!
+
+    private var staticCells: [UITableViewCell] {
+        return options.map {
+            let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
+            cell.textLabel?.text = $0
+            cell.selectionStyle = .none
+            return cell
+        }
+    }
+
+    init(title: String, options: [String], selectedOption: String) {
+        self.options = options
+        selectedIndex = options.index(of: selectedOption)!
+        backupIndex = selectedIndex
+        super.init(style: .grouped)
+        self.title = title
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        resetButton = UIBarButtonItem(title: "Reset", style: .done, target: self, action: #selector(resetButtonPressed))
+        resetButton.isEnabled = false
+        navigationItem.rightBarButtonItem = resetButton
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        let selectedOption = options[selectedIndex]
+        onDismiss?(selectedOption)
+    }
+
+    // MARK: - Table view data source
+
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return options.count
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell = staticCells[indexPath.row]
+        if selectedIndex == indexPath.row {
+            cell.accessoryType = .checkmark
+        } else {
+            cell.accessoryType = .none
+        }
+        return cell
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        // uselect old
+        if let cell = tableView.cellForRow(at: IndexPath(item: selectedIndex, section: 0)) {
+            cell.accessoryType = .none
+        }
+        // select new
+        if let cell = tableView.cellForRow(at: indexPath) {
+            cell.accessoryType = .checkmark
+        }
+        selectedIndex = indexPath.row
+        resetButton.isEnabled = true
+    }
+
+    @objc func resetButtonPressed() {
+        selectedIndex = backupIndex
+        tableView.reloadData()
+    }
 
 }
 
 
 enum SecurityType {
-	case IMAPSecurity
-	case SMTPSecurity
+    case IMAPSecurity
+    case SMTPSecurity
 }
 
 enum SecurityValue: String {
-	case AUTO = "Automatic"
-	case TLS = "SSL / TLS"
-	case STARTTLS = "STARTTLS"
-	case PLAIN = "OFF"
+    case AUTO = "Automatic"
+    case TLS = "SSL / TLS"
+    case STARTTLS = "STARTTLS"
+    case PLAIN = "OFF"
 }
 
 class SecurityConverter {
 
-	static func convertValueToInt(type: SecurityType, value: SecurityValue) -> Int {
-		switch type {
-		case .IMAPSecurity:
-			switch value {
-			case .AUTO:
-				return 0x000
-			case .STARTTLS:
-				return 0x100
-			case .TLS:
-				return 0x200
-			case .PLAIN:
-				return 0x400
-			}
-		case .SMTPSecurity:
-			switch value{
-			case .AUTO:
-				return 0x00000
-			case .STARTTLS:
-				return 0x10000
-			case .TLS:
-				return 0x20000
-			case .PLAIN:
-				return 0x40000
-			}
-		}
-	}
-
-	static func convertHexToString(type: SecurityType, hex value: Int) -> String {
-		switch type {
-		case .IMAPSecurity:
-			switch value {
-			case 0x00:
-				return "Automatic"
-			case 0x100:
-				return "STARTTLS"
-			case 0x200:
-				return "SSL / TLS"
-			case  0x400:
-				return "OFF"
-			default:
-				return "Undefined"
-			}
-		case .SMTPSecurity:
-			switch value {
-			case 0x00000:
-				return "Automatic"
-			case 0x10000:
-					return "STARTTLS"
-			case 0x20000:
-				return "SSL / TLS"
-			case  0x40000:
-				return "OFF"
-			default:
-				return "Undefined"
-			}
-		}
-	}
+    static func convertValueToInt(type: SecurityType, value: SecurityValue) -> Int {
+        switch type {
+        case .IMAPSecurity:
+            switch value {
+            case .AUTO:
+                return 0x000
+            case .STARTTLS:
+                return 0x100
+            case .TLS:
+                return 0x200
+            case .PLAIN:
+                return 0x400
+            }
+        case .SMTPSecurity:
+            switch value{
+            case .AUTO:
+                return 0x00000
+            case .STARTTLS:
+                return 0x10000
+            case .TLS:
+                return 0x20000
+            case .PLAIN:
+                return 0x40000
+            }
+        }
+    }
+
+    static func convertHexToString(type: SecurityType, hex value: Int) -> String {
+        switch type {
+        case .IMAPSecurity:
+            switch value {
+            case 0x00:
+                return "Automatic"
+            case 0x100:
+                return "STARTTLS"
+            case 0x200:
+                return "SSL / TLS"
+            case  0x400:
+                return "OFF"
+            default:
+                return "Undefined"
+            }
+        case .SMTPSecurity:
+            switch value {
+            case 0x00000:
+                return "Automatic"
+            case 0x10000:
+                return "STARTTLS"
+            case 0x20000:
+                return "SSL / TLS"
+            case  0x40000:
+                return "OFF"
+            default:
+                return "Undefined"
+            }
+        }
+    }
 }

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

@@ -4,676 +4,676 @@ import UICircularProgressRing
 
 class AccountSetupController: UITableViewController {
 
-	weak var coordinator: AccountSetupCoordinator?
-
-	private var skipOauth = false
-
-	private var backupProgressObserver: Any?
-	private var configureProgressObserver: Any?
-	private var oauth2Observer: Any?
-
-	lazy var configProgressIndicator: UICircularProgressRing = {
-		let progress = UICircularProgressRing()
-		progress.style = UICircularRingStyle.inside
-		progress.outerRingColor = UIColor.clear
-		progress.maxValue = 100
-		progress.innerRingColor = DCColors.primary
-		progress.innerRingWidth = 2
-		progress.startAngle = 270
-		progress.fontColor = UIColor.lightGray
-		progress.font = UIFont.systemFont(ofSize: 12)
-		return progress
-	}()
-
-	lazy var configProgressAlert: UIAlertController = {
-		let alert = UIAlertController(title: "Configuring Account", message: "\n\n\n", preferredStyle: .alert)
-		// temp workaround: add 3 newlines to let alertbox grow to fit progressview
-		let progressView = configProgressIndicator
-		progressView.translatesAutoresizingMaskIntoConstraints = false
-		alert.view.addSubview(progressView)
-		progressView.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor).isActive = true
-		progressView.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor, constant: 0).isActive = true
-		progressView.heightAnchor.constraint(equalToConstant: 65).isActive = true
-		progressView.widthAnchor.constraint(equalToConstant: 65).isActive = true
-		alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: loginCancelled(_:)))
-		return alert
-	}()
-
-	private lazy var emailCell: TextFieldCell = {
-		let cell = TextFieldCell.makeEmailCell(delegate: self)
-		cell.textField.tag = 0
-		cell.textField.accessibilityIdentifier = "emailTextField" // will be used to eventually show oAuth-Dialogue when pressing return key
-		cell.setText(text: MRConfig.addr ?? nil)
-		cell.textField.delegate = self
-		return cell
-	}()
-
-	private lazy var passwordCell: TextFieldCell = {
-		let cell = TextFieldCell.makePasswordCell(delegate: self)
-		cell.textField.tag = 1
-		cell.accessibilityIdentifier = "passwordCell" // will be used to eventually show oAuth-Dialogue when selecting
-		cell.setText(text: MRConfig.mailPw ?? nil)
-		return cell
-	}()
-
-	private lazy var restoreCell: ActionCell = {
-		let cell = ActionCell(frame: .zero)
-		cell.actionTitle = "Restore from backup"
-		cell.accessibilityIdentifier = "restoreCell"
-		return cell
-	}()
-
-	lazy var imapServerCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "IMAP Server", placeholder: MRConfig.mailServer ?? MRConfig.configuredMailServer, delegate: self)
-		cell.accessibilityIdentifier = "IMAPServerCell"
-		cell.textField.tag = 2
-		return cell
-	}()
-
-	lazy var imapUserCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "IMAP User", placeholder: MRConfig.mailUser ?? MRConfig.configuredMailUser, delegate: self)
-		cell.accessibilityIdentifier = "IMAPUserCell"
-		cell.textField.tag = 3
-		return cell
-	}()
-
-	lazy var imapPortCell: UITableViewCell = {
-		let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
-		cell.textLabel?.text = "IMAP Port"
-		cell.accessoryType = .disclosureIndicator
-		cell.detailTextLabel?.text = MRConfig.mailPort ?? MRConfig.configuredMailPort
-		cell.accessibilityIdentifier = "IMAPPortCell"
-		cell.selectionStyle = .none
-		return cell
-	}()
-
-	lazy var imapSecurityCell: UITableViewCell = {
-		let text = "\(MRConfig.getImapSecurity())"
-		let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
-		cell.textLabel?.text = "IMAP Security"
-		// let cell = TextFieldCell(description: "IMAP Security", placeholder: text, delegate: self)
-		cell.accessibilityIdentifier = "IMAPSecurityCell"
-		cell.accessoryType = .disclosureIndicator
-		cell.detailTextLabel?.text = "\(MRConfig.getImapSecurity())"
-		cell.selectionStyle = .none
-		return cell
-	}()
-
-	lazy var smtpServerCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "SMTP Server", placeholder: MRConfig.sendServer ?? MRConfig.configuredSendServer, delegate: self)
-		cell.accessibilityIdentifier = "SMTPServerCell"
-		cell.textField.tag = 4
-		return cell
-	}()
-
-	lazy var smtpUserCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "SMTP User", placeholder: MRConfig.sendUser ?? MRConfig.configuredSendUser, delegate: self)
-		cell.accessibilityIdentifier = "SMTPUserCell"
-		cell.textField.tag = 5
-		return cell
-	}()
-
-	lazy var smtpPortCell: UITableViewCell = {
-		let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
-		cell.textLabel?.text = "SMTP Port"
-		cell.accessoryType = .disclosureIndicator
-		cell.detailTextLabel?.text = MRConfig.sendPort ?? MRConfig.configuredSendPort
-		cell.accessibilityIdentifier = "SMTPPortCell"
-		cell.selectionStyle = .none
-		return cell
-	}()
-
-	lazy var smtpPasswordCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "SMTP Password", placeholder: "*************", delegate: self)
-		cell.accessibilityIdentifier = "SMTPPasswordCell"
-		cell.textField.tag = 6
-		return cell
-	}()
-
-	lazy var smtpSecurityCell: UITableViewCell = {
-		let security = "\(MRConfig.getSmtpSecurity())"
-		let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
-		cell.textLabel?.text = "SMTP Security"
-		cell.detailTextLabel?.text = security
-		cell.accessibilityIdentifier = "SMTPSecurityCell"
-		cell.accessoryType = .disclosureIndicator
-		cell.selectionStyle = .none
-		return cell
-	}()
-
-	// this loginButton can be enabled and disabled
-	private lazy var loginButton: UIBarButtonItem = {
-		let button = UIBarButtonItem(title: "Login", style: .done, target: self, action: #selector(loginButtonPressed))
-		button.isEnabled = dc_is_configured(mailboxPointer) == 0
-		return button
-	}()
-
-	private lazy var basicSectionCells: [UITableViewCell] = [emailCell, passwordCell]
-	private lazy var restoreCells: [UITableViewCell] = [restoreCell]
-	private lazy var advancedSectionCells: [UITableViewCell] = [
-		imapServerCell,
-		imapUserCell,
-		imapPortCell,
-		imapSecurityCell,
-		smtpServerCell,
-		smtpUserCell,
-		smtpPortCell,
-		smtpPasswordCell,
-		smtpSecurityCell
-	]
-
-	private var advancedSectionShowing: Bool = false
-
-	init() {
-		super.init(style: .grouped)
-		hidesBottomBarWhenPushed = true
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		title = "Login to your server"
-		// navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: #selector(closeButtonPressed))
-		navigationItem.rightBarButtonItem = loginButton
-	}
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-		// needs to be changed if returning from portSettingsController
-		smtpPortCell.detailTextLabel?.text = MRConfig.sendPort ?? MRConfig.configuredSendPort
-		imapPortCell.detailTextLabel?.text = MRConfig.mailPort ?? MRConfig.configuredMailPort
-		smtpSecurityCell.detailTextLabel?.text = SecurityConverter.convertHexToString(type: .SMTPSecurity, hex: MRConfig.getSmtpSecurity())
-		imapSecurityCell.detailTextLabel?.text  = SecurityConverter.convertHexToString(type: .IMAPSecurity, hex: MRConfig.getImapSecurity())
-	}
-
-	override func viewDidAppear(_ animated: Bool) {
-		super.viewDidAppear(animated)
-		addProgressHudEventListener()
-		// loginButton.isEnabled = false
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		resignFirstResponderOnAllCells()
-	}
-
-	override func viewDidDisappear(_: Bool) {
-
-		let nc = NotificationCenter.default
-		if let backupProgressObserver = self.backupProgressObserver {
-			nc.removeObserver(backupProgressObserver)
-		}
-		if let configureProgressObserver = self.configureProgressObserver {
-			nc.removeObserver(configureProgressObserver)
-		}
-		if let oauth2Observer = self.oauth2Observer {
-			nc.removeObserver(oauth2Observer)
-		}
-	}
-
-	// MARK: - Table view data source
-
-	override func numberOfSections(in _: UITableView) -> Int {
-		// #warning Incomplete implementation, return the number of sections
-		return 3
-	}
-
-	override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-		// #warning Incomplete implementation, return the number of rows
-		if section == 0 {
-			return basicSectionCells.count
-		} else if section == 1 {
-			return restoreCells.count
-		} else {
-			return advancedSectionShowing ? advancedSectionCells.count : 0
-		}
-	}
-
-	override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
-		if section == 2 {
-			return "Advanced"
-		} else {
-			return nil
-		}
-	}
-
-	override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
-		if section == 2 {
-			// Advanced Header
-			let advancedView = AdvancedSectionHeader()
-			advancedView.handleTap = toggleAdvancedSection
-			// set tapHandler
-			return advancedView
-
-		} else {
-			return nil
-		}
-	}
-
-	override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
-		return 36.0
-	}
-
-	override func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? {
-		if section == 0 {
-			return "There are no Delta Chat servers, your data stays on your device!"
-		} else if section == 2 {
-			if advancedSectionShowing {
-				return "For known email providers additional settings are setup automatically. Sometimes IMAP needs to be enabled in the web frontend. Consult your email provider or friends for help"
-			} else {
-				return nil
-			}
-		} else {
-			return nil
-		}
-	}
-
-	override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-		let section = indexPath.section
-		let row = indexPath.row
-
-		if section == 0 {
-			// basicSection
-			return basicSectionCells[row]
-		} else if section == 1 {
-			return restoreCells[row]
-		} else {
-			// advancedSection
-			return advancedSectionCells[row]
-		}
-	}
-
-	override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-		guard let tappedCell = tableView.cellForRow(at: indexPath) else { return }
-		// handle tap on password -> show oAuthDialogue
-		if let textFieldCell = tappedCell as? TextFieldCell {
-			if textFieldCell.accessibilityIdentifier == "passwordCell" {
-				if let emailAdress = textFieldCell.getText() {
-					_ = showOAuthAlertIfNeeded(emailAddress: emailAdress, handleCancel: nil)
-				}
-			}
-		}
-
-		if tappedCell.accessibilityIdentifier == "restoreCell" {
-			restoreBackup()
-		} else if tappedCell.accessibilityIdentifier == "IMAPPortCell" {
-			coordinator?.showImapPortOptions()
-		} else if tappedCell.accessibilityIdentifier == "SMTPPortCell" {
-			coordinator?.showSmtpPortsOptions()
-		} else if tappedCell.accessibilityIdentifier == "IMAPSecurityCell" {
-			coordinator?.showImapSecurityOptions()
-		} else if tappedCell.accessibilityIdentifier == "SMTPSecurityCell" {
-			coordinator?.showSmptpSecurityOptions()
-		}
-	}
-
-	private func toggleAdvancedSection(button: UILabel) {
-		let willShow = !advancedSectionShowing
-
-		// extract indexPaths from advancedCells
-		let advancedIndexPaths: [IndexPath] = advancedSectionCells.indices.map { IndexPath(row: $0, section: 2) }
-
-		// advancedSectionCells.indices.map({indexPaths.append(IndexPath(row: $0, section: 1))}
-
-		// set flag before delete/insert operation, because cellForRowAt will be triggered and uses this flag
-		advancedSectionShowing = willShow
-
-		button.text = willShow ? "Hide" : "Show"
-
-		if willShow {
-			tableView.insertRows(at: advancedIndexPaths, with: .fade)
-		} else {
-			tableView.deleteRows(at: advancedIndexPaths, with: .fade)
-		}
-		tableView.reloadData() // to re-organize footer view (without that sometimes advanced section footer is still visible)
-	}
-
-	@objc private func loginButtonPressed() {
-		guard let emailAddress = emailCell.getText() else {
-			return // handle case when either email or pw fields are empty
-		}
-
-		let oAuthStarted = showOAuthAlertIfNeeded(emailAddress: emailAddress, handleCancel: loginButtonPressed)
-		// if canceled we will run this method again but this time oAuthStarted will be false
-
-		if oAuthStarted {
-			// the loginFlow will be handled by oAuth2
-			return
-		}
-
-		let password = passwordCell.getText() ?? "" // empty passwords are ok -> for oauth there is no password needed
-
-		login(emailAddress: emailAddress, password: password)
-	}
-
-	private func login(emailAddress: String, password: String, skipAdvanceSetup: Bool = false) {
-		resignFirstResponderOnAllCells()	// this will resign focus from all textFieldCells so the keyboard wont pop up anymore
-		MRConfig.addr = emailAddress
-		MRConfig.mailPw = password
-
-		if !skipAdvanceSetup {
-			evaluateAdvancedSetup() // this will set MRConfig related to advanced fields
-		}
-
-		print("oAuth-Flag when loggin in: \(MRConfig.getAuthFlags())")
-		dc_configure(mailboxPointer)
-		showProgressHud()
-	}
-
-	@objc func closeButtonPressed() {
-		dismiss(animated: true, completion: nil)
-	}
-
-	// returns true if needed
-	private func showOAuthAlertIfNeeded(emailAddress: String, handleCancel: (() -> Void)?) -> Bool {
-		return false;
-
-		// disable oauth2 for now as not yet supported by deltachat-rust.
-		/*
-		if skipOauth {
-			// user has previously denied oAuth2-setup
-			return false
-		}
-
-		guard let oAuth2UrlPointer = dc_get_oauth2_url(mailboxPointer, emailAddress, "chat.delta:/auth") else {
-			//MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_NORMAL)) -- do not reset, there may be different values
-			return false
-		}
-
-		let oAuth2Url = String(cString: oAuth2UrlPointer)
-
-		if let url = URL(string: oAuth2Url) {
-			let title = "Continue with simplified setup"
-			// swiftlint:disable all
-			let message = "The entered e-mail address supports a simplified setup (oAuth2).\n\nIn the next step, please allow Delta Chat to act as your Chat with E-Mail app.\n\nThere are no Delta Chat servers, your data stays on your device."
-
-			let oAuthAlertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
-			let confirm = UIAlertAction(title: "Confirm", style: .default, handler: {
-				[unowned self] _ in
-				let nc = NotificationCenter.default
-				self.oauth2Observer = nc.addObserver(self, selector: #selector(self.oauthLoginApproved), name: NSNotification.Name("oauthLoginApproved"), object: nil)
-				self.launchOAuthBrowserWindow(url: url)
-			})
-			let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: {
-				_ in
-				MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_NORMAL))
-				self.skipOauth = true
-				handleCancel?()
-
-			})
-			oAuthAlertController.addAction(confirm)
-			oAuthAlertController.addAction(cancel)
-
-			present(oAuthAlertController, animated: true, completion: nil)
-			return true
-		} else {
-			return false
-		}
-		*/
-	}
-
-	@objc func oauthLoginApproved(notification: Notification) {
-		guard let userInfo = notification.userInfo, let token = userInfo["token"] as? String, let emailAddress = emailCell.getText() else {
-			return
-		}
-		passwordCell.setText(text: token)
-		MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_OAUTH2))
-		login(emailAddress: emailAddress, password: token, skipAdvanceSetup: true)
-	}
-
-	private func launchOAuthBrowserWindow(url: URL) {
-		UIApplication.shared.open(url) // this opens safari as seperate app
-	}
-
-	private func addProgressHudEventListener() {
-		let nc = NotificationCenter.default
-		backupProgressObserver = nc.addObserver(
-			forName: dcNotificationBackupProgress,
-			object: nil,
-			queue: nil
-		) {
-			notification in
-			if let ui = notification.userInfo {
-				if ui["error"] as! Bool {
-					self.updateProgressHud(error: ui["errorMessage"] as? String)
-					// self.hudHandler.setHudError(ui["errorMessage"] as? String)
-				} else if ui["done"] as! Bool {
-					self.updateProgressHudSuccess(callback: self.handleLoginSuccess)
-				} else {
-					self.updateProgressHudValue(value: ui["progress"] as! Int)
-				}
-			}
-		}
-		configureProgressObserver = nc.addObserver(
-			forName: dcNotificationConfigureProgress,
-			object: nil,
-			queue: nil
-		) {
-			notification in
-			if let ui = notification.userInfo {
-				if ui["error"] as! Bool {
-					self.updateProgressHud(error: ui["errorMessage"] as? String)
-					// self.hudHandler.setHudError(ui["errorMessage"] as? String)
-				} else if ui["done"] as! Bool {
-					self.updateProgressHudSuccess(callback: self.handleLoginSuccess)
-				} else {
-					self.updateProgressHudValue(value: ui["progress"] as! Int)
-				}
-			}
-		}
-	}
-
-	private func evaluateAdvancedSetup() {
-		for cell in advancedSectionCells {
-			if let textFieldCell = cell as? TextFieldCell {
-				switch cell.accessibilityIdentifier {
-				case "IMAPServerCell":
-					MRConfig.mailServer = textFieldCell.getText() ?? nil
-				case "IMAPUserCell":
-					MRConfig.mailUser = textFieldCell.getText() ?? nil
-				case "IMAPPortCell":
-					MRConfig.mailPort = textFieldCell.getText() ?? nil
-				case "IMAPSecurityCell":
-					let flag = 0
-					MRConfig.setImapSecurity(imapFlags: flag)
-				case "SMTPServerCell":
-					MRConfig.sendServer = textFieldCell.getText() ?? nil
-				case "SMTPUserCell":
-					MRConfig.sendUser = textFieldCell.getText() ?? nil
-				case "SMTPPortCell":
-					MRConfig.sendPort = textFieldCell.getText() ?? nil
-				case "SMTPPasswordCell":
-					MRConfig.sendPw = textFieldCell.getText() ?? nil
-				case "SMTPSecurityCell":
-					let flag = 0
-					MRConfig.setSmtpSecurity(smptpFlags: flag)
-				default:
-					logger.info("unknown identifier", cell.accessibilityIdentifier ?? "")
-				}
-			}
-		}
-	}
-
-	private func restoreBackup() {
-		logger.info("restoring backup")
-		if MRConfig.configured {
-			return
-		}
-		let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
-		if !documents.isEmpty {
-			logger.info("looking for backup in: \(documents[0])")
-
-			if let file = dc_imex_has_backup(mailboxPointer, documents[0]) {
-				logger.info("restoring backup: \(String(cString: file))")
-
-				// hudHandler.showBackupHud("Restoring Backup")
-				dc_imex(mailboxPointer, DC_IMEX_IMPORT_BACKUP, file, nil)
-
-				return
-			}
-
-			let alert = UIAlertController(title: "Can not restore", message: "No Backup found", preferredStyle: .alert)
-			alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-
-			}))
-			present(alert, animated: true, completion: nil)
-			return
-		}
-
-		logger.error("no documents directory found")
-	}
-
-	private func handleLoginSuccess() {
-		// used when login hud successfully went trough
-		dismiss(animated: true, completion: nil)
-	}
-
-	private func resignFirstResponderOnAllCells() {
-		let _ = basicSectionCells.map({
-			resignCell(cell: $0)
-		})
-
-		let _ = advancedSectionCells.map({
-			resignCell(cell: $0)
-			}
-		)
-	}
-
-	func resignCell(cell: UITableViewCell) {
-		if let c = cell as? TextFieldCell {
-			c.textField.resignFirstResponder()
-		}
-	}
+    weak var coordinator: AccountSetupCoordinator?
+
+    private var skipOauth = false
+
+    private var backupProgressObserver: Any?
+    private var configureProgressObserver: Any?
+    private var oauth2Observer: Any?
+
+    lazy var configProgressIndicator: UICircularProgressRing = {
+        let progress = UICircularProgressRing()
+        progress.style = UICircularRingStyle.inside
+        progress.outerRingColor = UIColor.clear
+        progress.maxValue = 100
+        progress.innerRingColor = DCColors.primary
+        progress.innerRingWidth = 2
+        progress.startAngle = 270
+        progress.fontColor = UIColor.lightGray
+        progress.font = UIFont.systemFont(ofSize: 12)
+        return progress
+    }()
+
+    lazy var configProgressAlert: UIAlertController = {
+        let alert = UIAlertController(title: "Configuring Account", message: "\n\n\n", preferredStyle: .alert)
+        // temp workaround: add 3 newlines to let alertbox grow to fit progressview
+        let progressView = configProgressIndicator
+        progressView.translatesAutoresizingMaskIntoConstraints = false
+        alert.view.addSubview(progressView)
+        progressView.centerXAnchor.constraint(equalTo: alert.view.centerXAnchor).isActive = true
+        progressView.centerYAnchor.constraint(equalTo: alert.view.centerYAnchor, constant: 0).isActive = true
+        progressView.heightAnchor.constraint(equalToConstant: 65).isActive = true
+        progressView.widthAnchor.constraint(equalToConstant: 65).isActive = true
+        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: loginCancelled(_:)))
+        return alert
+    }()
+
+    private lazy var emailCell: TextFieldCell = {
+        let cell = TextFieldCell.makeEmailCell(delegate: self)
+        cell.textField.tag = 0
+        cell.textField.accessibilityIdentifier = "emailTextField" // will be used to eventually show oAuth-Dialogue when pressing return key
+        cell.setText(text: MRConfig.addr ?? nil)
+        cell.textField.delegate = self
+        return cell
+    }()
+
+    private lazy var passwordCell: TextFieldCell = {
+        let cell = TextFieldCell.makePasswordCell(delegate: self)
+        cell.textField.tag = 1
+        cell.accessibilityIdentifier = "passwordCell" // will be used to eventually show oAuth-Dialogue when selecting
+        cell.setText(text: MRConfig.mailPw ?? nil)
+        return cell
+    }()
+
+    private lazy var restoreCell: ActionCell = {
+        let cell = ActionCell(frame: .zero)
+        cell.actionTitle = "Restore from backup"
+        cell.accessibilityIdentifier = "restoreCell"
+        return cell
+    }()
+
+    lazy var imapServerCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "IMAP Server", placeholder: MRConfig.mailServer ?? MRConfig.configuredMailServer, delegate: self)
+        cell.accessibilityIdentifier = "IMAPServerCell"
+        cell.textField.tag = 2
+        return cell
+    }()
+
+    lazy var imapUserCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "IMAP User", placeholder: MRConfig.mailUser ?? MRConfig.configuredMailUser, delegate: self)
+        cell.accessibilityIdentifier = "IMAPUserCell"
+        cell.textField.tag = 3
+        return cell
+    }()
+
+    lazy var imapPortCell: UITableViewCell = {
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = "IMAP Port"
+        cell.accessoryType = .disclosureIndicator
+        cell.detailTextLabel?.text = MRConfig.mailPort ?? MRConfig.configuredMailPort
+        cell.accessibilityIdentifier = "IMAPPortCell"
+        cell.selectionStyle = .none
+        return cell
+    }()
+
+    lazy var imapSecurityCell: UITableViewCell = {
+        let text = "\(MRConfig.getImapSecurity())"
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = "IMAP Security"
+        // let cell = TextFieldCell(description: "IMAP Security", placeholder: text, delegate: self)
+        cell.accessibilityIdentifier = "IMAPSecurityCell"
+        cell.accessoryType = .disclosureIndicator
+        cell.detailTextLabel?.text = "\(MRConfig.getImapSecurity())"
+        cell.selectionStyle = .none
+        return cell
+    }()
+
+    lazy var smtpServerCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "SMTP Server", placeholder: MRConfig.sendServer ?? MRConfig.configuredSendServer, delegate: self)
+        cell.accessibilityIdentifier = "SMTPServerCell"
+        cell.textField.tag = 4
+        return cell
+    }()
+
+    lazy var smtpUserCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "SMTP User", placeholder: MRConfig.sendUser ?? MRConfig.configuredSendUser, delegate: self)
+        cell.accessibilityIdentifier = "SMTPUserCell"
+        cell.textField.tag = 5
+        return cell
+    }()
+
+    lazy var smtpPortCell: UITableViewCell = {
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = "SMTP Port"
+        cell.accessoryType = .disclosureIndicator
+        cell.detailTextLabel?.text = MRConfig.sendPort ?? MRConfig.configuredSendPort
+        cell.accessibilityIdentifier = "SMTPPortCell"
+        cell.selectionStyle = .none
+        return cell
+    }()
+
+    lazy var smtpPasswordCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "SMTP Password", placeholder: "*************", delegate: self)
+        cell.accessibilityIdentifier = "SMTPPasswordCell"
+        cell.textField.tag = 6
+        return cell
+    }()
+
+    lazy var smtpSecurityCell: UITableViewCell = {
+        let security = "\(MRConfig.getSmtpSecurity())"
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = "SMTP Security"
+        cell.detailTextLabel?.text = security
+        cell.accessibilityIdentifier = "SMTPSecurityCell"
+        cell.accessoryType = .disclosureIndicator
+        cell.selectionStyle = .none
+        return cell
+    }()
+
+    // this loginButton can be enabled and disabled
+    private lazy var loginButton: UIBarButtonItem = {
+        let button = UIBarButtonItem(title: "Login", style: .done, target: self, action: #selector(loginButtonPressed))
+        button.isEnabled = dc_is_configured(mailboxPointer) == 0
+        return button
+    }()
+
+    private lazy var basicSectionCells: [UITableViewCell] = [emailCell, passwordCell]
+    private lazy var restoreCells: [UITableViewCell] = [restoreCell]
+    private lazy var advancedSectionCells: [UITableViewCell] = [
+        imapServerCell,
+        imapUserCell,
+        imapPortCell,
+        imapSecurityCell,
+        smtpServerCell,
+        smtpUserCell,
+        smtpPortCell,
+        smtpPasswordCell,
+        smtpSecurityCell
+    ]
+
+    private var advancedSectionShowing: Bool = false
+
+    init() {
+        super.init(style: .grouped)
+        hidesBottomBarWhenPushed = true
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Login to your server"
+        // navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: #selector(closeButtonPressed))
+        navigationItem.rightBarButtonItem = loginButton
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        // needs to be changed if returning from portSettingsController
+        smtpPortCell.detailTextLabel?.text = MRConfig.sendPort ?? MRConfig.configuredSendPort
+        imapPortCell.detailTextLabel?.text = MRConfig.mailPort ?? MRConfig.configuredMailPort
+        smtpSecurityCell.detailTextLabel?.text = SecurityConverter.convertHexToString(type: .SMTPSecurity, hex: MRConfig.getSmtpSecurity())
+        imapSecurityCell.detailTextLabel?.text  = SecurityConverter.convertHexToString(type: .IMAPSecurity, hex: MRConfig.getImapSecurity())
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        addProgressHudEventListener()
+        // loginButton.isEnabled = false
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        resignFirstResponderOnAllCells()
+    }
+
+    override func viewDidDisappear(_: Bool) {
+
+        let nc = NotificationCenter.default
+        if let backupProgressObserver = self.backupProgressObserver {
+            nc.removeObserver(backupProgressObserver)
+        }
+        if let configureProgressObserver = self.configureProgressObserver {
+            nc.removeObserver(configureProgressObserver)
+        }
+        if let oauth2Observer = self.oauth2Observer {
+            nc.removeObserver(oauth2Observer)
+        }
+    }
+
+    // MARK: - Table view data source
+
+    override func numberOfSections(in _: UITableView) -> Int {
+        // #warning Incomplete implementation, return the number of sections
+        return 3
+    }
+
+    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+        // #warning Incomplete implementation, return the number of rows
+        if section == 0 {
+            return basicSectionCells.count
+        } else if section == 1 {
+            return restoreCells.count
+        } else {
+            return advancedSectionShowing ? advancedSectionCells.count : 0
+        }
+    }
+
+    override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
+        if section == 2 {
+            return "Advanced"
+        } else {
+            return nil
+        }
+    }
+
+    override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if section == 2 {
+            // Advanced Header
+            let advancedView = AdvancedSectionHeader()
+            advancedView.handleTap = toggleAdvancedSection
+            // set tapHandler
+            return advancedView
+
+        } else {
+            return nil
+        }
+    }
+
+    override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
+        return 36.0
+    }
+
+    override func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? {
+        if section == 0 {
+            return "There are no Delta Chat servers, your data stays on your device!"
+        } else if section == 2 {
+            if advancedSectionShowing {
+                return "For known email providers additional settings are setup automatically. Sometimes IMAP needs to be enabled in the web frontend. Consult your email provider or friends for help"
+            } else {
+                return nil
+            }
+        } else {
+            return nil
+        }
+    }
+
+    override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let section = indexPath.section
+        let row = indexPath.row
+
+        if section == 0 {
+            // basicSection
+            return basicSectionCells[row]
+        } else if section == 1 {
+            return restoreCells[row]
+        } else {
+            // advancedSection
+            return advancedSectionCells[row]
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        guard let tappedCell = tableView.cellForRow(at: indexPath) else { return }
+        // handle tap on password -> show oAuthDialogue
+        if let textFieldCell = tappedCell as? TextFieldCell {
+            if textFieldCell.accessibilityIdentifier == "passwordCell" {
+                if let emailAdress = textFieldCell.getText() {
+                    _ = showOAuthAlertIfNeeded(emailAddress: emailAdress, handleCancel: nil)
+                }
+            }
+        }
+
+        if tappedCell.accessibilityIdentifier == "restoreCell" {
+            restoreBackup()
+        } else if tappedCell.accessibilityIdentifier == "IMAPPortCell" {
+            coordinator?.showImapPortOptions()
+        } else if tappedCell.accessibilityIdentifier == "SMTPPortCell" {
+            coordinator?.showSmtpPortsOptions()
+        } else if tappedCell.accessibilityIdentifier == "IMAPSecurityCell" {
+            coordinator?.showImapSecurityOptions()
+        } else if tappedCell.accessibilityIdentifier == "SMTPSecurityCell" {
+            coordinator?.showSmptpSecurityOptions()
+        }
+    }
+
+    private func toggleAdvancedSection(button: UILabel) {
+        let willShow = !advancedSectionShowing
+
+        // extract indexPaths from advancedCells
+        let advancedIndexPaths: [IndexPath] = advancedSectionCells.indices.map { IndexPath(row: $0, section: 2) }
+
+        // advancedSectionCells.indices.map({indexPaths.append(IndexPath(row: $0, section: 1))}
+
+        // set flag before delete/insert operation, because cellForRowAt will be triggered and uses this flag
+        advancedSectionShowing = willShow
+
+        button.text = willShow ? "Hide" : "Show"
+
+        if willShow {
+            tableView.insertRows(at: advancedIndexPaths, with: .fade)
+        } else {
+            tableView.deleteRows(at: advancedIndexPaths, with: .fade)
+        }
+        tableView.reloadData() // to re-organize footer view (without that sometimes advanced section footer is still visible)
+    }
+
+    @objc private func loginButtonPressed() {
+        guard let emailAddress = emailCell.getText() else {
+            return // handle case when either email or pw fields are empty
+        }
+
+        let oAuthStarted = showOAuthAlertIfNeeded(emailAddress: emailAddress, handleCancel: loginButtonPressed)
+        // if canceled we will run this method again but this time oAuthStarted will be false
+
+        if oAuthStarted {
+            // the loginFlow will be handled by oAuth2
+            return
+        }
+
+        let password = passwordCell.getText() ?? "" // empty passwords are ok -> for oauth there is no password needed
+
+        login(emailAddress: emailAddress, password: password)
+    }
+
+    private func login(emailAddress: String, password: String, skipAdvanceSetup: Bool = false) {
+        resignFirstResponderOnAllCells()	// this will resign focus from all textFieldCells so the keyboard wont pop up anymore
+        MRConfig.addr = emailAddress
+        MRConfig.mailPw = password
+
+        if !skipAdvanceSetup {
+            evaluateAdvancedSetup() // this will set MRConfig related to advanced fields
+        }
+
+        print("oAuth-Flag when loggin in: \(MRConfig.getAuthFlags())")
+        dc_configure(mailboxPointer)
+        showProgressHud()
+    }
+
+    @objc func closeButtonPressed() {
+        dismiss(animated: true, completion: nil)
+    }
+
+    // returns true if needed
+    private func showOAuthAlertIfNeeded(emailAddress: String, handleCancel: (() -> Void)?) -> Bool {
+        return false;
+
+        // disable oauth2 for now as not yet supported by deltachat-rust.
+        /*
+         if skipOauth {
+         	// user has previously denied oAuth2-setup
+         	return false
+         }
+
+         guard let oAuth2UrlPointer = dc_get_oauth2_url(mailboxPointer, emailAddress, "chat.delta:/auth") else {
+         	//MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_NORMAL)) -- do not reset, there may be different values
+         	return false
+         }
+
+         let oAuth2Url = String(cString: oAuth2UrlPointer)
+
+         if let url = URL(string: oAuth2Url) {
+         	let title = "Continue with simplified setup"
+         	// swiftlint:disable all
+         	let message = "The entered e-mail address supports a simplified setup (oAuth2).\n\nIn the next step, please allow Delta Chat to act as your Chat with E-Mail app.\n\nThere are no Delta Chat servers, your data stays on your device."
+
+         	let oAuthAlertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
+         	let confirm = UIAlertAction(title: "Confirm", style: .default, handler: {
+         		[unowned self] _ in
+         		let nc = NotificationCenter.default
+         		self.oauth2Observer = nc.addObserver(self, selector: #selector(self.oauthLoginApproved), name: NSNotification.Name("oauthLoginApproved"), object: nil)
+         		self.launchOAuthBrowserWindow(url: url)
+         	})
+         	let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: {
+         		_ in
+         		MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_NORMAL))
+         		self.skipOauth = true
+         		handleCancel?()
+
+         	})
+         	oAuthAlertController.addAction(confirm)
+         	oAuthAlertController.addAction(cancel)
+
+         	present(oAuthAlertController, animated: true, completion: nil)
+         	return true
+         } else {
+         	return false
+         }
+         */
+    }
+
+    @objc func oauthLoginApproved(notification: Notification) {
+        guard let userInfo = notification.userInfo, let token = userInfo["token"] as? String, let emailAddress = emailCell.getText() else {
+            return
+        }
+        passwordCell.setText(text: token)
+        MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_OAUTH2))
+        login(emailAddress: emailAddress, password: token, skipAdvanceSetup: true)
+    }
+
+    private func launchOAuthBrowserWindow(url: URL) {
+        UIApplication.shared.open(url) // this opens safari as seperate app
+    }
+
+    private func addProgressHudEventListener() {
+        let nc = NotificationCenter.default
+        backupProgressObserver = nc.addObserver(
+            forName: dcNotificationBackupProgress,
+            object: nil,
+            queue: nil
+        ) {
+            notification in
+            if let ui = notification.userInfo {
+                if ui["error"] as! Bool {
+                    self.updateProgressHud(error: ui["errorMessage"] as? String)
+                    // self.hudHandler.setHudError(ui["errorMessage"] as? String)
+                } else if ui["done"] as! Bool {
+                    self.updateProgressHudSuccess(callback: self.handleLoginSuccess)
+                } else {
+                    self.updateProgressHudValue(value: ui["progress"] as! Int)
+                }
+            }
+        }
+        configureProgressObserver = nc.addObserver(
+            forName: dcNotificationConfigureProgress,
+            object: nil,
+            queue: nil
+        ) {
+            notification in
+            if let ui = notification.userInfo {
+                if ui["error"] as! Bool {
+                    self.updateProgressHud(error: ui["errorMessage"] as? String)
+                    // self.hudHandler.setHudError(ui["errorMessage"] as? String)
+                } else if ui["done"] as! Bool {
+                    self.updateProgressHudSuccess(callback: self.handleLoginSuccess)
+                } else {
+                    self.updateProgressHudValue(value: ui["progress"] as! Int)
+                }
+            }
+        }
+    }
+
+    private func evaluateAdvancedSetup() {
+        for cell in advancedSectionCells {
+            if let textFieldCell = cell as? TextFieldCell {
+                switch cell.accessibilityIdentifier {
+                case "IMAPServerCell":
+                    MRConfig.mailServer = textFieldCell.getText() ?? nil
+                case "IMAPUserCell":
+                    MRConfig.mailUser = textFieldCell.getText() ?? nil
+                case "IMAPPortCell":
+                    MRConfig.mailPort = textFieldCell.getText() ?? nil
+                case "IMAPSecurityCell":
+                    let flag = 0
+                    MRConfig.setImapSecurity(imapFlags: flag)
+                case "SMTPServerCell":
+                    MRConfig.sendServer = textFieldCell.getText() ?? nil
+                case "SMTPUserCell":
+                    MRConfig.sendUser = textFieldCell.getText() ?? nil
+                case "SMTPPortCell":
+                    MRConfig.sendPort = textFieldCell.getText() ?? nil
+                case "SMTPPasswordCell":
+                    MRConfig.sendPw = textFieldCell.getText() ?? nil
+                case "SMTPSecurityCell":
+                    let flag = 0
+                    MRConfig.setSmtpSecurity(smptpFlags: flag)
+                default:
+                    logger.info("unknown identifier", cell.accessibilityIdentifier ?? "")
+                }
+            }
+        }
+    }
+
+    private func restoreBackup() {
+        logger.info("restoring backup")
+        if MRConfig.configured {
+            return
+        }
+        let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
+        if !documents.isEmpty {
+            logger.info("looking for backup in: \(documents[0])")
+
+            if let file = dc_imex_has_backup(mailboxPointer, documents[0]) {
+                logger.info("restoring backup: \(String(cString: file))")
+
+                // hudHandler.showBackupHud("Restoring Backup")
+                dc_imex(mailboxPointer, DC_IMEX_IMPORT_BACKUP, file, nil)
+
+                return
+            }
+
+            let alert = UIAlertController(title: "Can not restore", message: "No Backup found", preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+
+            }))
+            present(alert, animated: true, completion: nil)
+            return
+        }
+
+        logger.error("no documents directory found")
+    }
+
+    private func handleLoginSuccess() {
+        // used when login hud successfully went trough
+        dismiss(animated: true, completion: nil)
+    }
+
+    private func resignFirstResponderOnAllCells() {
+        let _ = basicSectionCells.map({
+            resignCell(cell: $0)
+        })
+
+        let _ = advancedSectionCells.map({
+            resignCell(cell: $0)
+        }
+        )
+    }
+
+    func resignCell(cell: UITableViewCell) {
+        if let c = cell as? TextFieldCell {
+            c.textField.resignFirstResponder()
+        }
+    }
 }
 
 extension AccountSetupController: UITextFieldDelegate {
-	func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-		let currentTag = textField.tag
-		if let nextField = tableView.viewWithTag(currentTag + 1) as? UITextField {
-			if nextField.tag > 1, !advancedSectionShowing {
-				// gets here when trying to activate a collapsed cell
-				return false
-			}
-			nextField.becomeFirstResponder()
-		}
-		return false
-	}
-
-	func textFieldDidBeginEditing(_ textField: UITextField) {
-		if textField.accessibilityIdentifier == "emailTextField" {
-			loginButton.isEnabled = true
-			// this will re-enable possible oAuth2-login
-			skipOauth = false
-		}
-	}
-
-	func textFieldDidEndEditing(_ textField: UITextField) {
-		if textField.accessibilityIdentifier == "emailTextField" {
-			let _ = showOAuthAlertIfNeeded(emailAddress: textField.text ?? "", handleCancel: {
-				self.passwordCell.textField.becomeFirstResponder()
-			})
-		}
-	}
+    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+        let currentTag = textField.tag
+        if let nextField = tableView.viewWithTag(currentTag + 1) as? UITextField {
+            if nextField.tag > 1, !advancedSectionShowing {
+                // gets here when trying to activate a collapsed cell
+                return false
+            }
+            nextField.becomeFirstResponder()
+        }
+        return false
+    }
+
+    func textFieldDidBeginEditing(_ textField: UITextField) {
+        if textField.accessibilityIdentifier == "emailTextField" {
+            loginButton.isEnabled = true
+            // this will re-enable possible oAuth2-login
+            skipOauth = false
+        }
+    }
+
+    func textFieldDidEndEditing(_ textField: UITextField) {
+        if textField.accessibilityIdentifier == "emailTextField" {
+            let _ = showOAuthAlertIfNeeded(emailAddress: textField.text ?? "", handleCancel: {
+                self.passwordCell.textField.becomeFirstResponder()
+            })
+        }
+    }
 }
 
 class AdvancedSectionHeader: UIView {
-	var handleTap: ((UILabel) -> Void)?
-
-	private var label: UILabel = {
-		let label = UILabel()
-		label.text = "ADVANCED"
-		label.font = UIFont.systemFont(ofSize: 15)
-		label.textColor = UIColor.darkGray
-		return label
-	}()
-
-	/*
-	why UILabel, why no UIButton? For unknown reasons UIButton's target function was not triggered when one of the textfields in the tableview was active -> used label as workaround
-	*/
-	private lazy var toggleButton: UILabel = {
-		let label = UILabel()
-		label.text = "Show"
-		label.font = UIFont.systemFont(ofSize: 15, weight: .medium)
-		label.textColor = UIColor.systemBlue
-		return label
-	}()
-
-	init() {
-		super.init(frame: .zero) // will be constraint from tableViewDelegate
-		setupSubviews()
-		let tap = UITapGestureRecognizer(target: self, action: #selector(viewTapped)) // use this if the whole header is supposed to be clickable
-		addGestureRecognizer(tap)
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	func setupSubviews() {
-		addSubview(label)
-		label.translatesAutoresizingMaskIntoConstraints = false
-		label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15).isActive = true
-		label.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0).isActive = true
-		addSubview(toggleButton)
-		toggleButton.translatesAutoresizingMaskIntoConstraints = false
-		toggleButton.leadingAnchor.constraint(equalTo: trailingAnchor, constant: -60).isActive = true // since button will change title it should be left aligned
-		toggleButton.centerYAnchor.constraint(equalTo: label.centerYAnchor, constant: 0).isActive = true
-	}
-
-	@objc func buttonTapped(_: UIButton) {
-		// handleTap?(button)
-	}
-
-	@objc func viewTapped() {
-		handleTap?(toggleButton)
-	}
+    var handleTap: ((UILabel) -> Void)?
+
+    private var label: UILabel = {
+        let label = UILabel()
+        label.text = "ADVANCED"
+        label.font = UIFont.systemFont(ofSize: 15)
+        label.textColor = UIColor.darkGray
+        return label
+    }()
+
+    /*
+     why UILabel, why no UIButton? For unknown reasons UIButton's target function was not triggered when one of the textfields in the tableview was active -> used label as workaround
+     */
+    private lazy var toggleButton: UILabel = {
+        let label = UILabel()
+        label.text = "Show"
+        label.font = UIFont.systemFont(ofSize: 15, weight: .medium)
+        label.textColor = UIColor.systemBlue
+        return label
+    }()
+
+    init() {
+        super.init(frame: .zero) // will be constraint from tableViewDelegate
+        setupSubviews()
+        let tap = UITapGestureRecognizer(target: self, action: #selector(viewTapped)) // use this if the whole header is supposed to be clickable
+        addGestureRecognizer(tap)
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    func setupSubviews() {
+        addSubview(label)
+        label.translatesAutoresizingMaskIntoConstraints = false
+        label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15).isActive = true
+        label.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0).isActive = true
+        addSubview(toggleButton)
+        toggleButton.translatesAutoresizingMaskIntoConstraints = false
+        toggleButton.leadingAnchor.constraint(equalTo: trailingAnchor, constant: -60).isActive = true // since button will change title it should be left aligned
+        toggleButton.centerYAnchor.constraint(equalTo: label.centerYAnchor, constant: 0).isActive = true
+    }
+
+    @objc func buttonTapped(_: UIButton) {
+        // handleTap?(button)
+    }
+
+    @objc func viewTapped() {
+        handleTap?(toggleButton)
+    }
 }
 
 extension AccountSetupController {
 
-	func showProgressHud() {
-		configProgressAlert.actions[0].isEnabled = true
-		configProgressAlert.title = "Configuring Account"
-		configProgressAlert.message = "\n\n\n"	// workaround to create space for progress indicator
-		configProgressIndicator.alpha = 1
-		configProgressIndicator.value = 0
-		present(configProgressAlert, animated: true, completion: nil)
-
-	}
-
-	func updateProgressHud(error message: String?) {
-		configProgressAlert.title = "Unable to Login!"
-		configProgressAlert.message = message
-		configProgressIndicator.alpha = 0
-	}
-
-	func updateProgressHudSuccess(callback: (()->())?) {
-		configProgressAlert.actions[0].isEnabled = false
-		configProgressIndicator.alpha = 0
-		configProgressAlert.title = "Login Successful!"
-		configProgressAlert.message = "You are ready to use Delta Chat."
-		loginButton.isEnabled = dc_is_configured(mailboxPointer) == 0
-		DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
-			self.configProgressAlert.dismiss(animated: true) {
-				self.handleLoginSuccess()
-			}
-		})
-	}
-
-	func updateProgressHudValue(value: Int?) {
-		if let value = value {
-			print("progress hud: \(value)")
-			configProgressIndicator.value = CGFloat(value / 10)
-		}
-	}
-
-	func loginCancelled(_ action: UIAlertAction) {
-		MRConfig.addr = nil
-		MRConfig.mailPw = nil
-		DispatchQueue.global(qos: .background).async {
-			dc_stop_ongoing_process(mailboxPointer)		// this function freezes UI so execute in background thread
-		}
-	}
+    func showProgressHud() {
+        configProgressAlert.actions[0].isEnabled = true
+        configProgressAlert.title = "Configuring Account"
+        configProgressAlert.message = "\n\n\n"	// workaround to create space for progress indicator
+        configProgressIndicator.alpha = 1
+        configProgressIndicator.value = 0
+        present(configProgressAlert, animated: true, completion: nil)
+
+    }
+
+    func updateProgressHud(error message: String?) {
+        configProgressAlert.title = "Unable to Login!"
+        configProgressAlert.message = message
+        configProgressIndicator.alpha = 0
+    }
+
+    func updateProgressHudSuccess(callback: (()->())?) {
+        configProgressAlert.actions[0].isEnabled = false
+        configProgressIndicator.alpha = 0
+        configProgressAlert.title = "Login Successful!"
+        configProgressAlert.message = "You are ready to use Delta Chat."
+        loginButton.isEnabled = dc_is_configured(mailboxPointer) == 0
+        DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
+            self.configProgressAlert.dismiss(animated: true) {
+                self.handleLoginSuccess()
+            }
+        })
+    }
+
+    func updateProgressHudValue(value: Int?) {
+        if let value = value {
+            print("progress hud: \(value)")
+            configProgressIndicator.value = CGFloat(value / 10)
+        }
+    }
+
+    func loginCancelled(_ action: UIAlertAction) {
+        MRConfig.addr = nil
+        MRConfig.mailPw = nil
+        DispatchQueue.global(qos: .background).async {
+            dc_stop_ongoing_process(mailboxPointer)		// this function freezes UI so execute in background thread
+        }
+    }
 }

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

@@ -1,185 +1,185 @@
 import UIKit
 
 class ChatListController: UIViewController {
-	weak var coordinator: ChatListCoordinator?
-	var chatList: MRChatList?
-
-	lazy var chatTable: UITableView = {
-		let chatTable = UITableView()
-		chatTable.dataSource = self
-		chatTable.delegate = self
-		chatTable.rowHeight = 80
-		return chatTable
-	}()
-
-	var msgChangedObserver: Any?
-	var incomingMsgObserver: Any?
-	var viewChatObserver: Any?
-
-	var newButton: UIBarButtonItem!
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-
-		if #available(iOS 11.0, *) {
-			navigationController?.navigationBar.prefersLargeTitles = true
-			navigationItem.largeTitleDisplayMode = .always
-		}
-		getChatList()
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		super.viewWillDisappear(animated)
-		if #available(iOS 11.0, *) {
-			navigationController?.navigationBar.prefersLargeTitles = false
-		}
-	}
-
-	override func viewDidAppear(_ animated: Bool) {
-		super.viewDidAppear(animated)
-		let nc = NotificationCenter.default
-		msgChangedObserver = nc.addObserver(forName: dcNotificationChanged,
-																				object: nil, queue: nil) {
-																					_ in
-																					self.getChatList()
-		}
-		incomingMsgObserver = nc.addObserver(forName: dcNotificationIncoming,
-																				 object: nil, queue: nil) {
-																					_ in
-																					self.getChatList()
-		}
-
-		viewChatObserver = nc.addObserver(forName: dcNotificationViewChat, object: nil, queue: nil) {
-			notification in
-			if let chatId = notification.userInfo?["chat_id"] as? Int {
-				self.coordinator?.showChat(chatId: chatId)
-			}
-		}
-	}
-
-	override func viewDidDisappear(_ animated: Bool) {
-		super.viewDidDisappear(animated)
-
-		let nc = NotificationCenter.default
-		if let msgChangedObserver = self.msgChangedObserver {
-			nc.removeObserver(msgChangedObserver)
-		}
-		if let incomingMsgObserver = self.incomingMsgObserver {
-			nc.removeObserver(incomingMsgObserver)
-		}
-		if let viewChatObserver = self.viewChatObserver {
-			nc.removeObserver(viewChatObserver)
-		}
-	}
-
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		title = "Chats"
-		navigationController?.navigationBar.prefersLargeTitles = true
-
-		newButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.compose, target: self, action: #selector(didPressNewChat))
-		newButton.tintColor = DCColors.primary
-		navigationItem.rightBarButtonItem = newButton
-
-		setupChatTable()
-	}
-
-	private func setupChatTable() {
-		view.addSubview(chatTable)
-		chatTable.translatesAutoresizingMaskIntoConstraints = false
-		chatTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
-		chatTable.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
-		chatTable.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
-		chatTable.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
-	}
-
-	@objc func didPressNewChat() {
-		coordinator?.showNewChatController()
-	}
-
-	func getChatList() {
-		guard let chatlistPointer = dc_get_chatlist(mailboxPointer, DC_GCL_NO_SPECIALS, nil, 0) else {
-			fatalError("chatlistPointer was nil")
-		}
-		// ownership of chatlistPointer transferred here to ChatList object
-		chatList = MRChatList(chatListPointer: chatlistPointer)
-		chatTable.reloadData()
-	}
+    weak var coordinator: ChatListCoordinator?
+    var chatList: MRChatList?
+
+    lazy var chatTable: UITableView = {
+        let chatTable = UITableView()
+        chatTable.dataSource = self
+        chatTable.delegate = self
+        chatTable.rowHeight = 80
+        return chatTable
+    }()
+
+    var msgChangedObserver: Any?
+    var incomingMsgObserver: Any?
+    var viewChatObserver: Any?
+
+    var newButton: UIBarButtonItem!
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = true
+            navigationItem.largeTitleDisplayMode = .always
+        }
+        getChatList()
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        let nc = NotificationCenter.default
+        msgChangedObserver = nc.addObserver(forName: dcNotificationChanged,
+                                            object: nil, queue: nil) {
+            _ in
+            self.getChatList()
+        }
+        incomingMsgObserver = nc.addObserver(forName: dcNotificationIncoming,
+                                             object: nil, queue: nil) {
+            _ in
+            self.getChatList()
+        }
+
+        viewChatObserver = nc.addObserver(forName: dcNotificationViewChat, object: nil, queue: nil) {
+            notification in
+            if let chatId = notification.userInfo?["chat_id"] as? Int {
+                self.coordinator?.showChat(chatId: chatId)
+            }
+        }
+    }
+
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+
+        let nc = NotificationCenter.default
+        if let msgChangedObserver = self.msgChangedObserver {
+            nc.removeObserver(msgChangedObserver)
+        }
+        if let incomingMsgObserver = self.incomingMsgObserver {
+            nc.removeObserver(incomingMsgObserver)
+        }
+        if let viewChatObserver = self.viewChatObserver {
+            nc.removeObserver(viewChatObserver)
+        }
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Chats"
+        navigationController?.navigationBar.prefersLargeTitles = true
+
+        newButton = UIBarButtonItem(barButtonSystemItem: UIBarButtonItem.SystemItem.compose, target: self, action: #selector(didPressNewChat))
+        newButton.tintColor = DCColors.primary
+        navigationItem.rightBarButtonItem = newButton
+
+        setupChatTable()
+    }
+
+    private func setupChatTable() {
+        view.addSubview(chatTable)
+        chatTable.translatesAutoresizingMaskIntoConstraints = false
+        chatTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
+        chatTable.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
+        chatTable.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
+        chatTable.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
+    }
+
+    @objc func didPressNewChat() {
+        coordinator?.showNewChatController()
+    }
+
+    func getChatList() {
+        guard let chatlistPointer = dc_get_chatlist(mailboxPointer, DC_GCL_NO_SPECIALS, nil, 0) else {
+            fatalError("chatlistPointer was nil")
+        }
+        // ownership of chatlistPointer transferred here to ChatList object
+        chatList = MRChatList(chatListPointer: chatlistPointer)
+        chatTable.reloadData()
+    }
 }
 
 extension ChatListController: UITableViewDataSource, UITableViewDelegate {
-	func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-		guard let chatList = self.chatList else {
-			fatalError("chatList was nil in data source")
-		}
-
-		return chatList.length
-	}
-
-	func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-		let row = indexPath.row
-		guard let chatList = self.chatList else {
-			fatalError("chatList was nil in data source")
-		}
-
-		let cell: ContactCell
-		if let c = tableView.dequeueReusableCell(withIdentifier: "ChatCell") as? ContactCell {
-			cell = c
-		} else {
-			cell = ContactCell(style: .default, reuseIdentifier: "ChatCell")
-		}
-
-		let chatId = chatList.getChatId(index: row)
-		let chat = MRChat(id: chatId)
-		let summary = chatList.summary(index: row)
-
-		cell.nameLabel.text = chat.name
-		if let img = chat.profileImage {
-			cell.setImage(img)
-		} else {
-			cell.setBackupImage(name: chat.name, color: chat.color)
-		}
-		cell.setVerified(isVerified: chat.isVerified)
-
-		let result1 = summary.text1 ?? ""
-		let result2 = summary.text2 ?? ""
-		let result: String
-		if !result1.isEmpty, !result2.isEmpty {
-			result = "\(result1): \(result2)"
-		} else {
-			result = "\(result1)\(result2)"
-		}
-
-		cell.emailLabel.text = result
-		cell.setTimeLabel(summary.timeStamp)
-		cell.setDeliveryStatusIndicator(summary.state)
-
-		return cell
-	}
-
-	func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-		let row = indexPath.row
-		if let chatId = chatList?.getChatId(index: row) {
-			coordinator?.showChat(chatId: chatId)
-		}
-	}
-
-
-	func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
-		let section = indexPath.section
-		let row = indexPath.row
-		guard let chatList = chatList else {
-			return nil
-		}
-
-		// assigning swipe by delete to chats
-		let delete = UITableViewRowAction(style: .destructive, title: "Delete") { [unowned self] _, indexPath in
-			let chatId = chatList.getChatId(index: row)
-			dc_delete_chat(mailboxPointer, UInt32(chatId))
-			self.getChatList()
-
-		}
-		delete.backgroundColor = UIColor.red
-		return [delete]
-	}
+    func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+        guard let chatList = self.chatList else {
+            fatalError("chatList was nil in data source")
+        }
+
+        return chatList.length
+    }
+
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let row = indexPath.row
+        guard let chatList = self.chatList else {
+            fatalError("chatList was nil in data source")
+        }
+
+        let cell: ContactCell
+        if let c = tableView.dequeueReusableCell(withIdentifier: "ChatCell") as? ContactCell {
+            cell = c
+        } else {
+            cell = ContactCell(style: .default, reuseIdentifier: "ChatCell")
+        }
+
+        let chatId = chatList.getChatId(index: row)
+        let chat = MRChat(id: chatId)
+        let summary = chatList.summary(index: row)
+
+        cell.nameLabel.text = chat.name
+        if let img = chat.profileImage {
+            cell.setImage(img)
+        } else {
+            cell.setBackupImage(name: chat.name, color: chat.color)
+        }
+        cell.setVerified(isVerified: chat.isVerified)
+
+        let result1 = summary.text1 ?? ""
+        let result2 = summary.text2 ?? ""
+        let result: String
+        if !result1.isEmpty, !result2.isEmpty {
+            result = "\(result1): \(result2)"
+        } else {
+            result = "\(result1)\(result2)"
+        }
+
+        cell.emailLabel.text = result
+        cell.setTimeLabel(summary.timeStamp)
+        cell.setDeliveryStatusIndicator(summary.state)
+
+        return cell
+    }
+
+    func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let row = indexPath.row
+        if let chatId = chatList?.getChatId(index: row) {
+            coordinator?.showChat(chatId: chatId)
+        }
+    }
+
+
+    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
+        let section = indexPath.section
+        let row = indexPath.row
+        guard let chatList = chatList else {
+            return nil
+        }
+
+        // assigning swipe by delete to chats
+        let delete = UITableViewRowAction(style: .destructive, title: "Delete") { [unowned self] _, indexPath in
+            let chatId = chatList.getChatId(index: row)
+            dc_delete_chat(mailboxPointer, UInt32(chatId))
+            self.getChatList()
+
+        }
+        delete.backgroundColor = UIColor.red
+        return [delete]
+    }
 }

+ 1920 - 1920
deltachat-ios/Controller/ChatViewController.swift

@@ -5,1967 +5,1967 @@ import UIKit
 import InputBarAccessoryView
 
 protocol MediaSendHandler {
-	func onSuccess()
+    func onSuccess()
 }
 
 extension ChatViewController: MediaSendHandler {
-	func onSuccess() {
-		refreshMessages()
-	}
+    func onSuccess() {
+        refreshMessages()
+    }
 }
 
 class ChatViewController: MessagesViewController {
-	weak var coordinator: ChatViewCoordinator?
-
-	let outgoingAvatarOverlap: CGFloat = 17.5
-	let loadCount = 30
-
-	let chatId: Int
-	let refreshControl = UIRefreshControl()
-	var messageList: [MRMessage] = []
-
-	var msgChangedObserver: Any?
-	var incomingMsgObserver: Any?
-
-	lazy var navBarTap: UITapGestureRecognizer = {
-		UITapGestureRecognizer(target: self, action: #selector(chatProfilePressed))
-	}()
-
-	var disableWriting = false
-	var previewView: UIView?
-	var previewController: PreviewController?
-
-	override var inputAccessoryView: UIView? {
-		if disableWriting {
-			return nil
-		}
-		return messageInputBar
-	}
-
-	private var titleView = ChatTitleView()
-
-	init(chatId: Int, title: String? = nil) {
-		self.chatId = chatId
-		super.init(nibName: nil, bundle: nil)
-		if let title = title {
-			titleView.updateTitleView(title: title, subtitle: nil)
-		}
-		hidesBottomBarWhenPushed = true
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	override func viewDidLoad() {
-		messagesCollectionView.register(CustomMessageCell.self)
-		super.viewDidLoad()
-		navigationItem.titleView = titleView
-
-		view.backgroundColor = DCColors.chatBackgroundColor
-		if !MRConfig.configured {
-			// TODO: display message about nothing being configured
-			return
-		}
-		configureMessageCollectionView()
-
-		if !disableWriting {
-			configureMessageInputBar()
-			messageInputBar.inputTextView.text = textDraft
-			messageInputBar.inputTextView.becomeFirstResponder()
-		}
-
-		loadFirstMessages()
-	}
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-
-		// this will be removed in viewWillDisappear
-		navigationController?.navigationBar.addGestureRecognizer(navBarTap)
-
-		let chat = MRChat(id: chatId)
-		titleView.updateTitleView(title: chat.name, subtitle: chat.subtitle)
-
-		if let image = chat.profileImage {
-			navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(chatProfilePressed))
-		} else {
-			let initialsLabel =  InitialsBadge(name: chat.name, color: chat.color, size: 28)
-			navigationItem.rightBarButtonItem = UIBarButtonItem(customView: initialsLabel)
-		}
-
-		configureMessageMenu()
-
-		if #available(iOS 11.0, *) {
-			if disableWriting {
-				navigationController?.navigationBar.prefersLargeTitles = true
-			}
-		}
-
-		let nc = NotificationCenter.default
-		msgChangedObserver = nc.addObserver(
-			forName: dcNotificationChanged,
-			object: nil,
-			queue: OperationQueue.main
-		) { notification in
-			if let ui = notification.userInfo {
-				if self.disableWriting {
-					// always refresh, as we can't check currently
-					self.refreshMessages()
-				} else if let id = ui["message_id"] as? Int {
-					if id > 0 {
-						self.updateMessage(id)
-					}
-				}
-			}
-		}
-
-		incomingMsgObserver = nc.addObserver(
-			forName: dcNotificationIncoming,
-			object: nil, queue: OperationQueue.main
-		) { notification in
-			if let ui = notification.userInfo {
-				if self.chatId == ui["chat_id"] as! Int {
-					let id = ui["message_id"] as! Int
-					if id > 0 {
-						self.insertMessage(MRMessage(id: id))
-					}
-				}
-			}
-		}
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		super.viewWillDisappear(animated)
-
-		// the navigationController will be used when chatDetail is pushed, so we have to remove that gestureRecognizer
-		navigationController?.navigationBar.removeGestureRecognizer(navBarTap)
-
-		let cnt = Int(dc_get_fresh_msg_cnt(mailboxPointer, UInt32(chatId)))
-		logger.info("updating count for chat \(cnt)")
-		UIApplication.shared.applicationIconBadgeNumber = cnt
-
-		if #available(iOS 11.0, *) {
-			if disableWriting {
-				navigationController?.navigationBar.prefersLargeTitles = false
-			}
-		}
-	}
-
-	override func viewDidDisappear(_ animated: Bool) {
-		super.viewDidDisappear(animated)
-
-		setTextDraft()
-		let nc = NotificationCenter.default
-		if let msgChangedObserver = self.msgChangedObserver {
-			nc.removeObserver(msgChangedObserver)
-		}
-		if let incomingMsgObserver = self.incomingMsgObserver {
-			nc.removeObserver(incomingMsgObserver)
-		}
-	}
-
-	@objc
-	private func loadMoreMessages() {
-		DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
-			DispatchQueue.main.async {
-				self.messageList = self.getMessageIds(self.loadCount, from: self.messageList.count) + self.messageList
-				self.messagesCollectionView.reloadDataAndKeepOffset()
-				self.refreshControl.endRefreshing()
-			}
-		}
-	}
-
-	@objc
-	private func refreshMessages() {
-		DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
-			DispatchQueue.main.async {
-				self.messageList = self.getMessageIds(self.messageList.count)
-				self.messagesCollectionView.reloadDataAndKeepOffset()
-				self.refreshControl.endRefreshing()
-				if self.isLastSectionVisible() {
-					self.messagesCollectionView.scrollToBottom(animated: true)
-				}
-			}
-		}
-	}
-
-	private func loadFirstMessages() {
-		DispatchQueue.global(qos: .userInitiated).async {
-			DispatchQueue.main.async {
-				self.messageList = self.getMessageIds(self.loadCount)
-				self.messagesCollectionView.reloadData()
-				self.refreshControl.endRefreshing()
-				self.messagesCollectionView.scrollToBottom(animated: false)
-			}
-		}
-	}
-
-	private var textDraft: String? {
-		// FIXME: need to free pointer
-		if let draft = dc_get_draft(mailboxPointer, UInt32(chatId)) {
-			if let text = dc_msg_get_text(draft) {
-				let s = String(validatingUTF8: text)!
-				return s
-			}
-			return nil
-		}
-		return nil
-	}
-
-	private func getMessageIds(_ count: Int, from: Int? = nil) -> [MRMessage] {
-		let cMessageIds = dc_get_chat_msgs(mailboxPointer, UInt32(chatId), 0, 0)
-
-		let ids: [Int]
-		if let from = from {
-			ids = Utils.copyAndFreeArrayWithOffset(inputArray: cMessageIds, len: count, skipEnd: from)
-		} else {
-			ids = Utils.copyAndFreeArrayWithLen(inputArray: cMessageIds, len: count)
-		}
-
-		let markIds: [UInt32] = ids.map { UInt32($0) }
-		dc_markseen_msgs(mailboxPointer, UnsafePointer(markIds), Int32(ids.count))
-
-		return ids.map {
-			MRMessage(id: $0)
-		}
-	}
-
-	private func setTextDraft() {
-		if let text = self.messageInputBar.inputTextView.text {
-			let draft = dc_msg_new(mailboxPointer, DC_MSG_TEXT)
-			dc_msg_set_text(draft, text.cString(using: .utf8))
-			dc_set_draft(mailboxPointer, UInt32(chatId), draft)
-
-			// cleanup
-			dc_msg_unref(draft)
-		}
-	}
-
-
-
-	private func configureMessageMenu() {
-		var menuItems: [UIMenuItem]
-
-		if disableWriting {
-			menuItems = [
-				UIMenuItem(title: "Start Chat", action: #selector(MessageCollectionViewCell.messageStartChat(_:))),
-				UIMenuItem(title: "Dismiss", action: #selector(MessageCollectionViewCell.messageDismiss(_:))),
-				UIMenuItem(title: "Block", action: #selector(MessageCollectionViewCell.messageBlock(_:))),
-			]
-		} else {
-			// Configures the UIMenu which is shown when selecting a message
-			menuItems = [
-				UIMenuItem(title: "Info", action: #selector(MessageCollectionViewCell.messageInfo(_:))),
-			]
-		}
-
-		UIMenuController.shared.menuItems = menuItems
-	}
-
-	private func configureMessageCollectionView() {
-		messagesCollectionView.messagesDataSource = self
-		messagesCollectionView.messageCellDelegate = self
-
-		scrollsToBottomOnKeyboardBeginsEditing = true // default false
-		maintainPositionOnKeyboardFrameChanged = true // default false
-		messagesCollectionView.addSubview(refreshControl)
-		refreshControl.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
-
-		let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
-		layout?.sectionInset = UIEdgeInsets(top: 1, left: 8, bottom: 1, right: 8)
-
-		// Hide the outgoing avatar and adjust the label alignment to line up with the messages
-		layout?.setMessageOutgoingAvatarSize(.zero)
-		layout?.setMessageOutgoingMessageTopLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
-		layout?.setMessageOutgoingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
-
-		// Set outgoing avatar to overlap with the message bubble
-		layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 18, bottom: outgoingAvatarOverlap, right: 0)))
-		layout?.setMessageIncomingAvatarSize(CGSize(width: 30, height: 30))
-		layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: -18, bottom: outgoingAvatarOverlap / 2, right: 18))
-		layout?.setMessageIncomingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: -7, left: 38, bottom: 0, right: 0)))
-
-		layout?.setMessageIncomingAccessoryViewSize(CGSize(width: 30, height: 30))
-		layout?.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 8, right: 0))
-		layout?.setMessageOutgoingAccessoryViewSize(CGSize(width: 30, height: 30))
-		layout?.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 8))
-
-		messagesCollectionView.messagesLayoutDelegate = self
-		messagesCollectionView.messagesDisplayDelegate = self
-	}
-
-	private func configureMessageInputBar() {
-		messageInputBar.delegate = self
-		messageInputBar.inputTextView.tintColor = DCColors.primary
-		messageInputBar.inputTextView.placeholder = "Message"
-		messageInputBar.isTranslucent = true
-		messageInputBar.separatorLine.isHidden = true
-		messageInputBar.inputTextView.tintColor = DCColors.primary
-
-		scrollsToBottomOnKeyboardBeginsEditing = true
-
-		messageInputBar.inputTextView.backgroundColor = UIColor(red: 245 / 255, green: 245 / 255, blue: 245 / 255, alpha: 1)
-		messageInputBar.inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
-		messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 38)
-		messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 38)
-		messageInputBar.inputTextView.layer.borderColor = UIColor(red: 200 / 255, green: 200 / 255, blue: 200 / 255, alpha: 1).cgColor
-		messageInputBar.inputTextView.layer.borderWidth = 1.0
-		messageInputBar.inputTextView.layer.cornerRadius = 16.0
-		messageInputBar.inputTextView.layer.masksToBounds = true
-		messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
-		configureInputBarItems()
-	}
-
-	private func configureInputBarItems() {
-
-		messageInputBar.setLeftStackViewWidthConstant(to: 30, animated: false)
-		messageInputBar.setRightStackViewWidthConstant(to: 30, animated: false)
-
-
-		let sendButtonImage = UIImage(named: "paper_plane")?.withRenderingMode(.alwaysTemplate)
-		messageInputBar.sendButton.image = sendButtonImage
-		messageInputBar.sendButton.title = nil
-		messageInputBar.sendButton.tintColor = UIColor(white: 1, alpha: 1)
-		messageInputBar.sendButton.layer.cornerRadius = 15
-		messageInputBar.middleContentViewPadding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 10)	// this adds a padding between textinputfield and send button
-		messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
-		messageInputBar.sendButton.setSize(CGSize(width: 30, height: 30), animated: false)
-
-
-		let leftItems = [
-			InputBarButtonItem()
-				.configure {
-					$0.spacing = .fixed(0)
-					let clipperIcon = #imageLiteral(resourceName: "ic_attach_file_36pt").withRenderingMode(.alwaysTemplate)
-					$0.image = clipperIcon
-					$0.tintColor = UIColor(white: 0.8, alpha: 1)
-					$0.setSize(CGSize(width: 30, height: 30), animated: false)
-				}.onSelected {
-					$0.tintColor = DCColors.primary
-				}.onDeselected {
-					$0.tintColor = UIColor(white: 0.8, alpha: 1)
-				}.onTouchUpInside { _ in
-					self.clipperButtonPressed()
-			}
-		]
-
-		messageInputBar.setStackViewItems(leftItems, forStack: .left, animated: false)
-
-		// This just adds some more flare
-		messageInputBar.sendButton
-			.onEnabled { item in
-				UIView.animate(withDuration: 0.3, animations: {
-					item.backgroundColor = DCColors.primary
-				})
-			}.onDisabled { item in
-				UIView.animate(withDuration: 0.3, animations: {
-					item.backgroundColor = UIColor(white: 0.9, alpha: 1)
-				})
-		}
-	}
-
-	@objc private func chatProfilePressed() {
-		coordinator?.showChatDetail(chatId: chatId)
-	}
-
-	// MARK: - UICollectionViewDataSource
-	public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
-		guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
-			fatalError("notMessagesCollectionView")
-		}
-
-		guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
-			fatalError("nilMessagesDataSource")
-		}
-
-		let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
-		switch message.kind {
-		case .text, .attributedText, .emoji:
-			let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .photo, .video:
-			let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .location:
-			let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .contact:
-			let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .custom:
-			let cell = messagesCollectionView.dequeueReusableCell(CustomMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .audio(_):
-			let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		}
-	}
-
-	override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
-		if action == NSSelectorFromString("messageInfo:") ||
-			action == NSSelectorFromString("messageBlock:") ||
-			action == NSSelectorFromString("messageDismiss:") ||
-			action == NSSelectorFromString("messageStartChat:") {
-			return true
-		} else {
-			return super.collectionView(collectionView, canPerformAction: action, forItemAt: indexPath, withSender: sender)
-		}
-	}
-
-	override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
-		switch action {
-		case NSSelectorFromString("messageInfo:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: View info \(msg.messageId)")
-
-			let msgViewController = MessageInfoViewController(message: msg)
-			if let ctrl = navigationController {
-				ctrl.pushViewController(msgViewController, animated: true)
-			}
-		case NSSelectorFromString("messageStartChat:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: Start Chat \(msg.messageId)")
-			_ = msg.createChat()
-			// TODO: figure out how to properly show the chat after creation
-			refreshMessages()
-		case NSSelectorFromString("messageBlock:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: Block \(msg.messageId)")
-			msg.fromContact.block()
-
-			refreshMessages()
-		case NSSelectorFromString("messageDismiss:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: Dismiss \(msg.messageId)")
-			msg.fromContact.marknoticed()
-
-			refreshMessages()
-		default:
-			super.collectionView(collectionView, performAction: action, forItemAt: indexPath, withSender: sender)
-		}
-	}
+    weak var coordinator: ChatViewCoordinator?
+
+    let outgoingAvatarOverlap: CGFloat = 17.5
+    let loadCount = 30
+
+    let chatId: Int
+    let refreshControl = UIRefreshControl()
+    var messageList: [MRMessage] = []
+
+    var msgChangedObserver: Any?
+    var incomingMsgObserver: Any?
+
+    lazy var navBarTap: UITapGestureRecognizer = {
+        UITapGestureRecognizer(target: self, action: #selector(chatProfilePressed))
+    }()
+
+    var disableWriting = false
+    var previewView: UIView?
+    var previewController: PreviewController?
+
+    override var inputAccessoryView: UIView? {
+        if disableWriting {
+            return nil
+        }
+        return messageInputBar
+    }
+
+    private var titleView = ChatTitleView()
+
+    init(chatId: Int, title: String? = nil) {
+        self.chatId = chatId
+        super.init(nibName: nil, bundle: nil)
+        if let title = title {
+            titleView.updateTitleView(title: title, subtitle: nil)
+        }
+        hidesBottomBarWhenPushed = true
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        messagesCollectionView.register(CustomMessageCell.self)
+        super.viewDidLoad()
+        navigationItem.titleView = titleView
+
+        view.backgroundColor = DCColors.chatBackgroundColor
+        if !MRConfig.configured {
+            // TODO: display message about nothing being configured
+            return
+        }
+        configureMessageCollectionView()
+
+        if !disableWriting {
+            configureMessageInputBar()
+            messageInputBar.inputTextView.text = textDraft
+            messageInputBar.inputTextView.becomeFirstResponder()
+        }
+
+        loadFirstMessages()
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+
+        // this will be removed in viewWillDisappear
+        navigationController?.navigationBar.addGestureRecognizer(navBarTap)
+
+        let chat = MRChat(id: chatId)
+        titleView.updateTitleView(title: chat.name, subtitle: chat.subtitle)
+
+        if let image = chat.profileImage {
+            navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(chatProfilePressed))
+        } else {
+            let initialsLabel =  InitialsBadge(name: chat.name, color: chat.color, size: 28)
+            navigationItem.rightBarButtonItem = UIBarButtonItem(customView: initialsLabel)
+        }
+
+        configureMessageMenu()
+
+        if #available(iOS 11.0, *) {
+            if disableWriting {
+                navigationController?.navigationBar.prefersLargeTitles = true
+            }
+        }
+
+        let nc = NotificationCenter.default
+        msgChangedObserver = nc.addObserver(
+            forName: dcNotificationChanged,
+            object: nil,
+            queue: OperationQueue.main
+        ) { notification in
+            if let ui = notification.userInfo {
+                if self.disableWriting {
+                    // always refresh, as we can't check currently
+                    self.refreshMessages()
+                } else if let id = ui["message_id"] as? Int {
+                    if id > 0 {
+                        self.updateMessage(id)
+                    }
+                }
+            }
+        }
+
+        incomingMsgObserver = nc.addObserver(
+            forName: dcNotificationIncoming,
+            object: nil, queue: OperationQueue.main
+        ) { notification in
+            if let ui = notification.userInfo {
+                if self.chatId == ui["chat_id"] as! Int {
+                    let id = ui["message_id"] as! Int
+                    if id > 0 {
+                        self.insertMessage(MRMessage(id: id))
+                    }
+                }
+            }
+        }
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+
+        // the navigationController will be used when chatDetail is pushed, so we have to remove that gestureRecognizer
+        navigationController?.navigationBar.removeGestureRecognizer(navBarTap)
+
+        let cnt = Int(dc_get_fresh_msg_cnt(mailboxPointer, UInt32(chatId)))
+        logger.info("updating count for chat \(cnt)")
+        UIApplication.shared.applicationIconBadgeNumber = cnt
+
+        if #available(iOS 11.0, *) {
+            if disableWriting {
+                navigationController?.navigationBar.prefersLargeTitles = false
+            }
+        }
+    }
+
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+
+        setTextDraft()
+        let nc = NotificationCenter.default
+        if let msgChangedObserver = self.msgChangedObserver {
+            nc.removeObserver(msgChangedObserver)
+        }
+        if let incomingMsgObserver = self.incomingMsgObserver {
+            nc.removeObserver(incomingMsgObserver)
+        }
+    }
+
+    @objc
+    private func loadMoreMessages() {
+        DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
+            DispatchQueue.main.async {
+                self.messageList = self.getMessageIds(self.loadCount, from: self.messageList.count) + self.messageList
+                self.messagesCollectionView.reloadDataAndKeepOffset()
+                self.refreshControl.endRefreshing()
+            }
+        }
+    }
+
+    @objc
+    private func refreshMessages() {
+        DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
+            DispatchQueue.main.async {
+                self.messageList = self.getMessageIds(self.messageList.count)
+                self.messagesCollectionView.reloadDataAndKeepOffset()
+                self.refreshControl.endRefreshing()
+                if self.isLastSectionVisible() {
+                    self.messagesCollectionView.scrollToBottom(animated: true)
+                }
+            }
+        }
+    }
+
+    private func loadFirstMessages() {
+        DispatchQueue.global(qos: .userInitiated).async {
+            DispatchQueue.main.async {
+                self.messageList = self.getMessageIds(self.loadCount)
+                self.messagesCollectionView.reloadData()
+                self.refreshControl.endRefreshing()
+                self.messagesCollectionView.scrollToBottom(animated: false)
+            }
+        }
+    }
+
+    private var textDraft: String? {
+        // FIXME: need to free pointer
+        if let draft = dc_get_draft(mailboxPointer, UInt32(chatId)) {
+            if let text = dc_msg_get_text(draft) {
+                let s = String(validatingUTF8: text)!
+                return s
+            }
+            return nil
+        }
+        return nil
+    }
+
+    private func getMessageIds(_ count: Int, from: Int? = nil) -> [MRMessage] {
+        let cMessageIds = dc_get_chat_msgs(mailboxPointer, UInt32(chatId), 0, 0)
+
+        let ids: [Int]
+        if let from = from {
+            ids = Utils.copyAndFreeArrayWithOffset(inputArray: cMessageIds, len: count, skipEnd: from)
+        } else {
+            ids = Utils.copyAndFreeArrayWithLen(inputArray: cMessageIds, len: count)
+        }
+
+        let markIds: [UInt32] = ids.map { UInt32($0) }
+        dc_markseen_msgs(mailboxPointer, UnsafePointer(markIds), Int32(ids.count))
+
+        return ids.map {
+            MRMessage(id: $0)
+        }
+    }
+
+    private func setTextDraft() {
+        if let text = self.messageInputBar.inputTextView.text {
+            let draft = dc_msg_new(mailboxPointer, DC_MSG_TEXT)
+            dc_msg_set_text(draft, text.cString(using: .utf8))
+            dc_set_draft(mailboxPointer, UInt32(chatId), draft)
+
+            // cleanup
+            dc_msg_unref(draft)
+        }
+    }
+
+
+
+    private func configureMessageMenu() {
+        var menuItems: [UIMenuItem]
+
+        if disableWriting {
+            menuItems = [
+                UIMenuItem(title: "Start Chat", action: #selector(MessageCollectionViewCell.messageStartChat(_:))),
+                UIMenuItem(title: "Dismiss", action: #selector(MessageCollectionViewCell.messageDismiss(_:))),
+                UIMenuItem(title: "Block", action: #selector(MessageCollectionViewCell.messageBlock(_:))),
+            ]
+        } else {
+            // Configures the UIMenu which is shown when selecting a message
+            menuItems = [
+                UIMenuItem(title: "Info", action: #selector(MessageCollectionViewCell.messageInfo(_:))),
+            ]
+        }
+
+        UIMenuController.shared.menuItems = menuItems
+    }
+
+    private func configureMessageCollectionView() {
+        messagesCollectionView.messagesDataSource = self
+        messagesCollectionView.messageCellDelegate = self
+
+        scrollsToBottomOnKeyboardBeginsEditing = true // default false
+        maintainPositionOnKeyboardFrameChanged = true // default false
+        messagesCollectionView.addSubview(refreshControl)
+        refreshControl.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
+
+        let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
+        layout?.sectionInset = UIEdgeInsets(top: 1, left: 8, bottom: 1, right: 8)
+
+        // Hide the outgoing avatar and adjust the label alignment to line up with the messages
+        layout?.setMessageOutgoingAvatarSize(.zero)
+        layout?.setMessageOutgoingMessageTopLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
+        layout?.setMessageOutgoingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
+
+        // Set outgoing avatar to overlap with the message bubble
+        layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 18, bottom: outgoingAvatarOverlap, right: 0)))
+        layout?.setMessageIncomingAvatarSize(CGSize(width: 30, height: 30))
+        layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: -18, bottom: outgoingAvatarOverlap / 2, right: 18))
+        layout?.setMessageIncomingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: -7, left: 38, bottom: 0, right: 0)))
+
+        layout?.setMessageIncomingAccessoryViewSize(CGSize(width: 30, height: 30))
+        layout?.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 8, right: 0))
+        layout?.setMessageOutgoingAccessoryViewSize(CGSize(width: 30, height: 30))
+        layout?.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 8))
+
+        messagesCollectionView.messagesLayoutDelegate = self
+        messagesCollectionView.messagesDisplayDelegate = self
+    }
+
+    private func configureMessageInputBar() {
+        messageInputBar.delegate = self
+        messageInputBar.inputTextView.tintColor = DCColors.primary
+        messageInputBar.inputTextView.placeholder = "Message"
+        messageInputBar.isTranslucent = true
+        messageInputBar.separatorLine.isHidden = true
+        messageInputBar.inputTextView.tintColor = DCColors.primary
+
+        scrollsToBottomOnKeyboardBeginsEditing = true
+
+        messageInputBar.inputTextView.backgroundColor = UIColor(red: 245 / 255, green: 245 / 255, blue: 245 / 255, alpha: 1)
+        messageInputBar.inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
+        messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 38)
+        messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 38)
+        messageInputBar.inputTextView.layer.borderColor = UIColor(red: 200 / 255, green: 200 / 255, blue: 200 / 255, alpha: 1).cgColor
+        messageInputBar.inputTextView.layer.borderWidth = 1.0
+        messageInputBar.inputTextView.layer.cornerRadius = 16.0
+        messageInputBar.inputTextView.layer.masksToBounds = true
+        messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
+        configureInputBarItems()
+    }
+
+    private func configureInputBarItems() {
+
+        messageInputBar.setLeftStackViewWidthConstant(to: 30, animated: false)
+        messageInputBar.setRightStackViewWidthConstant(to: 30, animated: false)
+
+
+        let sendButtonImage = UIImage(named: "paper_plane")?.withRenderingMode(.alwaysTemplate)
+        messageInputBar.sendButton.image = sendButtonImage
+        messageInputBar.sendButton.title = nil
+        messageInputBar.sendButton.tintColor = UIColor(white: 1, alpha: 1)
+        messageInputBar.sendButton.layer.cornerRadius = 15
+        messageInputBar.middleContentViewPadding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 10)	// this adds a padding between textinputfield and send button
+        messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
+        messageInputBar.sendButton.setSize(CGSize(width: 30, height: 30), animated: false)
+
+
+        let leftItems = [
+            InputBarButtonItem()
+                .configure {
+                    $0.spacing = .fixed(0)
+                    let clipperIcon = #imageLiteral(resourceName: "ic_attach_file_36pt").withRenderingMode(.alwaysTemplate)
+                    $0.image = clipperIcon
+                    $0.tintColor = UIColor(white: 0.8, alpha: 1)
+                    $0.setSize(CGSize(width: 30, height: 30), animated: false)
+                }.onSelected {
+                    $0.tintColor = DCColors.primary
+                }.onDeselected {
+                    $0.tintColor = UIColor(white: 0.8, alpha: 1)
+                }.onTouchUpInside { _ in
+                    self.clipperButtonPressed()
+                }
+        ]
+
+        messageInputBar.setStackViewItems(leftItems, forStack: .left, animated: false)
+
+        // This just adds some more flare
+        messageInputBar.sendButton
+            .onEnabled { item in
+                UIView.animate(withDuration: 0.3, animations: {
+                    item.backgroundColor = DCColors.primary
+                })
+            }.onDisabled { item in
+                UIView.animate(withDuration: 0.3, animations: {
+                    item.backgroundColor = UIColor(white: 0.9, alpha: 1)
+                })
+            }
+    }
+
+    @objc private func chatProfilePressed() {
+        coordinator?.showChatDetail(chatId: chatId)
+    }
+
+    // MARK: - UICollectionViewDataSource
+    public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
+            fatalError("notMessagesCollectionView")
+        }
+
+        guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
+            fatalError("nilMessagesDataSource")
+        }
+
+        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+        switch message.kind {
+        case .text, .attributedText, .emoji:
+            let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .photo, .video:
+            let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .location:
+            let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .contact:
+            let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .custom:
+            let cell = messagesCollectionView.dequeueReusableCell(CustomMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        case .audio(_):
+            let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        }
+    }
+
+    override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
+        if action == NSSelectorFromString("messageInfo:") ||
+            action == NSSelectorFromString("messageBlock:") ||
+            action == NSSelectorFromString("messageDismiss:") ||
+            action == NSSelectorFromString("messageStartChat:") {
+            return true
+        } else {
+            return super.collectionView(collectionView, canPerformAction: action, forItemAt: indexPath, withSender: sender)
+        }
+    }
+
+    override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
+        switch action {
+        case NSSelectorFromString("messageInfo:"):
+            let msg = messageList[indexPath.section]
+            logger.info("message: View info \(msg.messageId)")
+
+            let msgViewController = MessageInfoViewController(message: msg)
+            if let ctrl = navigationController {
+                ctrl.pushViewController(msgViewController, animated: true)
+            }
+        case NSSelectorFromString("messageStartChat:"):
+            let msg = messageList[indexPath.section]
+            logger.info("message: Start Chat \(msg.messageId)")
+            _ = msg.createChat()
+            // TODO: figure out how to properly show the chat after creation
+            refreshMessages()
+        case NSSelectorFromString("messageBlock:"):
+            let msg = messageList[indexPath.section]
+            logger.info("message: Block \(msg.messageId)")
+            msg.fromContact.block()
+
+            refreshMessages()
+        case NSSelectorFromString("messageDismiss:"):
+            let msg = messageList[indexPath.section]
+            logger.info("message: Dismiss \(msg.messageId)")
+            msg.fromContact.marknoticed()
+
+            refreshMessages()
+        default:
+            super.collectionView(collectionView, performAction: action, forItemAt: indexPath, withSender: sender)
+        }
+    }
 }
 
 // MARK: - MessagesDataSource
 extension ChatViewController: MessagesDataSource {
 
-	func numberOfSections(in _: MessagesCollectionView) -> Int {
-		return messageList.count
-	}
-
-	func currentSender() -> SenderType {
-		let currentSender = Sender(id: "1", displayName: "Alice")
-		return currentSender
-	}
-
-	func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
-		return messageList[indexPath.section]
-	}
-
-	func avatar(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> Avatar {
-		let message = messageList[indexPath.section]
-		let contact = message.fromContact
-		return Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
-	}
-
-	func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-		if isInfoMessage(at: indexPath) {
-			return nil
-		}
-
-		if isTimeLabelVisible(at: indexPath) {
-			return NSAttributedString(
-				string: MessageKitDateFormatter.shared.string(from: message.sentDate),
-				attributes: [
-					NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
-					NSAttributedString.Key.foregroundColor: UIColor.darkGray,
-				]
-			)
-		}
-
-		return nil
-	}
-
-	func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-		if !isPreviousMessageSameSender(at: indexPath) {
-			let name = message.sender.displayName
-			let m = messageList[indexPath.section]
-			return NSAttributedString(string: name, attributes: [
-				.font: UIFont.systemFont(ofSize: 14),
-				.foregroundColor: m.fromContact.color,
-				])
-		}
-		return nil
-	}
-
-	func isTimeLabelVisible(at indexPath: IndexPath) -> Bool {
-		guard indexPath.section + 1 < messageList.count else { return false }
-
-		let messageA = messageList[indexPath.section]
-		let messageB = messageList[indexPath.section + 1]
-
-		if messageA.fromContactId == messageB.fromContactId {
-			return false
-		}
-
-		let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
-		let dateA = messageA.sentDate
-		let dateB = messageB.sentDate
-
-		let dayA = (calendar?.component(.day, from: dateA))
-		let dayB = (calendar?.component(.day, from: dateB))
-
-		return dayA != dayB
-	}
-
-	func isPreviousMessageSameSender(at indexPath: IndexPath) -> Bool {
-		guard indexPath.section - 1 >= 0 else { return false }
-		let messageA = messageList[indexPath.section - 1]
-		let messageB = messageList[indexPath.section]
-
-		if messageA.isInfo {
-			return false
-		}
-
-		return messageA.fromContactId == messageB.fromContactId
-	}
-
-	func isInfoMessage(at indexPath: IndexPath) -> Bool {
-		return messageList[indexPath.section].isInfo
-	}
-
-	func isNextMessageSameSender(at indexPath: IndexPath) -> Bool {
-		guard indexPath.section + 1 < messageList.count else { return false }
-		let messageA = messageList[indexPath.section]
-		let messageB = messageList[indexPath.section + 1]
-
-		if messageA.isInfo {
-			return false
-		}
-
-		return messageA.fromContactId == messageB.fromContactId
-	}
-
-	func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-		guard indexPath.section < messageList.count else { return nil }
-		let m = messageList[indexPath.section]
-
-		if m.isInfo || isNextMessageSameSender(at: indexPath) {
-			return nil
-		}
-
-		let timestampAttributes: [NSAttributedString.Key: Any] = [
-			.font: UIFont.systemFont(ofSize: 12),
-			.foregroundColor: UIColor.lightGray,
-		]
-
-		if isFromCurrentSender(message: message) {
-			let text = NSMutableAttributedString()
-			text.append(NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes))
-
-			text.append(NSAttributedString(
-				string: " - " + m.stateDescription(),
-				attributes: [
-					.font: UIFont.systemFont(ofSize: 12),
-					.foregroundColor: UIColor.darkText,
-				]
-			))
-
-			return text
-		}
-
-		return NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes)
-	}
-
-	func updateMessage(_ messageId: Int) {
-		if let index = messageList.firstIndex(where: { $0.id == messageId }) {
-			dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(messageId)]), 1)
-
-			messageList[index] = MRMessage(id: messageId)
-			// Reload section to update header/footer labels
-			messagesCollectionView.performBatchUpdates({
-				messagesCollectionView.reloadSections([index])
-				if index > 0 {
-					messagesCollectionView.reloadSections([index - 1])
-				}
-				if index < messageList.count - 1 {
-					messagesCollectionView.reloadSections([index + 1])
-				}
-			}, completion: { [weak self] _ in
-				if self?.isLastSectionVisible() == true {
-					self?.messagesCollectionView.scrollToBottom(animated: true)
-				}
-			})
-		} else {
-			let msg = MRMessage(id: messageId)
-			if msg.chatId == chatId {
-				insertMessage(msg)
-			}
-		}
-	}
-
-	func insertMessage(_ message: MRMessage) {
-		dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(message.id)]), 1)
-		messageList.append(message)
-		// Reload last section to update header/footer labels and insert a new one
-		messagesCollectionView.performBatchUpdates({
-			messagesCollectionView.insertSections([messageList.count - 1])
-			if messageList.count >= 2 {
-				messagesCollectionView.reloadSections([messageList.count - 2])
-			}
-		}, completion: { [weak self] _ in
-			if self?.isLastSectionVisible() == true {
-				self?.messagesCollectionView.scrollToBottom(animated: true)
-			}
-		})
-	}
-
-	func isLastSectionVisible() -> Bool {
-		guard !messageList.isEmpty else { return false }
-
-		let lastIndexPath = IndexPath(item: 0, section: messageList.count - 1)
-		return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
-	}
+    func numberOfSections(in _: MessagesCollectionView) -> Int {
+        return messageList.count
+    }
+
+    func currentSender() -> SenderType {
+        let currentSender = Sender(id: "1", displayName: "Alice")
+        return currentSender
+    }
+
+    func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
+        return messageList[indexPath.section]
+    }
+
+    func avatar(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> Avatar {
+        let message = messageList[indexPath.section]
+        let contact = message.fromContact
+        return Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
+    }
+
+    func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        if isInfoMessage(at: indexPath) {
+            return nil
+        }
+
+        if isTimeLabelVisible(at: indexPath) {
+            return NSAttributedString(
+                string: MessageKitDateFormatter.shared.string(from: message.sentDate),
+                attributes: [
+                    NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
+                    NSAttributedString.Key.foregroundColor: UIColor.darkGray,
+                ]
+            )
+        }
+
+        return nil
+    }
+
+    func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        if !isPreviousMessageSameSender(at: indexPath) {
+            let name = message.sender.displayName
+            let m = messageList[indexPath.section]
+            return NSAttributedString(string: name, attributes: [
+                .font: UIFont.systemFont(ofSize: 14),
+                .foregroundColor: m.fromContact.color,
+            ])
+        }
+        return nil
+    }
+
+    func isTimeLabelVisible(at indexPath: IndexPath) -> Bool {
+        guard indexPath.section + 1 < messageList.count else { return false }
+
+        let messageA = messageList[indexPath.section]
+        let messageB = messageList[indexPath.section + 1]
+
+        if messageA.fromContactId == messageB.fromContactId {
+            return false
+        }
+
+        let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
+        let dateA = messageA.sentDate
+        let dateB = messageB.sentDate
+
+        let dayA = (calendar?.component(.day, from: dateA))
+        let dayB = (calendar?.component(.day, from: dateB))
+
+        return dayA != dayB
+    }
+
+    func isPreviousMessageSameSender(at indexPath: IndexPath) -> Bool {
+        guard indexPath.section - 1 >= 0 else { return false }
+        let messageA = messageList[indexPath.section - 1]
+        let messageB = messageList[indexPath.section]
+
+        if messageA.isInfo {
+            return false
+        }
+
+        return messageA.fromContactId == messageB.fromContactId
+    }
+
+    func isInfoMessage(at indexPath: IndexPath) -> Bool {
+        return messageList[indexPath.section].isInfo
+    }
+
+    func isNextMessageSameSender(at indexPath: IndexPath) -> Bool {
+        guard indexPath.section + 1 < messageList.count else { return false }
+        let messageA = messageList[indexPath.section]
+        let messageB = messageList[indexPath.section + 1]
+
+        if messageA.isInfo {
+            return false
+        }
+
+        return messageA.fromContactId == messageB.fromContactId
+    }
+
+    func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        guard indexPath.section < messageList.count else { return nil }
+        let m = messageList[indexPath.section]
+
+        if m.isInfo || isNextMessageSameSender(at: indexPath) {
+            return nil
+        }
+
+        let timestampAttributes: [NSAttributedString.Key: Any] = [
+            .font: UIFont.systemFont(ofSize: 12),
+            .foregroundColor: UIColor.lightGray,
+        ]
+
+        if isFromCurrentSender(message: message) {
+            let text = NSMutableAttributedString()
+            text.append(NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes))
+
+            text.append(NSAttributedString(
+                string: " - " + m.stateDescription(),
+                attributes: [
+                    .font: UIFont.systemFont(ofSize: 12),
+                    .foregroundColor: UIColor.darkText,
+                ]
+            ))
+
+            return text
+        }
+
+        return NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes)
+    }
+
+    func updateMessage(_ messageId: Int) {
+        if let index = messageList.firstIndex(where: { $0.id == messageId }) {
+            dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(messageId)]), 1)
+
+            messageList[index] = MRMessage(id: messageId)
+            // Reload section to update header/footer labels
+            messagesCollectionView.performBatchUpdates({
+                messagesCollectionView.reloadSections([index])
+                if index > 0 {
+                    messagesCollectionView.reloadSections([index - 1])
+                }
+                if index < messageList.count - 1 {
+                    messagesCollectionView.reloadSections([index + 1])
+                }
+            }, completion: { [weak self] _ in
+                if self?.isLastSectionVisible() == true {
+                    self?.messagesCollectionView.scrollToBottom(animated: true)
+                }
+            })
+        } else {
+            let msg = MRMessage(id: messageId)
+            if msg.chatId == chatId {
+                insertMessage(msg)
+            }
+        }
+    }
+
+    func insertMessage(_ message: MRMessage) {
+        dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(message.id)]), 1)
+        messageList.append(message)
+        // Reload last section to update header/footer labels and insert a new one
+        messagesCollectionView.performBatchUpdates({
+            messagesCollectionView.insertSections([messageList.count - 1])
+            if messageList.count >= 2 {
+                messagesCollectionView.reloadSections([messageList.count - 2])
+            }
+        }, completion: { [weak self] _ in
+            if self?.isLastSectionVisible() == true {
+                self?.messagesCollectionView.scrollToBottom(animated: true)
+            }
+        })
+    }
+
+    func isLastSectionVisible() -> Bool {
+        guard !messageList.isEmpty else { return false }
+
+        let lastIndexPath = IndexPath(item: 0, section: messageList.count - 1)
+        return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
+    }
 }
 
 // MARK: - MessagesDisplayDelegate
 extension ChatViewController: MessagesDisplayDelegate {
-	// MARK: - Text Messages
-	func textColor(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
-		return .darkText
-	}
-
-	// MARK: - All Messages
-	func backgroundColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
-		return isFromCurrentSender(message: message) ? DCColors.messagePrimaryColor : DCColors.messageSecondaryColor
-	}
-
-	func messageStyle(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageStyle {
-		if isInfoMessage(at: indexPath) {
-			return .custom { view in
-				view.style = .none
-				view.backgroundColor = UIColor(alpha: 10, red: 0, green: 0, blue: 0)
-				let radius: CGFloat = 16
-				let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: radius, height: radius))
-				let mask = CAShapeLayer()
-				mask.path = path.cgPath
-				view.layer.mask = mask
-				view.center.x = self.view.center.x
-			}
-		}
-
-		var corners: UIRectCorner = []
-
-		if isFromCurrentSender(message: message) {
-			corners.formUnion(.topLeft)
-			corners.formUnion(.bottomLeft)
-			if !isPreviousMessageSameSender(at: indexPath) {
-				corners.formUnion(.topRight)
-			}
-			if !isNextMessageSameSender(at: indexPath) {
-				corners.formUnion(.bottomRight)
-			}
-		} else {
-			corners.formUnion(.topRight)
-			corners.formUnion(.bottomRight)
-			if !isPreviousMessageSameSender(at: indexPath) {
-				corners.formUnion(.topLeft)
-			}
-			if !isNextMessageSameSender(at: indexPath) {
-				corners.formUnion(.bottomLeft)
-			}
-		}
-
-		return .custom { view in
-			let radius: CGFloat = 16
-			let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
-			let mask = CAShapeLayer()
-			mask.path = path.cgPath
-			view.layer.mask = mask
-		}
-	}
-
-	func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) {
-		let message = messageList[indexPath.section]
-		let contact = message.fromContact
-		let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
-		avatarView.set(avatar: avatar)
-		avatarView.isHidden = isNextMessageSameSender(at: indexPath) || message.isInfo
-		avatarView.backgroundColor = contact.color
-	}
-
-	func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
-		return [.url, .date, .phoneNumber, .address]
-	}
+    // MARK: - Text Messages
+    func textColor(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
+        return .darkText
+    }
+
+    // MARK: - All Messages
+    func backgroundColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
+        return isFromCurrentSender(message: message) ? DCColors.messagePrimaryColor : DCColors.messageSecondaryColor
+    }
+
+    func messageStyle(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageStyle {
+        if isInfoMessage(at: indexPath) {
+            return .custom { view in
+                view.style = .none
+                view.backgroundColor = UIColor(alpha: 10, red: 0, green: 0, blue: 0)
+                let radius: CGFloat = 16
+                let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: radius, height: radius))
+                let mask = CAShapeLayer()
+                mask.path = path.cgPath
+                view.layer.mask = mask
+                view.center.x = self.view.center.x
+            }
+        }
+
+        var corners: UIRectCorner = []
+
+        if isFromCurrentSender(message: message) {
+            corners.formUnion(.topLeft)
+            corners.formUnion(.bottomLeft)
+            if !isPreviousMessageSameSender(at: indexPath) {
+                corners.formUnion(.topRight)
+            }
+            if !isNextMessageSameSender(at: indexPath) {
+                corners.formUnion(.bottomRight)
+            }
+        } else {
+            corners.formUnion(.topRight)
+            corners.formUnion(.bottomRight)
+            if !isPreviousMessageSameSender(at: indexPath) {
+                corners.formUnion(.topLeft)
+            }
+            if !isNextMessageSameSender(at: indexPath) {
+                corners.formUnion(.bottomLeft)
+            }
+        }
+
+        return .custom { view in
+            let radius: CGFloat = 16
+            let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+            let mask = CAShapeLayer()
+            mask.path = path.cgPath
+            view.layer.mask = mask
+        }
+    }
+
+    func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) {
+        let message = messageList[indexPath.section]
+        let contact = message.fromContact
+        let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
+        avatarView.set(avatar: avatar)
+        avatarView.isHidden = isNextMessageSameSender(at: indexPath) || message.isInfo
+        avatarView.backgroundColor = contact.color
+    }
+
+    func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
+        return [.url, .date, .phoneNumber, .address]
+    }
 }
 
 // MARK: - MessagesLayoutDelegate
 extension ChatViewController: MessagesLayoutDelegate {
-	func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
-		if isTimeLabelVisible(at: indexPath) {
-			return 18
-		}
-		return 0
-	}
-
-	func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
-		if isInfoMessage(at: indexPath) {
-			return 0
-		}
-
-		if isFromCurrentSender(message: message) {
-			return !isPreviousMessageSameSender(at: indexPath) ? 40 : 0
-		} else {
-			return !isPreviousMessageSameSender(at: indexPath) ? (40 + outgoingAvatarOverlap) : 0
-		}
-	}
-
-	func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
-		if isInfoMessage(at: indexPath) {
-			return 0
-		}
-
-		if !isNextMessageSameSender(at: indexPath) {
-			return 16
-		}
-
-		if isFromCurrentSender(message: message) {
-			return 0
-		}
-
-		return 9
-	}
-
-	func heightForLocation(message _: MessageType, at _: IndexPath, with _: CGFloat, in _: MessagesCollectionView) -> CGFloat {
-		return 40
-	}
-
-	func footerViewSize(for _: MessageType, at _: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
-		return CGSize(width: messagesCollectionView.bounds.width, height: 20)
-	}
-
-	@objc private func clipperButtonPressed() {
-		showClipperOptions()
-	}
-
-	private func showClipperOptions() {
-		let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
-		let photoAction = PhotoPickerAlertAction(title: "Photo", style: .default, handler: photoButtonPressed(_:))
-		let videoAction = PhotoPickerAlertAction(title: "Video", style: .default, handler: videoButtonPressed(_:))
-
-		alert.addAction(photoAction)
-		alert.addAction(videoAction)
-		alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
-		self.present(alert, animated: true, completion: nil)
-	}
-
-	private func photoButtonPressed(_ action: UIAlertAction) {
-		coordinator?.showCameraViewController()
-	}
-
-	private func videoButtonPressed(_ action: UIAlertAction) {
-		coordinator?.showVideoLibrary()
-	}
+    func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+        if isTimeLabelVisible(at: indexPath) {
+            return 18
+        }
+        return 0
+    }
+
+    func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+        if isInfoMessage(at: indexPath) {
+            return 0
+        }
+
+        if isFromCurrentSender(message: message) {
+            return !isPreviousMessageSameSender(at: indexPath) ? 40 : 0
+        } else {
+            return !isPreviousMessageSameSender(at: indexPath) ? (40 + outgoingAvatarOverlap) : 0
+        }
+    }
+
+    func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+        if isInfoMessage(at: indexPath) {
+            return 0
+        }
+
+        if !isNextMessageSameSender(at: indexPath) {
+            return 16
+        }
+
+        if isFromCurrentSender(message: message) {
+            return 0
+        }
+
+        return 9
+    }
+
+    func heightForLocation(message _: MessageType, at _: IndexPath, with _: CGFloat, in _: MessagesCollectionView) -> CGFloat {
+        return 40
+    }
+
+    func footerViewSize(for _: MessageType, at _: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
+        return CGSize(width: messagesCollectionView.bounds.width, height: 20)
+    }
+
+    @objc private func clipperButtonPressed() {
+        showClipperOptions()
+    }
+
+    private func showClipperOptions() {
+        let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+        let photoAction = PhotoPickerAlertAction(title: "Photo", style: .default, handler: photoButtonPressed(_:))
+        let videoAction = PhotoPickerAlertAction(title: "Video", style: .default, handler: videoButtonPressed(_:))
+
+        alert.addAction(photoAction)
+        alert.addAction(videoAction)
+        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+        self.present(alert, animated: true, completion: nil)
+    }
+
+    private func photoButtonPressed(_ action: UIAlertAction) {
+        coordinator?.showCameraViewController()
+    }
+
+    private func videoButtonPressed(_ action: UIAlertAction) {
+        coordinator?.showVideoLibrary()
+    }
 
 }
 
 // MARK: - MessageCellDelegate
 extension ChatViewController: MessageCellDelegate {
-	func didTapMessage(in cell: MessageCollectionViewCell) {
-		if let indexPath = messagesCollectionView.indexPath(for: cell) {
-			let message = messageList[indexPath.section]
-
-			if let url = message.fileURL {
-				// find all other messages with same message type
-				var previousUrls: [URL] = []
-				var nextUrls: [URL] = []
-
-				var prev: Int = Int(dc_get_next_media(mailboxPointer, UInt32(message.id), -1, Int32(message.type), 0, 0))
-				while prev != 0 {
-					let prevMessage = MRMessage(id: prev)
-					if let url = prevMessage.fileURL {
-						previousUrls.insert(url, at: 0)
-					}
-					prev = Int(dc_get_next_media(mailboxPointer, UInt32(prevMessage.id), -1, Int32(prevMessage.type), 0, 0))
-				}
-
-				var next: Int = Int(dc_get_next_media(mailboxPointer, UInt32(message.id), 1, Int32(message.type), 0, 0))
-				while next != 0 {
-					let nextMessage = MRMessage(id: next)
-					if let url = nextMessage.fileURL {
-						nextUrls.insert(url, at: 0)
-					}
-					next = Int(dc_get_next_media(mailboxPointer, UInt32(nextMessage.id), 1, Int32(nextMessage.type), 0, 0))
-				}
-
-				// these are the files user will be able to swipe trough
-				let mediaUrls: [URL] = previousUrls + [url] + nextUrls
-				previewController = PreviewController(currentIndex: previousUrls.count, urls: mediaUrls)
-				present(previewController!.qlController, animated: true)
-			}
-		}
-	}
-
-	func didTapAvatar(in _: MessageCollectionViewCell) {
-		logger.info("Avatar tapped")
-	}
-
-	@objc(didTapCellTopLabelIn:) func didTapCellTopLabel(in _: MessageCollectionViewCell) {
-		logger.info("Top label tapped")
-	}
-
-	func didTapBottomLabel(in _: MessageCollectionViewCell) {
-		print("Bottom label tapped")
-	}
+    func didTapMessage(in cell: MessageCollectionViewCell) {
+        if let indexPath = messagesCollectionView.indexPath(for: cell) {
+            let message = messageList[indexPath.section]
+
+            if let url = message.fileURL {
+                // find all other messages with same message type
+                var previousUrls: [URL] = []
+                var nextUrls: [URL] = []
+
+                var prev: Int = Int(dc_get_next_media(mailboxPointer, UInt32(message.id), -1, Int32(message.type), 0, 0))
+                while prev != 0 {
+                    let prevMessage = MRMessage(id: prev)
+                    if let url = prevMessage.fileURL {
+                        previousUrls.insert(url, at: 0)
+                    }
+                    prev = Int(dc_get_next_media(mailboxPointer, UInt32(prevMessage.id), -1, Int32(prevMessage.type), 0, 0))
+                }
+
+                var next: Int = Int(dc_get_next_media(mailboxPointer, UInt32(message.id), 1, Int32(message.type), 0, 0))
+                while next != 0 {
+                    let nextMessage = MRMessage(id: next)
+                    if let url = nextMessage.fileURL {
+                        nextUrls.insert(url, at: 0)
+                    }
+                    next = Int(dc_get_next_media(mailboxPointer, UInt32(nextMessage.id), 1, Int32(nextMessage.type), 0, 0))
+                }
+
+                // these are the files user will be able to swipe trough
+                let mediaUrls: [URL] = previousUrls + [url] + nextUrls
+                previewController = PreviewController(currentIndex: previousUrls.count, urls: mediaUrls)
+                present(previewController!.qlController, animated: true)
+            }
+        }
+    }
+
+    func didTapAvatar(in _: MessageCollectionViewCell) {
+        logger.info("Avatar tapped")
+    }
+
+    @objc(didTapCellTopLabelIn:) func didTapCellTopLabel(in _: MessageCollectionViewCell) {
+        logger.info("Top label tapped")
+    }
+
+    func didTapBottomLabel(in _: MessageCollectionViewCell) {
+        print("Bottom label tapped")
+    }
 }
 
 // MARK: - MessageLabelDelegate
 extension ChatViewController: MessageLabelDelegate {
-	func didSelectAddress(_ addressComponents: [String: String]) {
-		let mapAddress = Utils.formatAddressForQuery(address: addressComponents)
-		if let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
-			// Use query, to handle malformed addresses
-			if let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") {
-				UIApplication.shared.open(url as URL)
-			}
-		}
-	}
-
-	func didSelectDate(_ date: Date) {
-		let interval = date.timeIntervalSinceReferenceDate
-		if let url = NSURL(string: "calshow:\(interval)") {
-			UIApplication.shared.open(url as URL)
-		}
-	}
-
-	func didSelectPhoneNumber(_ phoneNumber: String) {
-		logger.info("phone open", phoneNumber)
-		if let escapedPhoneNumber = phoneNumber.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
-			if let url = NSURL(string: "tel:\(escapedPhoneNumber)") {
-				UIApplication.shared.open(url as URL)
-			}
-		}
-	}
-
-	func didSelectURL(_ url: URL) {
-		UIApplication.shared.open(url)
-	}
+    func didSelectAddress(_ addressComponents: [String: String]) {
+        let mapAddress = Utils.formatAddressForQuery(address: addressComponents)
+        if let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+            // Use query, to handle malformed addresses
+            if let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") {
+                UIApplication.shared.open(url as URL)
+            }
+        }
+    }
+
+    func didSelectDate(_ date: Date) {
+        let interval = date.timeIntervalSinceReferenceDate
+        if let url = NSURL(string: "calshow:\(interval)") {
+            UIApplication.shared.open(url as URL)
+        }
+    }
+
+    func didSelectPhoneNumber(_ phoneNumber: String) {
+        logger.info("phone open", phoneNumber)
+        if let escapedPhoneNumber = phoneNumber.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+            if let url = NSURL(string: "tel:\(escapedPhoneNumber)") {
+                UIApplication.shared.open(url as URL)
+            }
+        }
+    }
+
+    func didSelectURL(_ url: URL) {
+        UIApplication.shared.open(url)
+    }
 }
 
 // MARK: - LocationMessageDisplayDelegate
 /*
-extension ChatViewController: LocationMessageDisplayDelegate {
-func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? {
-let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil)
-let pinImage = #imageLiteral(resourceName: "ic_block_36pt").withRenderingMode(.alwaysTemplate)
-annotationView.image = pinImage
-annotationView.centerOffset = CGPoint(x: 0, y: -pinImage.size.height / 2)
-return annotationView
-}
-func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)? {
-return { view in
-view.layer.transform = CATransform3DMakeScale(0, 0, 0)
-view.alpha = 0.0
-UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: [], animations: {
-view.layer.transform = CATransform3DIdentity
-view.alpha = 1.0
-}, completion: nil)
-}
-}
-}
-*/
+ extension ChatViewController: LocationMessageDisplayDelegate {
+ func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? {
+ let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil)
+ let pinImage = #imageLiteral(resourceName: "ic_block_36pt").withRenderingMode(.alwaysTemplate)
+ annotationView.image = pinImage
+ annotationView.centerOffset = CGPoint(x: 0, y: -pinImage.size.height / 2)
+ return annotationView
+ }
+ func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)? {
+ return { view in
+ view.layer.transform = CATransform3DMakeScale(0, 0, 0)
+ view.alpha = 0.0
+ UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: [], animations: {
+ view.layer.transform = CATransform3DIdentity
+ view.alpha = 1.0
+ }, completion: nil)
+ }
+ }
+ }
+ */
 
 // MARK: - MessageInputBarDelegate
 extension ChatViewController: InputBarAccessoryViewDelegate {
-	func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
-		DispatchQueue.global().async {
-			dc_send_text_msg(mailboxPointer, UInt32(self.chatId), text)
-		}
-		inputBar.inputTextView.text = String()
-	}
+    func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
+        DispatchQueue.global().async {
+            dc_send_text_msg(mailboxPointer, UInt32(self.chatId), text)
+        }
+        inputBar.inputTextView.text = String()
+    }
 }
 
 /*
-extension ChatViewController: MessageInputBarDelegate {
-}
-*/
+ extension ChatViewController: MessageInputBarDelegate {
+ }
+ */
 
 // MARK: - MessageCollectionViewCell
 extension MessageCollectionViewCell {
-	@objc func messageInfo(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageInfo(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-
-	@objc func messageBlock(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageBlock(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-
-	@objc func messageDismiss(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageDismiss(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-
-	@objc func messageStartChat(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageStartChat(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-}
-
-/*
-class ChatViewController: MessagesViewController {
-	weak var coordinator: ChatViewCoordinator?
-
-	let outgoingAvatarOverlap: CGFloat = 17.5
-	let loadCount = 30
-
-	private var isGroupChat: Bool {
-		return MRChat(id: chatId).chatType != .SINGLE
-	}
-
-	let chatId: Int
-	let refreshControl = UIRefreshControl()
-	var messageList: [MRMessage] = []
-
-	var msgChangedObserver: Any?
-	var incomingMsgObserver: Any?
-
-	lazy var navBarTap: UITapGestureRecognizer = {
-		UITapGestureRecognizer(target: self, action: #selector(chatProfilePressed))
-	}()
-
-	var disableWriting = false
-
-	var previewView: UIView?
-	var previewController: PreviewController?
-
-	override var inputAccessoryView: UIView? {
-		if disableWriting {
-			return nil
-		}
-		return messageInputBar
-	}
-
-	init(chatId: Int, title: String? = nil) {
-		self.chatId = chatId
-		super.init(nibName: nil, bundle: nil)
-		if let title = title {
-			updateTitleView(title: title, subtitle: nil)
-		}
-		hidesBottomBarWhenPushed = true
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	override func viewDidLoad() {
-		messagesCollectionView.register(CustomCell.self)
-		super.viewDidLoad()
-		view.backgroundColor = DCColors.chatBackgroundColor
-		if !MRConfig.configured {
-			// TODO: display message about nothing being configured
-			return
-		}
-		configureMessageCollectionView()
-
-		if !disableWriting {
-			configureMessageInputBar()
-			messageInputBar.inputTextView.text = textDraft
-			messageInputBar.inputTextView.becomeFirstResponder()
-		}
-
-		loadFirstMessages()
-	}
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-
-		// this will be removed in viewWillDisappear
-		navigationController?.navigationBar.addGestureRecognizer(navBarTap)
-
-		let chat = MRChat(id: chatId)
-		updateTitleView(title: chat.name, subtitle: chat.subtitle)
-
-		if let image = chat.profileImage {
-			navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(chatProfilePressed))
-		} else {
-			let initialsLabel = InitialsLabel(name: chat.name, color: chat.color, size: 28)
-			navigationItem.rightBarButtonItem = UIBarButtonItem(customView: initialsLabel)
-		}
-
-		configureMessageMenu()
-
-		if #available(iOS 11.0, *) {
-			if disableWriting {
-				navigationController?.navigationBar.prefersLargeTitles = true
-			}
-		}
-
-		let nc = NotificationCenter.default
-		msgChangedObserver = nc.addObserver(
-			forName: dcNotificationChanged,
-			object: nil,
-			queue: OperationQueue.main
-		) { notification in
-			if let ui = notification.userInfo {
-				if self.disableWriting {
-					// always refresh, as we can't check currently
-					self.refreshMessages()
-				} else if let id = ui["message_id"] as? Int {
-					if id > 0 {
-						self.updateMessage(id)
-					}
-				}
-			}
-		}
-
-		incomingMsgObserver = nc.addObserver(
-			forName: dcNotificationIncoming,
-			object: nil, queue: OperationQueue.main
-		) { notification in
-			if let ui = notification.userInfo {
-				if self.chatId == ui["chat_id"] as! Int {
-					let id = ui["message_id"] as! Int
-					if id > 0 {
-						self.insertMessage(MRMessage(id: id))
-					}
-				}
-			}
-		}
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		super.viewWillDisappear(animated)
-
-		// the navigationController will be used when chatDetail is pushed, so we have to remove that gestureRecognizer
-		navigationController?.navigationBar.removeGestureRecognizer(navBarTap)
-
-		let cnt = Int(dc_get_fresh_msg_cnt(mailboxPointer, UInt32(chatId)))
-		logger.info("updating count for chat \(cnt)")
-		UIApplication.shared.applicationIconBadgeNumber = cnt
-
-		if #available(iOS 11.0, *) {
-			if disableWriting {
-				navigationController?.navigationBar.prefersLargeTitles = false
-			}
-		}
-	}
-
-	override func viewDidDisappear(_ animated: Bool) {
-		super.viewDidDisappear(animated)
-
-		setTextDraft()
-		let nc = NotificationCenter.default
-		if let msgChangedObserver = self.msgChangedObserver {
-			nc.removeObserver(msgChangedObserver)
-		}
-		if let incomingMsgObserver = self.incomingMsgObserver {
-			nc.removeObserver(incomingMsgObserver)
-		}
-	}
-
-	@objc
-	private func loadMoreMessages() {
-		DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
-			DispatchQueue.main.async {
-				self.messageList = self.getMessageIds(self.loadCount, from: self.messageList.count) + self.messageList
-				self.messagesCollectionView.reloadDataAndKeepOffset()
-				self.refreshControl.endRefreshing()
-			}
-		}
-	}
-
-	@objc
-	private func refreshMessages() {
-		DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
-			DispatchQueue.main.async {
-				self.messageList = self.getMessageIds(self.messageList.count)
-				self.messagesCollectionView.reloadDataAndKeepOffset()
-				self.refreshControl.endRefreshing()
-				if self.isLastSectionVisible() {
-					self.messagesCollectionView.scrollToBottom(animated: true)
-				}
-			}
-		}
-	}
-
-	private func loadFirstMessages() {
-		DispatchQueue.global(qos: .userInitiated).async {
-			DispatchQueue.main.async {
-				self.messageList = self.getMessageIds(self.loadCount)
-				self.messagesCollectionView.reloadData()
-				self.refreshControl.endRefreshing()
-				self.messagesCollectionView.scrollToBottom(animated: false)
-			}
-		}
-	}
-
-	private var textDraft: String? {
-		// FIXME: need to free pointer
-		if let draft = dc_get_draft(mailboxPointer, UInt32(chatId)) {
-			if let text = dc_msg_get_text(draft) {
-				let s = String(validatingUTF8: text)!
-				return s
-			}
-			return nil
-		}
-		return nil
-	}
-
-	private func getMessageIds(_ count: Int, from: Int? = nil) -> [MRMessage] {
-		let cMessageIds = dc_get_chat_msgs(mailboxPointer, UInt32(chatId), 0, 0)
-
-		let ids: [Int]
-		if let from = from {
-			ids = Utils.copyAndFreeArrayWithOffset(inputArray: cMessageIds, len: count, skipEnd: from)
-		} else {
-			ids = Utils.copyAndFreeArrayWithLen(inputArray: cMessageIds, len: count)
-		}
-
-		let markIds: [UInt32] = ids.map { UInt32($0) }
-		dc_markseen_msgs(mailboxPointer, UnsafePointer(markIds), Int32(ids.count))
-
-		return ids.map {
-			MRMessage(id: $0)
-		}
-	}
-
-	private func setTextDraft() {
-		if let text = self.messageInputBar.inputTextView.text {
-			let draft = dc_msg_new(mailboxPointer, DC_MSG_TEXT)
-			dc_msg_set_text(draft, text.cString(using: .utf8))
-			dc_set_draft(mailboxPointer, UInt32(chatId), draft)
-
-			// cleanup
-			dc_msg_unref(draft)
-		}
-	}
-
-
-
-	private func configureMessageMenu() {
-		var menuItems: [UIMenuItem]
-
-		if disableWriting {
-			menuItems = [
-				UIMenuItem(title: "Start Chat", action: #selector(MessageCollectionViewCell.messageStartChat(_:))),
-				UIMenuItem(title: "Dismiss", action: #selector(MessageCollectionViewCell.messageDismiss(_:))),
-				UIMenuItem(title: "Block", action: #selector(MessageCollectionViewCell.messageBlock(_:))),
-			]
-		} else {
-			// Configures the UIMenu which is shown when selecting a message
-			menuItems = [
-				UIMenuItem(title: "Info", action: #selector(MessageCollectionViewCell.messageInfo(_:))),
-			]
-		}
-
-		UIMenuController.shared.menuItems = menuItems
-	}
-
-	private func configureMessageCollectionView() {
-		messagesCollectionView.messagesDataSource = self
-		messagesCollectionView.messageCellDelegate = self
-
-		scrollsToBottomOnKeyboardBeginsEditing = true // default false
-		maintainPositionOnKeyboardFrameChanged = true // default false
-		messagesCollectionView.addSubview(refreshControl)
-		refreshControl.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
-
-		let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
-		messagesCollectionView.messagesLayoutDelegate = self
-		messagesCollectionView.messagesDisplayDelegate = self
-
-
-		/*
-		let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
-		layout?.sectionInset = UIEdgeInsets(top: 1, left: 8, bottom: 1, right: 8)
-
-		// Hide the outgoing avatar and adjust the label alignment to line up with the messages
-		layout?.setMessageOutgoingAvatarSize(.zero)
-		layout?.setMessageOutgoingMessageTopLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
-		layout?.setMessageOutgoingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
-
-		// Set outgoing avatar to overlap with the message bubble
-		if isGroupChat {
-			layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 18, bottom: outgoingAvatarOverlap, right: 0)))
-			layout?.setMessageIncomingAvatarSize(CGSize(width: 30, height: 30))
-			layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: -18, bottom: outgoingAvatarOverlap / 2, right: 18))
-			layout?.setMessageIncomingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: -7, left: 38, bottom: 0, right: 0)))
-
-		} else {
-			layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0)))
-			layout?.setMessageIncomingAvatarSize(CGSize.zero) // no batch displayed in singleChats
-			layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: 0, bottom: outgoingAvatarOverlap / 2, right: 18))
-			layout?.setMessageIncomingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: -7, left: 12, bottom: 0, right: 0)))
-		}
-
-		layout?.setMessageIncomingAccessoryViewSize(CGSize(width: 30, height: 30))
-		layout?.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 8, right: 0))
-		layout?.setMessageOutgoingAccessoryViewSize(CGSize(width: 30, height: 30))
-		layout?.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 8))
-
-		messagesCollectionView.messagesLayoutDelegate = self
-		messagesCollectionView.messagesDisplayDelegate = self
-		*/
-	}
-
-	private func configureMessageInputBar() {
-		messageInputBar.delegate = self
-		messageInputBar.inputTextView.tintColor = DCColors.primary
-
-		messageInputBar.isTranslucent = true
-		messageInputBar.separatorLine.isHidden = true
-		messageInputBar.inputTextView.tintColor = DCColors.primary
-
-		scrollsToBottomOnKeyboardBeginsEditing = true
-
-		messageInputBar.inputTextView.backgroundColor = UIColor(red: 245 / 255, green: 245 / 255, blue: 245 / 255, alpha: 1)
-		messageInputBar.inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
-		messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 38)
-		messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 38)
-		messageInputBar.inputTextView.layer.borderColor = UIColor(red: 200 / 255, green: 200 / 255, blue: 200 / 255, alpha: 1).cgColor
-		messageInputBar.inputTextView.layer.borderWidth = 1.0
-		messageInputBar.inputTextView.layer.cornerRadius = 16.0
-		messageInputBar.inputTextView.layer.masksToBounds = true
-		messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
-		configureInputBarItems()
-	}
-
-	private func configureInputBarItems() {
-
-		messageInputBar.setLeftStackViewWidthConstant(to: 30, animated: false)
-		messageInputBar.setRightStackViewWidthConstant(to: 30, animated: false)
-
-
-		let sendButtonImage = UIImage(named: "paper_plane")?.withRenderingMode(.alwaysTemplate)
-		messageInputBar.sendButton.image = sendButtonImage
-		messageInputBar.sendButton.title = nil
-		messageInputBar.sendButton.tintColor = UIColor(white: 1, alpha: 1)
-		messageInputBar.sendButton.layer.cornerRadius = 15
-		messageInputBar.middleContentViewPadding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 10)	// this adds a padding between textinputfield and send button
-		messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
-		messageInputBar.sendButton.setSize(CGSize(width: 30, height: 30), animated: false)
-
-		let leftItems = [
-			InputBarButtonItem()
-				.configure {
-					$0.spacing = .fixed(0)
-					let clipperIcon = #imageLiteral(resourceName: "ic_attach_file_36pt").withRenderingMode(.alwaysTemplate)
-					$0.image = clipperIcon
-					$0.tintColor = UIColor(white: 0.8, alpha: 1)
-					$0.setSize(CGSize(width: 30, height: 30), animated: false)
-				}.onSelected {
-					$0.tintColor = DCColors.primary
-				}.onDeselected {
-					$0.tintColor = UIColor(white: 0.8, alpha: 1)
-				}.onTouchUpInside { _ in
-					self.clipperButtonPressed()
-			}
-			/*
-			InputBarButtonItem()
-			.configure {
-			$0.spacing = .fixed(0)
-			$0.image = UIImage(named: "camera")?.withRenderingMode(.alwaysTemplate)
-			$0.setSize(CGSize(width: 36, height: 36), animated: false)
-			$0.tintColor = UIColor(white: 0.8, alpha: 1)
-			}.onSelected {
-			$0.tintColor = DCColors.primary
-			}.onDeselected {
-			$0.tintColor = UIColor(white: 0.8, alpha: 1)
-			}.onTouchUpInside { _ in
-			self.didPressPhotoButton()
-			},
-			*/
-		]
-
-		messageInputBar.setStackViewItems(leftItems, forStack: .left, animated: false)
-
-		// This just adds some more flare
-		messageInputBar.sendButton
-			.onEnabled { item in
-				UIView.animate(withDuration: 0.3, animations: {
-					item.backgroundColor = DCColors.primary
-				})
-			}.onDisabled { item in
-				UIView.animate(withDuration: 0.3, animations: {
-					item.backgroundColor = UIColor(white: 0.9, alpha: 1)
-				})
-		}
-	}
-
-
-	@objc private func chatProfilePressed() {
-		coordinator?.showChatDetail(chatId: chatId)
-	}
-
-	// MARK: - UICollectionViewDataSource
-	public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
-		guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
-			fatalError("notMessagesCollectionView")
-		}
-
-		guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
-			fatalError("nilMessagesDataSource")
-		}
-
-		let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
-		switch message.kind {
-		case .text, .attributedText, .emoji:
-			let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .photo, .video:
-			let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .location:
-			let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .contact:
-			let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .custom:
-			let cell = messagesCollectionView.dequeueReusableCell(CustomCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		case .audio(_):
-			let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath)
-			cell.configure(with: message, at: indexPath, and: messagesCollectionView)
-			return cell
-		}
-	}
-
-	override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
-		if action == NSSelectorFromString("messageInfo:") ||
-			action == NSSelectorFromString("messageBlock:") ||
-			action == NSSelectorFromString("messageDismiss:") ||
-			action == NSSelectorFromString("messageStartChat:") {
-			return true
-		} else {
-			return super.collectionView(collectionView, canPerformAction: action, forItemAt: indexPath, withSender: sender)
-		}
-	}
-
-	override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
-		switch action {
-		case NSSelectorFromString("messageInfo:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: View info \(msg.messageId)")
-
-			let msgViewController = MessageInfoViewController(message: msg)
-			if let ctrl = navigationController {
-				ctrl.pushViewController(msgViewController, animated: true)
-			}
-		case NSSelectorFromString("messageStartChat:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: Start Chat \(msg.messageId)")
-			_ = msg.createChat()
-			// TODO: figure out how to properly show the chat after creation
-			refreshMessages()
-		case NSSelectorFromString("messageBlock:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: Block \(msg.messageId)")
-			msg.fromContact.block()
-
-			refreshMessages()
-		case NSSelectorFromString("messageDismiss:"):
-			let msg = messageList[indexPath.section]
-			logger.info("message: Dismiss \(msg.messageId)")
-			msg.fromContact.marknoticed()
-
-			refreshMessages()
-		default:
-			super.collectionView(collectionView, performAction: action, forItemAt: indexPath, withSender: sender)
-		}
-	}
-}
-
-// MARK: - MessagesDataSource
-extension ChatViewController: MessagesDataSource {
-
-	func numberOfSections(in _: MessagesCollectionView) -> Int {
-		return messageList.count
-	}
-
-	func currentSender() -> SenderType {
-		let currentSender = Sender(id: "1", displayName: "Alice")
-		return currentSender
-	}
-
-	func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
-		return messageList[indexPath.section]
-	}
-
-	func avatar(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> Avatar {
-		let message = messageList[indexPath.section]
-		let contact = message.fromContact
-		return Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
-	}
-
-	func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-		if isInfoMessage(at: indexPath) {
-			return nil
-		}
-
-		if isTimeLabelVisible(at: indexPath) {
-			return NSAttributedString(
-				string: MessageKitDateFormatter.shared.string(from: message.sentDate),
-				attributes: [
-					NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
-					NSAttributedString.Key.foregroundColor: UIColor.darkGray,
-				]
-			)
-		}
-
-		return nil
-	}
-
-	func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-
-		if !isGroupChat {
-			return nil
-		}
-
-		if !isPreviousMessageSameSender(at: indexPath) {
-			let name = message.sender.displayName
-			let m = messageList[indexPath.section]
-			return NSAttributedString(string: name, attributes: [
-				.font: UIFont.systemFont(ofSize: 14),
-				.foregroundColor: m.fromContact.color,
-				])
-		}
-		return nil
-	}
-
-	func isTimeLabelVisible(at indexPath: IndexPath) -> Bool {
-		guard indexPath.section + 1 < messageList.count else { return false }
-
-		let messageA = messageList[indexPath.section]
-		let messageB = messageList[indexPath.section + 1]
-
-		if messageA.fromContactId == messageB.fromContactId {
-			return false
-		}
-
-		let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
-		let dateA = messageA.sentDate
-		let dateB = messageB.sentDate
-
-		let dayA = (calendar?.component(.day, from: dateA))
-		let dayB = (calendar?.component(.day, from: dateB))
-
-		return dayA != dayB
-	}
-
-	func isPreviousMessageSameSender(at indexPath: IndexPath) -> Bool {
-		guard indexPath.section - 1 >= 0 else { return false }
-		let messageA = messageList[indexPath.section - 1]
-		let messageB = messageList[indexPath.section]
-
-		if messageA.isInfo {
-			return false
-		}
-
-		return messageA.fromContactId == messageB.fromContactId
-	}
-
-	func isInfoMessage(at indexPath: IndexPath) -> Bool {
-		return messageList[indexPath.section].isInfo
-	}
-
-	func isNextMessageSameSender(at indexPath: IndexPath) -> Bool {
-		guard indexPath.section + 1 < messageList.count else { return false }
-		let messageA = messageList[indexPath.section]
-		let messageB = messageList[indexPath.section + 1]
-
-		if messageA.isInfo {
-			return false
-		}
-
-		return messageA.fromContactId == messageB.fromContactId
-	}
-
-	func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-		guard indexPath.section < messageList.count else { return nil }
-		let m = messageList[indexPath.section]
-
-		if m.isInfo || isNextMessageSameSender(at: indexPath) {
-			return nil
-		}
-
-		let timestampAttributes: [NSAttributedString.Key: Any] = [
-			.font: UIFont.systemFont(ofSize: 12),
-			.foregroundColor: UIColor.lightGray,
-		]
-
-		if isFromCurrentSender(message: message) {
-			let text = NSMutableAttributedString()
-			text.append(NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes))
-
-			text.append(NSAttributedString(
-				string: " - " + m.stateDescription(),
-				attributes: [
-					.font: UIFont.systemFont(ofSize: 12),
-					.foregroundColor: UIColor.darkText,
-				]
-			))
-
-			return text
-		}
-
-		return NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes)
-	}
-
-	func updateMessage(_ messageId: Int) {
-		if let index = messageList.firstIndex(where: { $0.id == messageId }) {
-			dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(messageId)]), 1)
-
-			messageList[index] = MRMessage(id: messageId)
-			// Reload section to update header/footer labels
-			messagesCollectionView.performBatchUpdates({
-				messagesCollectionView.reloadSections([index])
-				if index > 0 {
-					messagesCollectionView.reloadSections([index - 1])
-				}
-				if index < messageList.count - 1 {
-					messagesCollectionView.reloadSections([index + 1])
-				}
-			}, completion: { [weak self] _ in
-				if self?.isLastSectionVisible() == true {
-					self?.messagesCollectionView.scrollToBottom(animated: true)
-				}
-			})
-		} else {
-			let msg = MRMessage(id: messageId)
-			if msg.chatId == chatId {
-				insertMessage(msg)
-			}
-		}
-	}
-
-	func insertMessage(_ message: MRMessage) {
-		dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(message.id)]), 1)
-		messageList.append(message)
-		// Reload last section to update header/footer labels and insert a new one
-		messagesCollectionView.performBatchUpdates({
-			messagesCollectionView.insertSections([messageList.count - 1])
-			if messageList.count >= 2 {
-				messagesCollectionView.reloadSections([messageList.count - 2])
-			}
-		}, completion: { [weak self] _ in
-			if self?.isLastSectionVisible() == true {
-				self?.messagesCollectionView.scrollToBottom(animated: true)
-			}
-		})
-	}
-
-	func isLastSectionVisible() -> Bool {
-		guard !messageList.isEmpty else { return false }
-
-		let lastIndexPath = IndexPath(item: 0, section: messageList.count - 1)
-		return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
-	}
-}
-
-// MARK: - MessagesDisplayDelegate
-
-extension ChatViewController: MessagesDisplayDelegate {
-
-	func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) {
-		let message = messageList[indexPath.section]
-		let contact = message.fromContact
-		let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
-		avatarView.set(avatar: avatar)
-		avatarView.isHidden = isNextMessageSameSender(at: indexPath) || message.isInfo
-		avatarView.backgroundColor = contact.color
-	}
-
-
-	func messageStyle(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageStyle {
-		if isInfoMessage(at: indexPath) {
-			return .custom { view in
-				view.style = .none
-				view.backgroundColor = UIColor(alpha: 10, red: 0, green: 0, blue: 0)
-				let radius: CGFloat = 16
-				let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: radius, height: radius))
-				let mask = CAShapeLayer()
-				mask.path = path.cgPath
-				view.layer.mask = mask
-				view.center.x = self.view.center.x
-			}
-		}
-
-		var corners: UIRectCorner = []
-
-		if isFromCurrentSender(message: message) {
-			corners.formUnion(.topLeft)
-			corners.formUnion(.bottomLeft)
-			if !isPreviousMessageSameSender(at: indexPath) {
-				corners.formUnion(.topRight)
-			}
-			if !isNextMessageSameSender(at: indexPath) {
-				corners.formUnion(.bottomRight)
-			}
-		} else {
-			corners.formUnion(.topRight)
-			corners.formUnion(.bottomRight)
-			if !isPreviousMessageSameSender(at: indexPath) {
-				corners.formUnion(.topLeft)
-			}
-			if !isNextMessageSameSender(at: indexPath) {
-				corners.formUnion(.bottomLeft)
-			}
-		}
-
-		return .custom { view in
-			let radius: CGFloat = 16
-			let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
-			let mask = CAShapeLayer()
-			mask.path = path.cgPath
-			view.layer.mask = mask
-		}
-	}
-}
-
-
-/*
-extension ChatViewController: MessagesDisplayDelegate {
-	// MARK: - Text Messages
-	func textColor(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
-		return .darkText
-	}
-
-	// MARK: - All Messages
-	func backgroundColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
-		return isFromCurrentSender(message: message) ? DCColors.messagePrimaryColor : DCColors.messageSecondaryColor
-	}
-
-
-
-	func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
-		return [.url, .date, .phoneNumber, .address]
-	}
-}
-*/
-
-// MARK: - MessagesLayoutDelegate
-
-extension ChatViewController: MessagesLayoutDelegate {
-	func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
-		if isTimeLabelVisible(at: indexPath) {
-			return 18
-		}
-		if !isPreviousMessageSameSender(at: indexPath) {
-			return 18
-		}
-		return 0
-	}
-}
-
-/*
-extension ChatViewController: MessagesLayoutDelegate {
-	func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
-		if isTimeLabelVisible(at: indexPath) {
-			return 18
-		}
-		return !isPreviousMessageSameSender(at: indexPath) ? 18 : 0
-	}
-
-	func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
-		if isInfoMessage(at: indexPath) {
-			return 0
-		}
-
-		if isFromCurrentSender(message: message) {
-			if !isGroupChat {
-				return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
-			} else {
-				return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
-			}
-		} else {
-			if !isGroupChat {
-				return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
-			} else {
-				return !isPreviousMessageSameSender(at: indexPath) ? (20 + outgoingAvatarOverlap) : 0
-			}
-		}
-	}
-
-	func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
-		if isInfoMessage(at: indexPath) {
-			return 0
-		}
-
-		if !isNextMessageSameSender(at: indexPath) {
-			return 16
-		}
-
-		if isFromCurrentSender(message: message) {
-			return 0
-		}
-		return 0
-	}
-
-	func heightForLocation(message _: MessageType, at _: IndexPath, with _: CGFloat, in _: MessagesCollectionView) -> CGFloat {
-		return 40
-	}
-
-	func footerViewSize(for _: MessageType, at _: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
-		return CGSize(width: messagesCollectionView.bounds.width, height: 10)
-	}
-*/
-
-extension ChatViewController {
-
-	@objc private func clipperButtonPressed() {
-		showClipperOptions()
-	}
-
-	private func photoButtonPressed() {
-		if UIImagePickerController.isSourceTypeAvailable(.camera) {
-			let cameraViewController = CameraViewController { [weak self] image, _ in
-				self?.dismiss(animated: true, completion: nil)
-
-				DispatchQueue.global().async {
-					if let pickedImage = image {
-						let width = Int32(exactly: pickedImage.size.width)!
-						let height = Int32(exactly: pickedImage.size.height)!
-						let path = Utils.saveImage(image: pickedImage)
-						let msg = dc_msg_new(mailboxPointer, DC_MSG_IMAGE)
-						dc_msg_set_file(msg, path, "image/jpeg")
-						dc_msg_set_dimension(msg, width, height)
-						dc_send_msg(mailboxPointer, UInt32(self!.chatId), msg)
-						// cleanup
-						dc_msg_unref(msg)
-					}
-				}
-			}
-
-			present(cameraViewController, animated: true, completion: nil)
-		} else {
-			let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
-			alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-				self.dismiss(animated: true, completion: nil)
-			}))
-			present(alert, animated: true, completion: nil)
-		}
-	}
-
-	private func showClipperOptions() {
-		let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
-
-		let photoAction = PhotoPickerAlertAction(title: "Photo", style: .default, handler: photoActionPressed(_:))
-		alert.addAction(photoAction)
-		alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
-		self.present(alert, animated: true, completion: nil)
-	}
-
-	private func photoActionPressed(_ action: UIAlertAction) {
-		photoButtonPressed()
-	}
-}
-
-
-// MARK: - MessageCellDelegate
-extension ChatViewController: MessageCellDelegate {
-	func didTapMessage(in cell: MessageCollectionViewCell) {
-		if let indexPath = messagesCollectionView.indexPath(for: cell) {
-			let message = messageList[indexPath.section]
-
-			if let url = message.fileURL {
-				previewController = PreviewController(urls: [url])
-				present(previewController!.qlController, animated: true)
-			}
-		}
-	}
-
-	func didTapAvatar(in cell: MessageCollectionViewCell) {
-		logger.info("Avatar tapped")
-		if let indexPath = super.messagesCollectionView.indexPath(for: cell) {
-			let contactId = messageList[indexPath.row].fromContact.id
-			coordinator?.showContactDetail(of: contactId)
-		}
-}
-
-	@objc(didTapCellTopLabelIn:) func didTapCellTopLabel(in _: MessageCollectionViewCell) {
-		logger.info("Top label tapped")
-	}
-
-	func didTapBottomLabel(in _: MessageCollectionViewCell) {
-		print("Bottom label tapped")
-	}
-}
-
-class PreviewController: QLPreviewControllerDataSource {
-	var urls: [URL]
-	var qlController: QLPreviewController
-
-	init(urls: [URL]) {
-		self.urls = urls
-		qlController = QLPreviewController()
-		qlController.dataSource = self
-	}
-
-	func numberOfPreviewItems(in _: QLPreviewController) -> Int {
-		return urls.count
-	}
-
-	func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
-		return urls[index] as QLPreviewItem
-	}
-}
-
-// MARK: - MessageLabelDelegate
-extension ChatViewController: MessageLabelDelegate {
-	func didSelectAddress(_ addressComponents: [String: String]) {
-		let mapAddress = Utils.formatAddressForQuery(address: addressComponents)
-		if let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
-			// Use query, to handle malformed addresses
-			if let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") {
-				UIApplication.shared.open(url as URL)
-			}
-		}
-	}
-
-	func didSelectDate(_ date: Date) {
-		let interval = date.timeIntervalSinceReferenceDate
-		if let url = NSURL(string: "calshow:\(interval)") {
-			UIApplication.shared.open(url as URL)
-		}
-	}
-
-	func didSelectPhoneNumber(_ phoneNumber: String) {
-		logger.info("phone open", phoneNumber)
-		if let escapedPhoneNumber = phoneNumber.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
-			if let url = NSURL(string: "tel:\(escapedPhoneNumber)") {
-				UIApplication.shared.open(url as URL)
-			}
-		}
-	}
-
-	func didSelectURL(_ url: URL) {
-		UIApplication.shared.open(url)
-	}
-}
-
-// MARK: - LocationMessageDisplayDelegate
-/*
-extension ChatViewController: LocationMessageDisplayDelegate {
-func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? {
-let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil)
-let pinImage = #imageLiteral(resourceName: "ic_block_36pt").withRenderingMode(.alwaysTemplate)
-annotationView.image = pinImage
-annotationView.centerOffset = CGPoint(x: 0, y: -pinImage.size.height / 2)
-return annotationView
-}
-func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)? {
-return { view in
-view.layer.transform = CATransform3DMakeScale(0, 0, 0)
-view.alpha = 0.0
-UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: [], animations: {
-view.layer.transform = CATransform3DIdentity
-view.alpha = 1.0
-}, completion: nil)
-}
-}
-}
-*/
-
-// MARK: - MessageInputBarDelegate
-extension ChatViewController: InputBarAccessoryViewDelegate {
-	func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
-		DispatchQueue.global().async {
-			dc_send_text_msg(mailboxPointer, UInt32(self.chatId), text)
-		}
-		inputBar.inputTextView.text = String()
-	}
+    @objc func messageInfo(_ sender: Any?) {
+        // Get the collectionView
+        if let collectionView = self.superview as? UICollectionView {
+            // Get indexPath
+            if let indexPath = collectionView.indexPath(for: self) {
+                // Trigger action
+                collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageInfo(_:)), forItemAt: indexPath, withSender: sender)
+            }
+        }
+    }
+
+    @objc func messageBlock(_ sender: Any?) {
+        // Get the collectionView
+        if let collectionView = self.superview as? UICollectionView {
+            // Get indexPath
+            if let indexPath = collectionView.indexPath(for: self) {
+                // Trigger action
+                collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageBlock(_:)), forItemAt: indexPath, withSender: sender)
+            }
+        }
+    }
+
+    @objc func messageDismiss(_ sender: Any?) {
+        // Get the collectionView
+        if let collectionView = self.superview as? UICollectionView {
+            // Get indexPath
+            if let indexPath = collectionView.indexPath(for: self) {
+                // Trigger action
+                collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageDismiss(_:)), forItemAt: indexPath, withSender: sender)
+            }
+        }
+    }
+
+    @objc func messageStartChat(_ sender: Any?) {
+        // Get the collectionView
+        if let collectionView = self.superview as? UICollectionView {
+            // Get indexPath
+            if let indexPath = collectionView.indexPath(for: self) {
+                // Trigger action
+                collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageStartChat(_:)), forItemAt: indexPath, withSender: sender)
+            }
+        }
+    }
 }
 
 /*
-extension ChatViewController: MessageInputBarDelegate {
-}
-*/
-
-// MARK: - MessageCollectionViewCell
-extension MessageCollectionViewCell {
-	@objc func messageInfo(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageInfo(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-
-	@objc func messageBlock(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageBlock(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-
-	@objc func messageDismiss(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageDismiss(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-
-	@objc func messageStartChat(_ sender: Any?) {
-		// Get the collectionView
-		if let collectionView = self.superview as? UICollectionView {
-			// Get indexPath
-			if let indexPath = collectionView.indexPath(for: self) {
-				// Trigger action
-				collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageStartChat(_:)), forItemAt: indexPath, withSender: sender)
-			}
-		}
-	}
-}
-
-*/
+ class ChatViewController: MessagesViewController {
+ weak var coordinator: ChatViewCoordinator?
+
+ let outgoingAvatarOverlap: CGFloat = 17.5
+ let loadCount = 30
+
+ private var isGroupChat: Bool {
+ 	return MRChat(id: chatId).chatType != .SINGLE
+ }
+
+ let chatId: Int
+ let refreshControl = UIRefreshControl()
+ var messageList: [MRMessage] = []
+
+ var msgChangedObserver: Any?
+ var incomingMsgObserver: Any?
+
+ lazy var navBarTap: UITapGestureRecognizer = {
+ 	UITapGestureRecognizer(target: self, action: #selector(chatProfilePressed))
+ }()
+
+ var disableWriting = false
+
+ var previewView: UIView?
+ var previewController: PreviewController?
+
+ override var inputAccessoryView: UIView? {
+ 	if disableWriting {
+ 		return nil
+ 	}
+ 	return messageInputBar
+ }
+
+ init(chatId: Int, title: String? = nil) {
+ 	self.chatId = chatId
+ 	super.init(nibName: nil, bundle: nil)
+ 	if let title = title {
+ 		updateTitleView(title: title, subtitle: nil)
+ 	}
+ 	hidesBottomBarWhenPushed = true
+ }
+
+ required init?(coder _: NSCoder) {
+ 	fatalError("init(coder:) has not been implemented")
+ }
+
+ override func viewDidLoad() {
+ 	messagesCollectionView.register(CustomCell.self)
+ 	super.viewDidLoad()
+ 	view.backgroundColor = DCColors.chatBackgroundColor
+ 	if !MRConfig.configured {
+ 		// TODO: display message about nothing being configured
+ 		return
+ 	}
+ 	configureMessageCollectionView()
+
+ 	if !disableWriting {
+ 		configureMessageInputBar()
+ 		messageInputBar.inputTextView.text = textDraft
+ 		messageInputBar.inputTextView.becomeFirstResponder()
+ 	}
+
+ 	loadFirstMessages()
+ }
+
+ override func viewWillAppear(_ animated: Bool) {
+ 	super.viewWillAppear(animated)
+
+ 	// this will be removed in viewWillDisappear
+ 	navigationController?.navigationBar.addGestureRecognizer(navBarTap)
+
+ 	let chat = MRChat(id: chatId)
+ 	updateTitleView(title: chat.name, subtitle: chat.subtitle)
+
+ 	if let image = chat.profileImage {
+ 		navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: .done, target: self, action: #selector(chatProfilePressed))
+ 	} else {
+ 		let initialsLabel = InitialsLabel(name: chat.name, color: chat.color, size: 28)
+ 		navigationItem.rightBarButtonItem = UIBarButtonItem(customView: initialsLabel)
+ 	}
+
+ 	configureMessageMenu()
+
+ 	if #available(iOS 11.0, *) {
+ 		if disableWriting {
+ 			navigationController?.navigationBar.prefersLargeTitles = true
+ 		}
+ 	}
+
+ 	let nc = NotificationCenter.default
+ 	msgChangedObserver = nc.addObserver(
+ 		forName: dcNotificationChanged,
+ 		object: nil,
+ 		queue: OperationQueue.main
+ 	) { notification in
+ 		if let ui = notification.userInfo {
+ 			if self.disableWriting {
+ 				// always refresh, as we can't check currently
+ 				self.refreshMessages()
+ 			} else if let id = ui["message_id"] as? Int {
+ 				if id > 0 {
+ 					self.updateMessage(id)
+ 				}
+ 			}
+ 		}
+ 	}
+
+ 	incomingMsgObserver = nc.addObserver(
+ 		forName: dcNotificationIncoming,
+ 		object: nil, queue: OperationQueue.main
+ 	) { notification in
+ 		if let ui = notification.userInfo {
+ 			if self.chatId == ui["chat_id"] as! Int {
+ 				let id = ui["message_id"] as! Int
+ 				if id > 0 {
+ 					self.insertMessage(MRMessage(id: id))
+ 				}
+ 			}
+ 		}
+ 	}
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ 	super.viewWillDisappear(animated)
+
+ 	// the navigationController will be used when chatDetail is pushed, so we have to remove that gestureRecognizer
+ 	navigationController?.navigationBar.removeGestureRecognizer(navBarTap)
+
+ 	let cnt = Int(dc_get_fresh_msg_cnt(mailboxPointer, UInt32(chatId)))
+ 	logger.info("updating count for chat \(cnt)")
+ 	UIApplication.shared.applicationIconBadgeNumber = cnt
+
+ 	if #available(iOS 11.0, *) {
+ 		if disableWriting {
+ 			navigationController?.navigationBar.prefersLargeTitles = false
+ 		}
+ 	}
+ }
+
+ override func viewDidDisappear(_ animated: Bool) {
+ 	super.viewDidDisappear(animated)
+
+ 	setTextDraft()
+ 	let nc = NotificationCenter.default
+ 	if let msgChangedObserver = self.msgChangedObserver {
+ 		nc.removeObserver(msgChangedObserver)
+ 	}
+ 	if let incomingMsgObserver = self.incomingMsgObserver {
+ 		nc.removeObserver(incomingMsgObserver)
+ 	}
+ }
+
+ @objc
+ private func loadMoreMessages() {
+ 	DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
+ 		DispatchQueue.main.async {
+ 			self.messageList = self.getMessageIds(self.loadCount, from: self.messageList.count) + self.messageList
+ 			self.messagesCollectionView.reloadDataAndKeepOffset()
+ 			self.refreshControl.endRefreshing()
+ 		}
+ 	}
+ }
+
+ @objc
+ private func refreshMessages() {
+ 	DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
+ 		DispatchQueue.main.async {
+ 			self.messageList = self.getMessageIds(self.messageList.count)
+ 			self.messagesCollectionView.reloadDataAndKeepOffset()
+ 			self.refreshControl.endRefreshing()
+ 			if self.isLastSectionVisible() {
+ 				self.messagesCollectionView.scrollToBottom(animated: true)
+ 			}
+ 		}
+ 	}
+ }
+
+ private func loadFirstMessages() {
+ 	DispatchQueue.global(qos: .userInitiated).async {
+ 		DispatchQueue.main.async {
+ 			self.messageList = self.getMessageIds(self.loadCount)
+ 			self.messagesCollectionView.reloadData()
+ 			self.refreshControl.endRefreshing()
+ 			self.messagesCollectionView.scrollToBottom(animated: false)
+ 		}
+ 	}
+ }
+
+ private var textDraft: String? {
+ 	// FIXME: need to free pointer
+ 	if let draft = dc_get_draft(mailboxPointer, UInt32(chatId)) {
+ 		if let text = dc_msg_get_text(draft) {
+ 			let s = String(validatingUTF8: text)!
+ 			return s
+ 		}
+ 		return nil
+ 	}
+ 	return nil
+ }
+
+ private func getMessageIds(_ count: Int, from: Int? = nil) -> [MRMessage] {
+ 	let cMessageIds = dc_get_chat_msgs(mailboxPointer, UInt32(chatId), 0, 0)
+
+ 	let ids: [Int]
+ 	if let from = from {
+ 		ids = Utils.copyAndFreeArrayWithOffset(inputArray: cMessageIds, len: count, skipEnd: from)
+ 	} else {
+ 		ids = Utils.copyAndFreeArrayWithLen(inputArray: cMessageIds, len: count)
+ 	}
+
+ 	let markIds: [UInt32] = ids.map { UInt32($0) }
+ 	dc_markseen_msgs(mailboxPointer, UnsafePointer(markIds), Int32(ids.count))
+
+ 	return ids.map {
+ 		MRMessage(id: $0)
+ 	}
+ }
+
+ private func setTextDraft() {
+ 	if let text = self.messageInputBar.inputTextView.text {
+ 		let draft = dc_msg_new(mailboxPointer, DC_MSG_TEXT)
+ 		dc_msg_set_text(draft, text.cString(using: .utf8))
+ 		dc_set_draft(mailboxPointer, UInt32(chatId), draft)
+
+ 		// cleanup
+ 		dc_msg_unref(draft)
+ 	}
+ }
+
+
+
+ private func configureMessageMenu() {
+ 	var menuItems: [UIMenuItem]
+
+ 	if disableWriting {
+ 		menuItems = [
+ 			UIMenuItem(title: "Start Chat", action: #selector(MessageCollectionViewCell.messageStartChat(_:))),
+ 			UIMenuItem(title: "Dismiss", action: #selector(MessageCollectionViewCell.messageDismiss(_:))),
+ 			UIMenuItem(title: "Block", action: #selector(MessageCollectionViewCell.messageBlock(_:))),
+ 		]
+ 	} else {
+ 		// Configures the UIMenu which is shown when selecting a message
+ 		menuItems = [
+ 			UIMenuItem(title: "Info", action: #selector(MessageCollectionViewCell.messageInfo(_:))),
+ 		]
+ 	}
+
+ 	UIMenuController.shared.menuItems = menuItems
+ }
+
+ private func configureMessageCollectionView() {
+ 	messagesCollectionView.messagesDataSource = self
+ 	messagesCollectionView.messageCellDelegate = self
+
+ 	scrollsToBottomOnKeyboardBeginsEditing = true // default false
+ 	maintainPositionOnKeyboardFrameChanged = true // default false
+ 	messagesCollectionView.addSubview(refreshControl)
+ 	refreshControl.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
+
+ 	let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
+ 	messagesCollectionView.messagesLayoutDelegate = self
+ 	messagesCollectionView.messagesDisplayDelegate = self
+
+
+ /*
+  	let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
+  	layout?.sectionInset = UIEdgeInsets(top: 1, left: 8, bottom: 1, right: 8)
+
+  	// Hide the outgoing avatar and adjust the label alignment to line up with the messages
+  	layout?.setMessageOutgoingAvatarSize(.zero)
+  	layout?.setMessageOutgoingMessageTopLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
+  	layout?.setMessageOutgoingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
+
+  	// Set outgoing avatar to overlap with the message bubble
+  	if isGroupChat {
+  		layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 18, bottom: outgoingAvatarOverlap, right: 0)))
+  		layout?.setMessageIncomingAvatarSize(CGSize(width: 30, height: 30))
+  		layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: -18, bottom: outgoingAvatarOverlap / 2, right: 18))
+  		layout?.setMessageIncomingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: -7, left: 38, bottom: 0, right: 0)))
+
+  	} else {
+  		layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 5, right: 0)))
+  		layout?.setMessageIncomingAvatarSize(CGSize.zero) // no batch displayed in singleChats
+  		layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: 0, bottom: outgoingAvatarOverlap / 2, right: 18))
+  		layout?.setMessageIncomingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: -7, left: 12, bottom: 0, right: 0)))
+  	}
+
+  	layout?.setMessageIncomingAccessoryViewSize(CGSize(width: 30, height: 30))
+  	layout?.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 8, right: 0))
+  	layout?.setMessageOutgoingAccessoryViewSize(CGSize(width: 30, height: 30))
+  	layout?.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 8))
+
+  	messagesCollectionView.messagesLayoutDelegate = self
+  	messagesCollectionView.messagesDisplayDelegate = self
+  */
+ }
+
+ private func configureMessageInputBar() {
+ 	messageInputBar.delegate = self
+ 	messageInputBar.inputTextView.tintColor = DCColors.primary
+
+ 	messageInputBar.isTranslucent = true
+ 	messageInputBar.separatorLine.isHidden = true
+ 	messageInputBar.inputTextView.tintColor = DCColors.primary
+
+ 	scrollsToBottomOnKeyboardBeginsEditing = true
+
+ 	messageInputBar.inputTextView.backgroundColor = UIColor(red: 245 / 255, green: 245 / 255, blue: 245 / 255, alpha: 1)
+ 	messageInputBar.inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
+ 	messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 38)
+ 	messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 38)
+ 	messageInputBar.inputTextView.layer.borderColor = UIColor(red: 200 / 255, green: 200 / 255, blue: 200 / 255, alpha: 1).cgColor
+ 	messageInputBar.inputTextView.layer.borderWidth = 1.0
+ 	messageInputBar.inputTextView.layer.cornerRadius = 16.0
+ 	messageInputBar.inputTextView.layer.masksToBounds = true
+ 	messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
+ 	configureInputBarItems()
+ }
+
+ private func configureInputBarItems() {
+
+ 	messageInputBar.setLeftStackViewWidthConstant(to: 30, animated: false)
+ 	messageInputBar.setRightStackViewWidthConstant(to: 30, animated: false)
+
+
+ 	let sendButtonImage = UIImage(named: "paper_plane")?.withRenderingMode(.alwaysTemplate)
+ 	messageInputBar.sendButton.image = sendButtonImage
+ 	messageInputBar.sendButton.title = nil
+ 	messageInputBar.sendButton.tintColor = UIColor(white: 1, alpha: 1)
+ 	messageInputBar.sendButton.layer.cornerRadius = 15
+ 	messageInputBar.middleContentViewPadding = UIEdgeInsets(top: 0, left: 5, bottom: 0, right: 10)	// this adds a padding between textinputfield and send button
+ 	messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
+ 	messageInputBar.sendButton.setSize(CGSize(width: 30, height: 30), animated: false)
+
+ 	let leftItems = [
+ 		InputBarButtonItem()
+ 			.configure {
+ 				$0.spacing = .fixed(0)
+ 				let clipperIcon = #imageLiteral(resourceName: "ic_attach_file_36pt").withRenderingMode(.alwaysTemplate)
+ 				$0.image = clipperIcon
+ 				$0.tintColor = UIColor(white: 0.8, alpha: 1)
+ 				$0.setSize(CGSize(width: 30, height: 30), animated: false)
+ 			}.onSelected {
+ 				$0.tintColor = DCColors.primary
+ 			}.onDeselected {
+ 				$0.tintColor = UIColor(white: 0.8, alpha: 1)
+ 			}.onTouchUpInside { _ in
+ 				self.clipperButtonPressed()
+ 		}
+ /*
+  		InputBarButtonItem()
+  		.configure {
+  		$0.spacing = .fixed(0)
+  		$0.image = UIImage(named: "camera")?.withRenderingMode(.alwaysTemplate)
+  		$0.setSize(CGSize(width: 36, height: 36), animated: false)
+  		$0.tintColor = UIColor(white: 0.8, alpha: 1)
+  		}.onSelected {
+  		$0.tintColor = DCColors.primary
+  		}.onDeselected {
+  		$0.tintColor = UIColor(white: 0.8, alpha: 1)
+  		}.onTouchUpInside { _ in
+  		self.didPressPhotoButton()
+  		},
+  */
+ 	]
+
+ 	messageInputBar.setStackViewItems(leftItems, forStack: .left, animated: false)
+
+ 	// This just adds some more flare
+ 	messageInputBar.sendButton
+ 		.onEnabled { item in
+ 			UIView.animate(withDuration: 0.3, animations: {
+ 				item.backgroundColor = DCColors.primary
+ 			})
+ 		}.onDisabled { item in
+ 			UIView.animate(withDuration: 0.3, animations: {
+ 				item.backgroundColor = UIColor(white: 0.9, alpha: 1)
+ 			})
+ 	}
+ }
+
+
+ @objc private func chatProfilePressed() {
+ 	coordinator?.showChatDetail(chatId: chatId)
+ }
+
+ // MARK: - UICollectionViewDataSource
+ public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+ 	guard let messagesCollectionView = collectionView as? MessagesCollectionView else {
+ 		fatalError("notMessagesCollectionView")
+ 	}
+
+ 	guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
+ 		fatalError("nilMessagesDataSource")
+ 	}
+
+ 	let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+ 	switch message.kind {
+ 	case .text, .attributedText, .emoji:
+ 		let cell = messagesCollectionView.dequeueReusableCell(TextMessageCell.self, for: indexPath)
+ 		cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+ 		return cell
+ 	case .photo, .video:
+ 		let cell = messagesCollectionView.dequeueReusableCell(MediaMessageCell.self, for: indexPath)
+ 		cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+ 		return cell
+ 	case .location:
+ 		let cell = messagesCollectionView.dequeueReusableCell(LocationMessageCell.self, for: indexPath)
+ 		cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+ 		return cell
+ 	case .contact:
+ 		let cell = messagesCollectionView.dequeueReusableCell(ContactMessageCell.self, for: indexPath)
+ 		cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+ 		return cell
+ 	case .custom:
+ 		let cell = messagesCollectionView.dequeueReusableCell(CustomCell.self, for: indexPath)
+ 		cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+ 		return cell
+ 	case .audio(_):
+ 		let cell = messagesCollectionView.dequeueReusableCell(AudioMessageCell.self, for: indexPath)
+ 		cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+ 		return cell
+ 	}
+ }
+
+ override func collectionView(_ collectionView: UICollectionView, canPerformAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
+ 	if action == NSSelectorFromString("messageInfo:") ||
+ 		action == NSSelectorFromString("messageBlock:") ||
+ 		action == NSSelectorFromString("messageDismiss:") ||
+ 		action == NSSelectorFromString("messageStartChat:") {
+ 		return true
+ 	} else {
+ 		return super.collectionView(collectionView, canPerformAction: action, forItemAt: indexPath, withSender: sender)
+ 	}
+ }
+
+ override func collectionView(_ collectionView: UICollectionView, performAction action: Selector, forItemAt indexPath: IndexPath, withSender sender: Any?) {
+ 	switch action {
+ 	case NSSelectorFromString("messageInfo:"):
+ 		let msg = messageList[indexPath.section]
+ 		logger.info("message: View info \(msg.messageId)")
+
+ 		let msgViewController = MessageInfoViewController(message: msg)
+ 		if let ctrl = navigationController {
+ 			ctrl.pushViewController(msgViewController, animated: true)
+ 		}
+ 	case NSSelectorFromString("messageStartChat:"):
+ 		let msg = messageList[indexPath.section]
+ 		logger.info("message: Start Chat \(msg.messageId)")
+ 		_ = msg.createChat()
+ 		// TODO: figure out how to properly show the chat after creation
+ 		refreshMessages()
+ 	case NSSelectorFromString("messageBlock:"):
+ 		let msg = messageList[indexPath.section]
+ 		logger.info("message: Block \(msg.messageId)")
+ 		msg.fromContact.block()
+
+ 		refreshMessages()
+ 	case NSSelectorFromString("messageDismiss:"):
+ 		let msg = messageList[indexPath.section]
+ 		logger.info("message: Dismiss \(msg.messageId)")
+ 		msg.fromContact.marknoticed()
+
+ 		refreshMessages()
+ 	default:
+ 		super.collectionView(collectionView, performAction: action, forItemAt: indexPath, withSender: sender)
+ 	}
+ }
+ }
+
+ // MARK: - MessagesDataSource
+ extension ChatViewController: MessagesDataSource {
+
+ func numberOfSections(in _: MessagesCollectionView) -> Int {
+ 	return messageList.count
+ }
+
+ func currentSender() -> SenderType {
+ 	let currentSender = Sender(id: "1", displayName: "Alice")
+ 	return currentSender
+ }
+
+ func messageForItem(at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageType {
+ 	return messageList[indexPath.section]
+ }
+
+ func avatar(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> Avatar {
+ 	let message = messageList[indexPath.section]
+ 	let contact = message.fromContact
+ 	return Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
+ }
+
+ func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+ 	if isInfoMessage(at: indexPath) {
+ 		return nil
+ 	}
+
+ 	if isTimeLabelVisible(at: indexPath) {
+ 		return NSAttributedString(
+ 			string: MessageKitDateFormatter.shared.string(from: message.sentDate),
+ 			attributes: [
+ 				NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10),
+ 				NSAttributedString.Key.foregroundColor: UIColor.darkGray,
+ 			]
+ 		)
+ 	}
+
+ 	return nil
+ }
+
+ func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+
+ 	if !isGroupChat {
+ 		return nil
+ 	}
+
+ 	if !isPreviousMessageSameSender(at: indexPath) {
+ 		let name = message.sender.displayName
+ 		let m = messageList[indexPath.section]
+ 		return NSAttributedString(string: name, attributes: [
+ 			.font: UIFont.systemFont(ofSize: 14),
+ 			.foregroundColor: m.fromContact.color,
+ 			])
+ 	}
+ 	return nil
+ }
+
+ func isTimeLabelVisible(at indexPath: IndexPath) -> Bool {
+ 	guard indexPath.section + 1 < messageList.count else { return false }
+
+ 	let messageA = messageList[indexPath.section]
+ 	let messageB = messageList[indexPath.section + 1]
+
+ 	if messageA.fromContactId == messageB.fromContactId {
+ 		return false
+ 	}
+
+ 	let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
+ 	let dateA = messageA.sentDate
+ 	let dateB = messageB.sentDate
+
+ 	let dayA = (calendar?.component(.day, from: dateA))
+ 	let dayB = (calendar?.component(.day, from: dateB))
+
+ 	return dayA != dayB
+ }
+
+ func isPreviousMessageSameSender(at indexPath: IndexPath) -> Bool {
+ 	guard indexPath.section - 1 >= 0 else { return false }
+ 	let messageA = messageList[indexPath.section - 1]
+ 	let messageB = messageList[indexPath.section]
+
+ 	if messageA.isInfo {
+ 		return false
+ 	}
+
+ 	return messageA.fromContactId == messageB.fromContactId
+ }
+
+ func isInfoMessage(at indexPath: IndexPath) -> Bool {
+ 	return messageList[indexPath.section].isInfo
+ }
+
+ func isNextMessageSameSender(at indexPath: IndexPath) -> Bool {
+ 	guard indexPath.section + 1 < messageList.count else { return false }
+ 	let messageA = messageList[indexPath.section]
+ 	let messageB = messageList[indexPath.section + 1]
+
+ 	if messageA.isInfo {
+ 		return false
+ 	}
+
+ 	return messageA.fromContactId == messageB.fromContactId
+ }
+
+ func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+ 	guard indexPath.section < messageList.count else { return nil }
+ 	let m = messageList[indexPath.section]
+
+ 	if m.isInfo || isNextMessageSameSender(at: indexPath) {
+ 		return nil
+ 	}
+
+ 	let timestampAttributes: [NSAttributedString.Key: Any] = [
+ 		.font: UIFont.systemFont(ofSize: 12),
+ 		.foregroundColor: UIColor.lightGray,
+ 	]
+
+ 	if isFromCurrentSender(message: message) {
+ 		let text = NSMutableAttributedString()
+ 		text.append(NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes))
+
+ 		text.append(NSAttributedString(
+ 			string: " - " + m.stateDescription(),
+ 			attributes: [
+ 				.font: UIFont.systemFont(ofSize: 12),
+ 				.foregroundColor: UIColor.darkText,
+ 			]
+ 		))
+
+ 		return text
+ 	}
+
+ 	return NSAttributedString(string: m.formattedSentDate(), attributes: timestampAttributes)
+ }
+
+ func updateMessage(_ messageId: Int) {
+ 	if let index = messageList.firstIndex(where: { $0.id == messageId }) {
+ 		dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(messageId)]), 1)
+
+ 		messageList[index] = MRMessage(id: messageId)
+ 		// Reload section to update header/footer labels
+ 		messagesCollectionView.performBatchUpdates({
+ 			messagesCollectionView.reloadSections([index])
+ 			if index > 0 {
+ 				messagesCollectionView.reloadSections([index - 1])
+ 			}
+ 			if index < messageList.count - 1 {
+ 				messagesCollectionView.reloadSections([index + 1])
+ 			}
+ 		}, completion: { [weak self] _ in
+ 			if self?.isLastSectionVisible() == true {
+ 				self?.messagesCollectionView.scrollToBottom(animated: true)
+ 			}
+ 		})
+ 	} else {
+ 		let msg = MRMessage(id: messageId)
+ 		if msg.chatId == chatId {
+ 			insertMessage(msg)
+ 		}
+ 	}
+ }
+
+ func insertMessage(_ message: MRMessage) {
+ 	dc_markseen_msgs(mailboxPointer, UnsafePointer([UInt32(message.id)]), 1)
+ 	messageList.append(message)
+ 	// Reload last section to update header/footer labels and insert a new one
+ 	messagesCollectionView.performBatchUpdates({
+ 		messagesCollectionView.insertSections([messageList.count - 1])
+ 		if messageList.count >= 2 {
+ 			messagesCollectionView.reloadSections([messageList.count - 2])
+ 		}
+ 	}, completion: { [weak self] _ in
+ 		if self?.isLastSectionVisible() == true {
+ 			self?.messagesCollectionView.scrollToBottom(animated: true)
+ 		}
+ 	})
+ }
+
+ func isLastSectionVisible() -> Bool {
+ 	guard !messageList.isEmpty else { return false }
+
+ 	let lastIndexPath = IndexPath(item: 0, section: messageList.count - 1)
+ 	return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
+ }
+ }
+
+ // MARK: - MessagesDisplayDelegate
+
+ extension ChatViewController: MessagesDisplayDelegate {
+
+ func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) {
+ 	let message = messageList[indexPath.section]
+ 	let contact = message.fromContact
+ 	let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
+ 	avatarView.set(avatar: avatar)
+ 	avatarView.isHidden = isNextMessageSameSender(at: indexPath) || message.isInfo
+ 	avatarView.backgroundColor = contact.color
+ }
+
+
+ func messageStyle(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> MessageStyle {
+ 	if isInfoMessage(at: indexPath) {
+ 		return .custom { view in
+ 			view.style = .none
+ 			view.backgroundColor = UIColor(alpha: 10, red: 0, green: 0, blue: 0)
+ 			let radius: CGFloat = 16
+ 			let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: UIRectCorner.allCorners, cornerRadii: CGSize(width: radius, height: radius))
+ 			let mask = CAShapeLayer()
+ 			mask.path = path.cgPath
+ 			view.layer.mask = mask
+ 			view.center.x = self.view.center.x
+ 		}
+ 	}
+
+ 	var corners: UIRectCorner = []
+
+ 	if isFromCurrentSender(message: message) {
+ 		corners.formUnion(.topLeft)
+ 		corners.formUnion(.bottomLeft)
+ 		if !isPreviousMessageSameSender(at: indexPath) {
+ 			corners.formUnion(.topRight)
+ 		}
+ 		if !isNextMessageSameSender(at: indexPath) {
+ 			corners.formUnion(.bottomRight)
+ 		}
+ 	} else {
+ 		corners.formUnion(.topRight)
+ 		corners.formUnion(.bottomRight)
+ 		if !isPreviousMessageSameSender(at: indexPath) {
+ 			corners.formUnion(.topLeft)
+ 		}
+ 		if !isNextMessageSameSender(at: indexPath) {
+ 			corners.formUnion(.bottomLeft)
+ 		}
+ 	}
+
+ 	return .custom { view in
+ 		let radius: CGFloat = 16
+ 		let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+ 		let mask = CAShapeLayer()
+ 		mask.path = path.cgPath
+ 		view.layer.mask = mask
+ 	}
+ }
+ }
+
+
+ /*
+  extension ChatViewController: MessagesDisplayDelegate {
+  // MARK: - Text Messages
+  func textColor(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
+  	return .darkText
+  }
+
+  // MARK: - All Messages
+  func backgroundColor(for message: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> UIColor {
+  	return isFromCurrentSender(message: message) ? DCColors.messagePrimaryColor : DCColors.messageSecondaryColor
+  }
+
+
+
+  func enabledDetectors(for _: MessageType, at _: IndexPath, in _: MessagesCollectionView) -> [DetectorType] {
+  	return [.url, .date, .phoneNumber, .address]
+  }
+  }
+  */
+
+ // MARK: - MessagesLayoutDelegate
+
+ extension ChatViewController: MessagesLayoutDelegate {
+ func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+ 	if isTimeLabelVisible(at: indexPath) {
+ 		return 18
+ 	}
+ 	if !isPreviousMessageSameSender(at: indexPath) {
+ 		return 18
+ 	}
+ 	return 0
+ }
+ }
+
+ /*
+  extension ChatViewController: MessagesLayoutDelegate {
+  func cellTopLabelHeight(for _: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+  	if isTimeLabelVisible(at: indexPath) {
+  		return 18
+  	}
+  	return !isPreviousMessageSameSender(at: indexPath) ? 18 : 0
+  }
+
+  func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+  	if isInfoMessage(at: indexPath) {
+  		return 0
+  	}
+
+  	if isFromCurrentSender(message: message) {
+  		if !isGroupChat {
+  			return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
+  		} else {
+  			return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
+  		}
+  	} else {
+  		if !isGroupChat {
+  			return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
+  		} else {
+  			return !isPreviousMessageSameSender(at: indexPath) ? (20 + outgoingAvatarOverlap) : 0
+  		}
+  	}
+  }
+
+  func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in _: MessagesCollectionView) -> CGFloat {
+  	if isInfoMessage(at: indexPath) {
+  		return 0
+  	}
+
+  	if !isNextMessageSameSender(at: indexPath) {
+  		return 16
+  	}
+
+  	if isFromCurrentSender(message: message) {
+  		return 0
+  	}
+  	return 0
+  }
+
+  func heightForLocation(message _: MessageType, at _: IndexPath, with _: CGFloat, in _: MessagesCollectionView) -> CGFloat {
+  	return 40
+  }
+
+  func footerViewSize(for _: MessageType, at _: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
+  	return CGSize(width: messagesCollectionView.bounds.width, height: 10)
+  }
+  */
+
+ extension ChatViewController {
+
+ @objc private func clipperButtonPressed() {
+ 	showClipperOptions()
+ }
+
+ private func photoButtonPressed() {
+ 	if UIImagePickerController.isSourceTypeAvailable(.camera) {
+ 		let cameraViewController = CameraViewController { [weak self] image, _ in
+ 			self?.dismiss(animated: true, completion: nil)
+
+ 			DispatchQueue.global().async {
+ 				if let pickedImage = image {
+ 					let width = Int32(exactly: pickedImage.size.width)!
+ 					let height = Int32(exactly: pickedImage.size.height)!
+ 					let path = Utils.saveImage(image: pickedImage)
+ 					let msg = dc_msg_new(mailboxPointer, DC_MSG_IMAGE)
+ 					dc_msg_set_file(msg, path, "image/jpeg")
+ 					dc_msg_set_dimension(msg, width, height)
+ 					dc_send_msg(mailboxPointer, UInt32(self!.chatId), msg)
+ 					// cleanup
+ 					dc_msg_unref(msg)
+ 				}
+ 			}
+ 		}
+
+ 		present(cameraViewController, animated: true, completion: nil)
+ 	} else {
+ 		let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
+ 		alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+ 			self.dismiss(animated: true, completion: nil)
+ 		}))
+ 		present(alert, animated: true, completion: nil)
+ 	}
+ }
+
+ private func showClipperOptions() {
+ 	let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
+
+ 	let photoAction = PhotoPickerAlertAction(title: "Photo", style: .default, handler: photoActionPressed(_:))
+ 	alert.addAction(photoAction)
+ 	alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
+ 	self.present(alert, animated: true, completion: nil)
+ }
+
+ private func photoActionPressed(_ action: UIAlertAction) {
+ 	photoButtonPressed()
+ }
+ }
+
+
+ // MARK: - MessageCellDelegate
+ extension ChatViewController: MessageCellDelegate {
+ func didTapMessage(in cell: MessageCollectionViewCell) {
+ 	if let indexPath = messagesCollectionView.indexPath(for: cell) {
+ 		let message = messageList[indexPath.section]
+
+ 		if let url = message.fileURL {
+ 			previewController = PreviewController(urls: [url])
+ 			present(previewController!.qlController, animated: true)
+ 		}
+ 	}
+ }
+
+ func didTapAvatar(in cell: MessageCollectionViewCell) {
+ 	logger.info("Avatar tapped")
+ 	if let indexPath = super.messagesCollectionView.indexPath(for: cell) {
+ 		let contactId = messageList[indexPath.row].fromContact.id
+ 		coordinator?.showContactDetail(of: contactId)
+ 	}
+ }
+
+ @objc(didTapCellTopLabelIn:) func didTapCellTopLabel(in _: MessageCollectionViewCell) {
+ 	logger.info("Top label tapped")
+ }
+
+ func didTapBottomLabel(in _: MessageCollectionViewCell) {
+ 	print("Bottom label tapped")
+ }
+ }
+
+ class PreviewController: QLPreviewControllerDataSource {
+ var urls: [URL]
+ var qlController: QLPreviewController
+
+ init(urls: [URL]) {
+ 	self.urls = urls
+ 	qlController = QLPreviewController()
+ 	qlController.dataSource = self
+ }
+
+ func numberOfPreviewItems(in _: QLPreviewController) -> Int {
+ 	return urls.count
+ }
+
+ func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
+ 	return urls[index] as QLPreviewItem
+ }
+ }
+
+ // MARK: - MessageLabelDelegate
+ extension ChatViewController: MessageLabelDelegate {
+ func didSelectAddress(_ addressComponents: [String: String]) {
+ 	let mapAddress = Utils.formatAddressForQuery(address: addressComponents)
+ 	if let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+ 		// Use query, to handle malformed addresses
+ 		if let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") {
+ 			UIApplication.shared.open(url as URL)
+ 		}
+ 	}
+ }
+
+ func didSelectDate(_ date: Date) {
+ 	let interval = date.timeIntervalSinceReferenceDate
+ 	if let url = NSURL(string: "calshow:\(interval)") {
+ 		UIApplication.shared.open(url as URL)
+ 	}
+ }
+
+ func didSelectPhoneNumber(_ phoneNumber: String) {
+ 	logger.info("phone open", phoneNumber)
+ 	if let escapedPhoneNumber = phoneNumber.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+ 		if let url = NSURL(string: "tel:\(escapedPhoneNumber)") {
+ 			UIApplication.shared.open(url as URL)
+ 		}
+ 	}
+ }
+
+ func didSelectURL(_ url: URL) {
+ 	UIApplication.shared.open(url)
+ }
+ }
+
+ // MARK: - LocationMessageDisplayDelegate
+ /*
+  extension ChatViewController: LocationMessageDisplayDelegate {
+  func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? {
+  let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil)
+  let pinImage = #imageLiteral(resourceName: "ic_block_36pt").withRenderingMode(.alwaysTemplate)
+  annotationView.image = pinImage
+  annotationView.centerOffset = CGPoint(x: 0, y: -pinImage.size.height / 2)
+  return annotationView
+  }
+  func animationBlockForLocation(message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> ((UIImageView) -> Void)? {
+  return { view in
+  view.layer.transform = CATransform3DMakeScale(0, 0, 0)
+  view.alpha = 0.0
+  UIView.animate(withDuration: 0.6, delay: 0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0, options: [], animations: {
+  view.layer.transform = CATransform3DIdentity
+  view.alpha = 1.0
+  }, completion: nil)
+  }
+  }
+  }
+  */
+
+ // MARK: - MessageInputBarDelegate
+ extension ChatViewController: InputBarAccessoryViewDelegate {
+ func inputBar(_ inputBar: InputBarAccessoryView, didPressSendButtonWith text: String) {
+ 	DispatchQueue.global().async {
+ 		dc_send_text_msg(mailboxPointer, UInt32(self.chatId), text)
+ 	}
+ 	inputBar.inputTextView.text = String()
+ }
+ }
+
+ /*
+  extension ChatViewController: MessageInputBarDelegate {
+  }
+  */
+
+ // MARK: - MessageCollectionViewCell
+ extension MessageCollectionViewCell {
+ @objc func messageInfo(_ sender: Any?) {
+ 	// Get the collectionView
+ 	if let collectionView = self.superview as? UICollectionView {
+ 		// Get indexPath
+ 		if let indexPath = collectionView.indexPath(for: self) {
+ 			// Trigger action
+ 			collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageInfo(_:)), forItemAt: indexPath, withSender: sender)
+ 		}
+ 	}
+ }
+
+ @objc func messageBlock(_ sender: Any?) {
+ 	// Get the collectionView
+ 	if let collectionView = self.superview as? UICollectionView {
+ 		// Get indexPath
+ 		if let indexPath = collectionView.indexPath(for: self) {
+ 			// Trigger action
+ 			collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageBlock(_:)), forItemAt: indexPath, withSender: sender)
+ 		}
+ 	}
+ }
+
+ @objc func messageDismiss(_ sender: Any?) {
+ 	// Get the collectionView
+ 	if let collectionView = self.superview as? UICollectionView {
+ 		// Get indexPath
+ 		if let indexPath = collectionView.indexPath(for: self) {
+ 			// Trigger action
+ 			collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageDismiss(_:)), forItemAt: indexPath, withSender: sender)
+ 		}
+ 	}
+ }
+
+ @objc func messageStartChat(_ sender: Any?) {
+ 	// Get the collectionView
+ 	if let collectionView = self.superview as? UICollectionView {
+ 		// Get indexPath
+ 		if let indexPath = collectionView.indexPath(for: self) {
+ 			// Trigger action
+ 			collectionView.delegate?.collectionView?(collectionView, performAction: #selector(MessageCollectionViewCell.messageStartChat(_:)), forItemAt: indexPath, withSender: sender)
+ 		}
+ 	}
+ }
+ }
+
+ */

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

@@ -2,149 +2,149 @@ import UIKit
 
 // this is also used as ChatDetail for SingleChats
 class ContactDetailViewController: UITableViewController {
-	weak var coordinator: ContactDetailCoordinatorProtocol?
-	var showChatCell: Bool = false // if this is set to true it will show a "goToChat-cell"
-
-	private enum CellIdentifiers: String {
-		case notification = "notificationCell"
-		case block = "blockContactCell"
-		case chat = "chatCell"
-	}
-
-	private let contactId: Int
-
-	private var contact: MRContact {
-		return MRContact(id: contactId)
-	}
-
-	private var notificationsCell: UITableViewCell = {
-		let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
-		cell.textLabel?.text = "Notifications"
-		cell.accessibilityIdentifier = CellIdentifiers.notification.rawValue
-		cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
-		cell.selectionStyle = .none
-		// TODO: add current notification status
-		return cell
-	}()
-
-	private lazy var chatCell: ActionCell = {
-		let cell = ActionCell()
-		cell.accessibilityIdentifier = CellIdentifiers.chat.rawValue
-		cell.actionColor = SystemColor.blue.uiColor
-		cell.actionTitle = "Chat with \(contact.name)"
-		cell.selectionStyle = .none
-		return cell
-	}()
-
-	private lazy var blockContactCell: ActionCell = {
-		let cell = ActionCell()
-		cell.accessibilityIdentifier = CellIdentifiers.block.rawValue
-		cell.actionTitle = contact.isBlocked ? "Unblock Contact" : "Block Contact"
-		cell.actionColor = contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
-		cell.selectionStyle = .none
-		return cell
-	}()
-
-	init(contactId: Int) {
-		self.contactId = contactId
-		super.init(style: .grouped)
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(editButtonPressed))
-		self.title = "Contact Info"
-	}
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-		tableView.reloadData()
-	}
-
-	override func numberOfSections(in tableView: UITableView) -> Int {
-		return 2
-	}
-
-	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-		if section == 0 {
-			return showChatCell ? 2 : 1
-		} else if section == 1 {
-			return 1
-		}
-		return 0
-	}
-
-	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-		let section = indexPath.section
-		let row = indexPath.row
-
-		if section == 0 {
-			if row == 0 {
-				return notificationsCell
-			} else {
-				return chatCell
-			}
-		} else {
-			return blockContactCell
-		}
-	}
-
-	override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-		guard let cell = tableView.cellForRow(at: indexPath) else {
-			return
-		}
-
-		if let identifier = CellIdentifiers(rawValue: cell.accessibilityIdentifier ?? "") {
-			switch identifier {
-			case .block:
-				toggleBlockContact()
-			case .chat:
-				let chatId = Int(dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId)))
-				coordinator?.showChat(chatId: chatId)
-			case .notification:
-				showNotificationSetup()
-			}
-		}
-	}
-
-	override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
-		if section == 0 {
-			let header = ContactDetailHeader()
-			header.updateDetails(title: contact.name, subtitle: contact.email)
-			if let img = contact.profileImage {
-				header.setImage(img)
-			} else {
-				header.setBackupImage(name: contact.name, color: contact.color)
-			}
-			header.setVerified(isVerified: contact.isVerified)
-			return header
-		}
-		return nil
-	}
-
-	private func toggleBlockContact() {
-		contact.isBlocked ? contact.unblock() : contact.block()
-		updateBlockContactCell()
-	}
-
-	private func updateBlockContactCell() {
-		blockContactCell.actionTitle = contact.isBlocked ? "Unblock Contact" : "Block Contact"
-		blockContactCell.actionColor = contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
-	}
-
-	private func showNotificationSetup() {
-		let notificationSetupAlert = UIAlertController(title: "Notifications Setup is not implemented yet", message: "But you get an idea where this is going", preferredStyle: .actionSheet)
-		let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
-		notificationSetupAlert.addAction(cancelAction)
-		present(notificationSetupAlert, animated: true, completion: nil)
-	}
-
-	@objc private func editButtonPressed() {
-		coordinator?.showEditContact(contactId: contactId)
-	}
+    weak var coordinator: ContactDetailCoordinatorProtocol?
+    var showChatCell: Bool = false // if this is set to true it will show a "goToChat-cell"
+
+    private enum CellIdentifiers: String {
+        case notification = "notificationCell"
+        case block = "blockContactCell"
+        case chat = "chatCell"
+    }
+
+    private let contactId: Int
+
+    private var contact: MRContact {
+        return MRContact(id: contactId)
+    }
+
+    private var notificationsCell: UITableViewCell = {
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = "Notifications"
+        cell.accessibilityIdentifier = CellIdentifiers.notification.rawValue
+        cell.accessoryType = UITableViewCell.AccessoryType.disclosureIndicator
+        cell.selectionStyle = .none
+        // TODO: add current notification status
+        return cell
+    }()
+
+    private lazy var chatCell: ActionCell = {
+        let cell = ActionCell()
+        cell.accessibilityIdentifier = CellIdentifiers.chat.rawValue
+        cell.actionColor = SystemColor.blue.uiColor
+        cell.actionTitle = "Chat with \(contact.name)"
+        cell.selectionStyle = .none
+        return cell
+    }()
+
+    private lazy var blockContactCell: ActionCell = {
+        let cell = ActionCell()
+        cell.accessibilityIdentifier = CellIdentifiers.block.rawValue
+        cell.actionTitle = contact.isBlocked ? "Unblock Contact" : "Block Contact"
+        cell.actionColor = contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
+        cell.selectionStyle = .none
+        return cell
+    }()
+
+    init(contactId: Int) {
+        self.contactId = contactId
+        super.init(style: .grouped)
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(editButtonPressed))
+        self.title = "Contact Info"
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        tableView.reloadData()
+    }
+
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 2
+    }
+
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if section == 0 {
+            return showChatCell ? 2 : 1
+        } else if section == 1 {
+            return 1
+        }
+        return 0
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let section = indexPath.section
+        let row = indexPath.row
+
+        if section == 0 {
+            if row == 0 {
+                return notificationsCell
+            } else {
+                return chatCell
+            }
+        } else {
+            return blockContactCell
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        guard let cell = tableView.cellForRow(at: indexPath) else {
+            return
+        }
+
+        if let identifier = CellIdentifiers(rawValue: cell.accessibilityIdentifier ?? "") {
+            switch identifier {
+            case .block:
+                toggleBlockContact()
+            case .chat:
+                let chatId = Int(dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId)))
+                coordinator?.showChat(chatId: chatId)
+            case .notification:
+                showNotificationSetup()
+            }
+        }
+    }
+
+    override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if section == 0 {
+            let header = ContactDetailHeader()
+            header.updateDetails(title: contact.name, subtitle: contact.email)
+            if let img = contact.profileImage {
+                header.setImage(img)
+            } else {
+                header.setBackupImage(name: contact.name, color: contact.color)
+            }
+            header.setVerified(isVerified: contact.isVerified)
+            return header
+        }
+        return nil
+    }
+
+    private func toggleBlockContact() {
+        contact.isBlocked ? contact.unblock() : contact.block()
+        updateBlockContactCell()
+    }
+
+    private func updateBlockContactCell() {
+        blockContactCell.actionTitle = contact.isBlocked ? "Unblock Contact" : "Block Contact"
+        blockContactCell.actionColor = contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
+    }
+
+    private func showNotificationSetup() {
+        let notificationSetupAlert = UIAlertController(title: "Notifications Setup is not implemented yet", message: "But you get an idea where this is going", preferredStyle: .actionSheet)
+        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
+        notificationSetupAlert.addAction(cancelAction)
+        present(notificationSetupAlert, animated: true, completion: nil)
+    }
+
+    @objc private func editButtonPressed() {
+        coordinator?.showEditContact(contactId: contactId)
+    }
 }
 

+ 235 - 235
deltachat-ios/Controller/ContactListController.swift

@@ -2,245 +2,245 @@ import UIKit
 import Contacts
 
 class ContactListController: UITableViewController {
-	weak var coordinator: ContactListCoordinator?
-
-	private lazy var searchController: UISearchController = {
-		let searchController = UISearchController(searchResultsController: nil)
-		searchController.searchResultsUpdater = self
-		searchController.obscuresBackgroundDuringPresentation = false
-		searchController.searchBar.placeholder = "Search Contact"
-		return searchController
-	}()
-
-	var contactIds: [Int] = Utils.getContactIds()
-
-	// contactWithSearchResults.indexesToHightLight empty by default
-	var contacts: [ContactWithSearchResults] {
-		return contactIds.map { ContactWithSearchResults(contact: MRContact(id: $0), indexesToHighlight: []) }
-	}
-
-	// used when seachbar is active
-	var filteredContacts: [ContactWithSearchResults] = []
-
-	// searchBar active?
-	func isFiltering() -> Bool {
-		return searchController.isActive && !searchBarIsEmpty()
-	}
-
-	let contactCellReuseIdentifier = "ChatCell"
-
-	lazy var deviceContactHandler: DeviceContactsHandler = {
-		let handler = DeviceContactsHandler()
-		handler.contactListDelegate = self
-		return handler
-	}()
-
-	lazy var newContactButton: UIBarButtonItem = {
-		let button = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(newContactButtonPressed))
-		// UIBarButtonItem(image: #imageLiteral(resourceName: "ic_add").withRenderingMode(.alwaysTemplate), style: .plain, target: self, action: #selector(newContactButtonPressed))
-		return button
-	}()
-
-	var deviceContactAccessGranted: Bool = false {
-		didSet {
-			tableView.reloadData()
-		}
-	}
-
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		title = "Contacts"
-		navigationController?.navigationBar.prefersLargeTitles = true
-		navigationItem.searchController = searchController
-		tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
-		tableView.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
-
-		navigationItem.rightBarButtonItem = newContactButton
-	}
-
-	private func getContactIds() {
-		contactIds = Utils.getContactIds()
-		tableView.reloadData()
-	}
-
-	private func searchBarIsEmpty() -> Bool {
-		return searchController.searchBar.text?.isEmpty ?? true
-	}
-
-	private func filterContentForSearchText(_ searchText: String, scope _: String = "All") {
-		let contactsWithHighlights: [ContactWithSearchResults] = contacts.map { contact in
-			let indexes = contact.contact.contains(searchText: searchText)
-			return ContactWithSearchResults(contact: contact.contact, indexesToHighlight: indexes)
-		}
-
-		filteredContacts = contactsWithHighlights.filter { !$0.indexesToHighlight.isEmpty }
-		tableView.reloadData()
-	}
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-		if #available(iOS 11.0, *) {
-			navigationController?.navigationBar.prefersLargeTitles = true
-		}
-		deviceContactHandler.importDeviceContacts()
-		deviceContactAccessGranted = CNContactStore.authorizationStatus(for: .contacts) == .authorized
-		searchController.searchBar.text = nil
-		getContactIds()
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		super.viewWillDisappear(animated)
-		if #available(iOS 11.0, *) {
-			navigationController?.navigationBar.prefersLargeTitles = false
-		}
-	}
-
-	override func didReceiveMemoryWarning() {
-		super.didReceiveMemoryWarning()
-	}
-
-	override func numberOfSections(in _: UITableView) -> Int {
-		return deviceContactAccessGranted ? 1 : 2
-	}
-
-	override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-		if !deviceContactAccessGranted && section == 0 {
-			return 1
-		}
-		return isFiltering() ? filteredContacts.count : contactIds.count
-	}
-
-	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-		let section = indexPath.section
-
-		if !deviceContactAccessGranted && section == 0 {
-			let cell: ActionCell
-			if let c = tableView.dequeueReusableCell(withIdentifier: "actionCell") as? ActionCell {
-				cell = c
-			} else {
-				cell = ActionCell(style: .default, reuseIdentifier: "actionCell")
-			}
-			cell.actionTitle = "Import Device Contacts"
-			return cell
-		} else {
-
-			let cell: ContactCell
-			if let c = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier) as? ContactCell {
-				cell = c
-			} else {
-				cell = ContactCell(style: .subtitle, reuseIdentifier: contactCellReuseIdentifier)
-			}
-			let row = indexPath.row
-			let contactRow = row
-
-			if contactRow < contactIds.count {
-				let contact: ContactWithSearchResults = isFiltering() ? filteredContacts[contactRow] : contacts[contactRow]
-				updateContactCell(cell: cell, contactWithHighlight: contact)
-				cell.selectionStyle = .none
-
-				cell.setVerified(isVerified: contact.contact.isVerified)
-			}
-			return cell
-		}
-	}
-
-	override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-		if !deviceContactAccessGranted && indexPath.section == 0 {
-			showSettingsAlert()
-		} else {
-			let contact = contactByIndexPath(indexPath)
-			let contactId = contact.contact.id
-			let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
-
-			if searchController.isActive {
-				searchController.dismiss(animated: false) {
-					self.coordinator?.showChat(chatId: Int(chatId))
-				}
-			} else {
-				self.coordinator?.showChat(chatId: Int(chatId))
-			}
-		}
-	}
-
-	override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
-		let contactId = contactByIndexPath(indexPath).contact.id
-
-		// assigning swipe by delete to chats
-		let edit = UITableViewRowAction(style: .default, title: "Edit") {
-			[unowned self] _, indexPath in
-			if self.searchController.isActive {
-				self.searchController.dismiss(animated: false) {
-					self.coordinator?.showContactDetail(contactId: contactId)
-				}
-			} else {
-				self.coordinator?.showContactDetail(contactId: contactId)
-			}
-		}
-		edit.backgroundColor = DCColors.primary
-		return [edit]
-	}
-
-	@objc func newContactButtonPressed() {
-		coordinator?.showNewContactController()
-	}
-
-	private func updateContactCell(cell: ContactCell, contactWithHighlight: ContactWithSearchResults) {
-		let contact = contactWithHighlight.contact
-
-		if let nameHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .NAME }).first,
-			let emailHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .EMAIL }).first {
-			// gets here when contact is a result of current search -> highlights relevant indexes
-			let nameLabelFontSize = cell.nameLabel.font.pointSize
-			let emailLabelFontSize = cell.emailLabel.font.pointSize
-
-			cell.nameLabel.attributedText = contact.name.boldAt(indexes: nameHighlightedIndexes.indexes, fontSize: nameLabelFontSize)
-			cell.emailLabel.attributedText = contact.email.boldAt(indexes: emailHighlightedIndexes.indexes, fontSize: emailLabelFontSize)
-		} else {
-			cell.nameLabel.text = contact.name
-			cell.emailLabel.text = contact.email
-		}
-		cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-		cell.setColor(contact.color)
-	}
-
-	private func contactByIndexPath(_ indexPath: IndexPath) -> ContactWithSearchResults {
-		return isFiltering() ? filteredContacts[indexPath.row] : contacts[indexPath.row]
-	}
+    weak var coordinator: ContactListCoordinator?
+
+    private lazy var searchController: UISearchController = {
+        let searchController = UISearchController(searchResultsController: nil)
+        searchController.searchResultsUpdater = self
+        searchController.obscuresBackgroundDuringPresentation = false
+        searchController.searchBar.placeholder = "Search Contact"
+        return searchController
+    }()
+
+    var contactIds: [Int] = Utils.getContactIds()
+
+    // contactWithSearchResults.indexesToHightLight empty by default
+    var contacts: [ContactWithSearchResults] {
+        return contactIds.map { ContactWithSearchResults(contact: MRContact(id: $0), indexesToHighlight: []) }
+    }
+
+    // used when seachbar is active
+    var filteredContacts: [ContactWithSearchResults] = []
+
+    // searchBar active?
+    func isFiltering() -> Bool {
+        return searchController.isActive && !searchBarIsEmpty()
+    }
+
+    let contactCellReuseIdentifier = "ChatCell"
+
+    lazy var deviceContactHandler: DeviceContactsHandler = {
+        let handler = DeviceContactsHandler()
+        handler.contactListDelegate = self
+        return handler
+    }()
+
+    lazy var newContactButton: UIBarButtonItem = {
+        let button = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(newContactButtonPressed))
+        // UIBarButtonItem(image: #imageLiteral(resourceName: "ic_add").withRenderingMode(.alwaysTemplate), style: .plain, target: self, action: #selector(newContactButtonPressed))
+        return button
+    }()
+
+    var deviceContactAccessGranted: Bool = false {
+        didSet {
+            tableView.reloadData()
+        }
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Contacts"
+        navigationController?.navigationBar.prefersLargeTitles = true
+        navigationItem.searchController = searchController
+        tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
+        tableView.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
+
+        navigationItem.rightBarButtonItem = newContactButton
+    }
+
+    private func getContactIds() {
+        contactIds = Utils.getContactIds()
+        tableView.reloadData()
+    }
+
+    private func searchBarIsEmpty() -> Bool {
+        return searchController.searchBar.text?.isEmpty ?? true
+    }
+
+    private func filterContentForSearchText(_ searchText: String, scope _: String = "All") {
+        let contactsWithHighlights: [ContactWithSearchResults] = contacts.map { contact in
+            let indexes = contact.contact.contains(searchText: searchText)
+            return ContactWithSearchResults(contact: contact.contact, indexesToHighlight: indexes)
+        }
+
+        filteredContacts = contactsWithHighlights.filter { !$0.indexesToHighlight.isEmpty }
+        tableView.reloadData()
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = true
+        }
+        deviceContactHandler.importDeviceContacts()
+        deviceContactAccessGranted = CNContactStore.authorizationStatus(for: .contacts) == .authorized
+        searchController.searchBar.text = nil
+        getContactIds()
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+    }
+
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+    }
+
+    override func numberOfSections(in _: UITableView) -> Int {
+        return deviceContactAccessGranted ? 1 : 2
+    }
+
+    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if !deviceContactAccessGranted && section == 0 {
+            return 1
+        }
+        return isFiltering() ? filteredContacts.count : contactIds.count
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let section = indexPath.section
+
+        if !deviceContactAccessGranted && section == 0 {
+            let cell: ActionCell
+            if let c = tableView.dequeueReusableCell(withIdentifier: "actionCell") as? ActionCell {
+                cell = c
+            } else {
+                cell = ActionCell(style: .default, reuseIdentifier: "actionCell")
+            }
+            cell.actionTitle = "Import Device Contacts"
+            return cell
+        } else {
+
+            let cell: ContactCell
+            if let c = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier) as? ContactCell {
+                cell = c
+            } else {
+                cell = ContactCell(style: .subtitle, reuseIdentifier: contactCellReuseIdentifier)
+            }
+            let row = indexPath.row
+            let contactRow = row
+
+            if contactRow < contactIds.count {
+                let contact: ContactWithSearchResults = isFiltering() ? filteredContacts[contactRow] : contacts[contactRow]
+                updateContactCell(cell: cell, contactWithHighlight: contact)
+                cell.selectionStyle = .none
+
+                cell.setVerified(isVerified: contact.contact.isVerified)
+            }
+            return cell
+        }
+    }
+
+    override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+        if !deviceContactAccessGranted && indexPath.section == 0 {
+            showSettingsAlert()
+        } else {
+            let contact = contactByIndexPath(indexPath)
+            let contactId = contact.contact.id
+            let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
+
+            if searchController.isActive {
+                searchController.dismiss(animated: false) {
+                    self.coordinator?.showChat(chatId: Int(chatId))
+                }
+            } else {
+                self.coordinator?.showChat(chatId: Int(chatId))
+            }
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
+        let contactId = contactByIndexPath(indexPath).contact.id
+
+        // assigning swipe by delete to chats
+        let edit = UITableViewRowAction(style: .default, title: "Edit") {
+            [unowned self] _, indexPath in
+            if self.searchController.isActive {
+                self.searchController.dismiss(animated: false) {
+                    self.coordinator?.showContactDetail(contactId: contactId)
+                }
+            } else {
+                self.coordinator?.showContactDetail(contactId: contactId)
+            }
+        }
+        edit.backgroundColor = DCColors.primary
+        return [edit]
+    }
+
+    @objc func newContactButtonPressed() {
+        coordinator?.showNewContactController()
+    }
+
+    private func updateContactCell(cell: ContactCell, contactWithHighlight: ContactWithSearchResults) {
+        let contact = contactWithHighlight.contact
+
+        if let nameHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .NAME }).first,
+            let emailHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .EMAIL }).first {
+            // gets here when contact is a result of current search -> highlights relevant indexes
+            let nameLabelFontSize = cell.nameLabel.font.pointSize
+            let emailLabelFontSize = cell.emailLabel.font.pointSize
+
+            cell.nameLabel.attributedText = contact.name.boldAt(indexes: nameHighlightedIndexes.indexes, fontSize: nameLabelFontSize)
+            cell.emailLabel.attributedText = contact.email.boldAt(indexes: emailHighlightedIndexes.indexes, fontSize: emailLabelFontSize)
+        } else {
+            cell.nameLabel.text = contact.name
+            cell.emailLabel.text = contact.email
+        }
+        cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
+        cell.setColor(contact.color)
+    }
+
+    private func contactByIndexPath(_ indexPath: IndexPath) -> ContactWithSearchResults {
+        return isFiltering() ? filteredContacts[indexPath.row] : contacts[indexPath.row]
+    }
 }
 
 extension ContactListController: ContactListDelegate {
-	func deviceContactsImported() {
-		contactIds = Utils.getContactIds()
-	}
-
-	func accessGranted() {
-		deviceContactAccessGranted = true
-	}
-
-	func accessDenied() {
-		deviceContactAccessGranted = false
-		getContactIds()
-	}
-
-	private func showSettingsAlert() {
-		let alert = UIAlertController(
-			title: "Import Contacts from to your device",
-			message: "To chat with contacts from your device open the settings menu and enable the Contacts option",
-			preferredStyle: .alert
-		)
-		alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
-			UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
-		})
-		alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
-		})
-		present(alert, animated: true)
-	}
+    func deviceContactsImported() {
+        contactIds = Utils.getContactIds()
+    }
+
+    func accessGranted() {
+        deviceContactAccessGranted = true
+    }
+
+    func accessDenied() {
+        deviceContactAccessGranted = false
+        getContactIds()
+    }
+
+    private func showSettingsAlert() {
+        let alert = UIAlertController(
+            title: "Import Contacts from to your device",
+            message: "To chat with contacts from your device open the settings menu and enable the Contacts option",
+            preferredStyle: .alert
+        )
+        alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
+            UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
+        })
+        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
+        })
+        present(alert, animated: true)
+    }
 }
 
 extension ContactListController: UISearchResultsUpdating {
-	func updateSearchResults(for searchController: UISearchController) {
-		if let searchText = searchController.searchBar.text {
-			filterContentForSearchText(searchText)
-		}
-	}
+    func updateSearchResults(for searchController: UISearchController) {
+        if let searchText = searchController.searchBar.text {
+            filterContentForSearchText(searchText)
+        }
+    }
 }

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

@@ -2,62 +2,62 @@ import UIKit
 import Reachability
 
 final class DCNavigationController: UINavigationController {
-  var stateChangedObserver: Any?
+    var stateChangedObserver: Any?
 
-  override func viewDidLoad() {
-    super.viewDidLoad()
+    override func viewDidLoad() {
+        super.viewDidLoad()
 
-    if #available(iOS 11.0, *) {
-      navigationBar.prefersLargeTitles = true
-    } else {
-      navigationBar.setBackgroundImage(UIImage(), for: .default)
+        if #available(iOS 11.0, *) {
+            navigationBar.prefersLargeTitles = true
+        } else {
+            navigationBar.setBackgroundImage(UIImage(), for: .default)
+        }
+
+        setShadow(nil)
+    }
+
+    private func setShadow(_ state: String?) {
+        switch state {
+        case "offline":
+            navigationBar.shadowImage = Constants.defaultShadow
+        case "online":
+            navigationBar.shadowImage = Constants.onlineShadow
+        default:
+            navigationBar.shadowImage = Constants.defaultShadow
+        }
     }
 
-    setShadow(nil)
-  }
-
-  private func setShadow(_ state: String?) {
-    switch state {
-    case "offline":
-      navigationBar.shadowImage = Constants.defaultShadow
-    case "online":
-      navigationBar.shadowImage = Constants.onlineShadow
-    default:
-      navigationBar.shadowImage = Constants.defaultShadow
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        if let connection = Reachability()?.connection {
+            switch connection {
+            case Reachability.Connection.cellular, Reachability.Connection.wifi:
+                setShadow("online")
+            case Reachability.Connection.none:
+                setShadow("offline")
+            }
+        }
+
+        let nc = NotificationCenter.default
+        stateChangedObserver = nc.addObserver(
+            forName: dcNotificationStateChanged,
+            object: nil,
+            queue: nil
+        ) {
+            notification in
+            if let state = notification.userInfo?["state"] {
+                self.setShadow(state as? String)
+            }
+        }
     }
-  }
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-		if let connection = Reachability()?.connection {
-			switch connection {
-			case Reachability.Connection.cellular, Reachability.Connection.wifi:
-				setShadow("online")
-			case Reachability.Connection.none:
-				setShadow("offline")
-			}
-		}
-
-		let nc = NotificationCenter.default
-		stateChangedObserver = nc.addObserver(
-			forName: dcNotificationStateChanged,
-			object: nil,
-			queue: nil
-		) {
-			notification in
-			if let state = notification.userInfo?["state"] {
-				self.setShadow(state as? String)
-			}
-		}
-	}
-
-  override func viewWillDisappear(_ animated: Bool) {
-    super.viewWillDisappear(animated)
-
-    let nc = NotificationCenter.default
-    if let stateChangedObserver = self.stateChangedObserver {
-      nc.removeObserver(stateChangedObserver)
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+
+        let nc = NotificationCenter.default
+        if let stateChangedObserver = self.stateChangedObserver {
+            nc.removeObserver(stateChangedObserver)
+        }
     }
-  }
 
 }

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

@@ -2,34 +2,34 @@ import UIKit
 
 class EditContactController: NewContactController {
 
-	// for editing existing contacts (only
-	// the name may be edited, therefore disable
-	// the email field)
-	init(contactIdForUpdate: Int) {
-		super.init()
-		title = "Edit Contact"
-
-		let contact = MRContact(id: contactIdForUpdate)
-		nameCell.textField.text = contact.name
-		emailCell.textField.text = contact.email
-		emailCell.textField.isEnabled = false
-		emailCell.contentView.alpha = 0.3
-
-		model.name = contact.name
-		model.email = contact.email
-
-		if contactIsValid() {
-			doneButton?.isEnabled = true
-		}
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	@objc override func saveContactButtonPressed() {
-		dc_create_contact(mailboxPointer, model.name, model.email)
-		coordinator?.navigateBack()
-	}
+    // for editing existing contacts (only
+    // the name may be edited, therefore disable
+    // the email field)
+    init(contactIdForUpdate: Int) {
+        super.init()
+        title = "Edit Contact"
+
+        let contact = MRContact(id: contactIdForUpdate)
+        nameCell.textField.text = contact.name
+        emailCell.textField.text = contact.email
+        emailCell.textField.isEnabled = false
+        emailCell.contentView.alpha = 0.3
+
+        model.name = contact.name
+        model.email = contact.email
+
+        if contactIsValid() {
+            doneButton?.isEnabled = true
+        }
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    @objc override func saveContactButtonPressed() {
+        dc_create_contact(mailboxPointer, model.name, model.email)
+        coordinator?.navigateBack()
+    }
 
 }

+ 51 - 51
deltachat-ios/Controller/EditGroupViewController.swift

@@ -2,68 +2,68 @@ import UIKit
 
 class EditGroupViewController: UITableViewController {
 
-	weak var coordinator: EditGroupCoordinator?
+    weak var coordinator: EditGroupCoordinator?
 
-	private let chat: MRChat
+    private let chat: MRChat
 
-	lazy var groupNameCell: GroupLabelCell = {
-		let cell = GroupLabelCell(style: .default, reuseIdentifier: nil)
-		cell.onTextChanged = groupNameEdited(_:)
-		return cell
-	}()
+    lazy var groupNameCell: GroupLabelCell = {
+        let cell = GroupLabelCell(style: .default, reuseIdentifier: nil)
+        cell.onTextChanged = groupNameEdited(_:)
+        return cell
+    }()
 
-	lazy var doneButton: UIBarButtonItem = {
-		let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(saveContactButtonPressed))
-		button.isEnabled = false
-		return button
-	}()
+    lazy var doneButton: UIBarButtonItem = {
+        let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(saveContactButtonPressed))
+        button.isEnabled = false
+        return button
+    }()
 
-	lazy var cancelButton: UIBarButtonItem = {
-		let button = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
-		return button
-	}()
+    lazy var cancelButton: UIBarButtonItem = {
+        let button = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
+        return button
+    }()
 
-	init(chat: MRChat) {
-		self.chat = chat
-		super.init(style: .grouped)
-		groupNameCell.inputField.text = chat.name
-		groupNameCell.groupBadge.setName(chat.name)
-		groupNameCell.groupBadge.setColor(chat.color)
-	}
+    init(chat: MRChat) {
+        self.chat = chat
+        super.init(style: .grouped)
+        groupNameCell.inputField.text = chat.name
+        groupNameCell.groupBadge.setName(chat.name)
+        groupNameCell.groupBadge.setColor(chat.color)
+    }
 
-	required init?(coder aDecoder: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		navigationItem.rightBarButtonItem = doneButton
-		navigationItem.leftBarButtonItem = cancelButton
-	}
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        navigationItem.rightBarButtonItem = doneButton
+        navigationItem.leftBarButtonItem = cancelButton
+    }
 
-	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-		return groupNameCell
-	}
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        return groupNameCell
+    }
 
-	override func numberOfSections(in tableView: UITableView) -> Int {
-		return 1
-	}
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
 
-	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-		return 1
-	}
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return 1
+    }
 
-	@objc func saveContactButtonPressed() {
-		let newName = groupNameCell.getGroupName()
-		dc_set_chat_name(mailboxPointer, UInt32(chat.id), newName)
-		coordinator?.navigateBack()
-	}
+    @objc func saveContactButtonPressed() {
+        let newName = groupNameCell.getGroupName()
+        dc_set_chat_name(mailboxPointer, UInt32(chat.id), newName)
+        coordinator?.navigateBack()
+    }
 
-	@objc func cancelButtonPressed() {
-		coordinator?.navigateBack()
-	}
+    @objc func cancelButtonPressed() {
+        coordinator?.navigateBack()
+    }
 
-	private func groupNameEdited(_ newName: String) {
-		doneButton.isEnabled = true
-	}
+    private func groupNameEdited(_ newName: String) {
+        doneButton.isEnabled = true
+    }
 }

+ 61 - 61
deltachat-ios/Controller/EditSettingsController.swift

@@ -2,78 +2,78 @@ import UIKit
 
 class EditSettingsController: UITableViewController {
 
-	private var displayNameBackup: String?
-	private var statusCellBackup: String?
+    private var displayNameBackup: String?
+    private var statusCellBackup: String?
 
-	private lazy var displayNameCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "Display Name", placeholder: "Display Name")
-		cell.setText(text: MRConfig.displayname ?? nil)
-		return cell
-	}()
+    private lazy var displayNameCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "Display Name", placeholder: "Display Name")
+        cell.setText(text: MRConfig.displayname ?? nil)
+        return cell
+    }()
 
-	private lazy var statusCell: TextFieldCell = {
-		let cell = TextFieldCell(description: "Status", placeholder: "Your Status")
-		cell.setText(text: MRConfig.selfstatus ?? nil)
-		return cell
-	}()
+    private lazy var statusCell: TextFieldCell = {
+        let cell = TextFieldCell(description: "Status", placeholder: "Your Status")
+        cell.setText(text: MRConfig.selfstatus ?? nil)
+        return cell
+    }()
 
-	init() {
-		super.init(style: .grouped)
-	}
+    init() {
+        super.init(style: .grouped)
+    }
 
-	required init?(coder aDecoder: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		// Uncomment the following line to preserve selection between presentations
-		// self.clearsSelectionOnViewWillAppear = false
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        // Uncomment the following line to preserve selection between presentations
+        // self.clearsSelectionOnViewWillAppear = false
 
-		// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
-		// self.navigationItem.rightBarButtonItem = self.editButtonItem
-	}
+        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
+        // self.navigationItem.rightBarButtonItem = self.editButtonItem
+    }
 
-	override func viewWillAppear(_ animated: Bool) {
-		displayNameBackup = MRConfig.displayname
-		statusCellBackup = MRConfig.selfstatus
-	}
+    override func viewWillAppear(_ animated: Bool) {
+        displayNameBackup = MRConfig.displayname
+        statusCellBackup = MRConfig.selfstatus
+    }
 
-	override func viewWillDisappear(_ animated: Bool) {
-		if displayNameBackup != displayNameCell.getText() || statusCellBackup != displayNameCell.getText() {
-			MRConfig.selfstatus = statusCell.getText()
-			MRConfig.displayname = displayNameCell.getText()	
-			dc_configure(mailboxPointer)
-		}
-	}
+    override func viewWillDisappear(_ animated: Bool) {
+        if displayNameBackup != displayNameCell.getText() || statusCellBackup != displayNameCell.getText() {
+            MRConfig.selfstatus = statusCell.getText()
+            MRConfig.displayname = displayNameCell.getText()	
+            dc_configure(mailboxPointer)
+        }
+    }
 
-	// MARK: - Table view data source
+    // MARK: - Table view data source
 
-	override func numberOfSections(in tableView: UITableView) -> Int {
-		// #warning Incomplete implementation, return the number of sections
-		return 2
-	}
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        // #warning Incomplete implementation, return the number of sections
+        return 2
+    }
 
-	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-		// #warning Incomplete implementation, return the number of rows
-		return 1
-	}
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        // #warning Incomplete implementation, return the number of rows
+        return 1
+    }
 
-	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-		let section = indexPath.section
-		if section == 0 {
-			return displayNameCell
-		} else {
-			return statusCell
-		}
-	}
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let section = indexPath.section
+        if section == 0 {
+            return displayNameCell
+        } else {
+            return statusCell
+        }
+    }
 
-	func activateField(option: SettingsEditOption) {
-		switch option {
-		case .DISPLAYNAME:
-			displayNameCell.textField.becomeFirstResponder()
-		case .STATUS:
-			statusCell.textField.becomeFirstResponder()
-		}
-	}
+    func activateField(option: SettingsEditOption) {
+        switch option {
+        case .DISPLAYNAME:
+            displayNameCell.textField.becomeFirstResponder()
+        case .STATUS:
+            statusCell.textField.becomeFirstResponder()
+        }
+    }
 }

+ 213 - 213
deltachat-ios/Controller/GroupChatDetailViewController.swift

@@ -1,222 +1,222 @@
 import UIKit
 
 class GroupChatDetailViewController: UIViewController {
-  private var currentUser: MRContact? {
-    return groupMembers.filter { $0.email == MRConfig.addr }.first
-  }
-
-	weak var coordinator: GroupChatDetailCoordinator?
-
-	fileprivate var chat: MRChat
-
-	var chatDetailTable: UITableView = {
-		let table = UITableView(frame: .zero, style: .grouped)
-		table.bounces = false
-		table.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
-		table.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
-		table.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
-
-		return table
-	}()
-
-	init(chatId: Int) {
-		chat = MRChat(id: chatId)
-		super.init(nibName: nil, bundle: nil)
-		setupSubviews()
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	private func setupSubviews() {
-		view.addSubview(chatDetailTable)
-		chatDetailTable.translatesAutoresizingMaskIntoConstraints = false
-
-		chatDetailTable.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
-		chatDetailTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
-		chatDetailTable.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
-		chatDetailTable.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
-	}
-
-	private func showNotificationSetup() {
-		let notificationSetupAlert = UIAlertController(title: "Notifications Setup is not implemented yet", message: "But you get an idea where this is going", preferredStyle: .actionSheet)
-		let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
-		notificationSetupAlert.addAction(cancelAction)
-		present(notificationSetupAlert, animated: true, completion: nil)
-	}
-
-  private lazy var editBarButtonItem: UIBarButtonItem = {
-    UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(editButtonPressed))
-  }()
-
-  private var groupMembers: [MRContact] = []
-
-  private let staticCellCountMemberSection = 1 //
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    title = "Group Info"
-    chatDetailTable.delegate = self
-    chatDetailTable.dataSource = self
-    navigationItem.rightBarButtonItem = editBarButtonItem
-  }
-
-  override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-		updateGroupMembers()
-		chatDetailTable.reloadData() // to display updates
-		editBarButtonItem.isEnabled = currentUser != nil
-  }
-
-  private func updateGroupMembers() {
-    let ids = chat.contactIds
-    groupMembers = ids.map { MRContact(id: $0) }
-    chatDetailTable.reloadData()
-  }
-
-  @objc func editButtonPressed() {
-		coordinator?.showGroupChatEdit(chat: chat)
-  }
-
-  private func leaveGroup() {
-    if let userId = currentUser?.id {
-      dc_remove_contact_from_chat(mailboxPointer, UInt32(chat.id), UInt32(userId))
-      editBarButtonItem.isEnabled = false
-      updateGroupMembers()
-    }
-  }
+    private var currentUser: MRContact? {
+        return groupMembers.filter { $0.email == MRConfig.addr }.first
+    }
+
+    weak var coordinator: GroupChatDetailCoordinator?
+
+    fileprivate var chat: MRChat
+
+    var chatDetailTable: UITableView = {
+        let table = UITableView(frame: .zero, style: .grouped)
+        table.bounces = false
+        table.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
+        table.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
+        table.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
+
+        return table
+    }()
+
+    init(chatId: Int) {
+        chat = MRChat(id: chatId)
+        super.init(nibName: nil, bundle: nil)
+        setupSubviews()
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func setupSubviews() {
+        view.addSubview(chatDetailTable)
+        chatDetailTable.translatesAutoresizingMaskIntoConstraints = false
+
+        chatDetailTable.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
+        chatDetailTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
+        chatDetailTable.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
+        chatDetailTable.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
+    }
+
+    private func showNotificationSetup() {
+        let notificationSetupAlert = UIAlertController(title: "Notifications Setup is not implemented yet", message: "But you get an idea where this is going", preferredStyle: .actionSheet)
+        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
+        notificationSetupAlert.addAction(cancelAction)
+        present(notificationSetupAlert, animated: true, completion: nil)
+    }
+
+    private lazy var editBarButtonItem: UIBarButtonItem = {
+        UIBarButtonItem(title: "Edit", style: .plain, target: self, action: #selector(editButtonPressed))
+    }()
+
+    private var groupMembers: [MRContact] = []
+
+    private let staticCellCountMemberSection = 1 //
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Group Info"
+        chatDetailTable.delegate = self
+        chatDetailTable.dataSource = self
+        navigationItem.rightBarButtonItem = editBarButtonItem
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        updateGroupMembers()
+        chatDetailTable.reloadData() // to display updates
+        editBarButtonItem.isEnabled = currentUser != nil
+    }
+
+    private func updateGroupMembers() {
+        let ids = chat.contactIds
+        groupMembers = ids.map { MRContact(id: $0) }
+        chatDetailTable.reloadData()
+    }
+
+    @objc func editButtonPressed() {
+        coordinator?.showGroupChatEdit(chat: chat)
+    }
+
+    private func leaveGroup() {
+        if let userId = currentUser?.id {
+            dc_remove_contact_from_chat(mailboxPointer, UInt32(chat.id), UInt32(userId))
+            editBarButtonItem.isEnabled = false
+            updateGroupMembers()
+        }
+    }
 }
 
 extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSource {
-  func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
-    if section == 1 {
-      return "Members:"
-    }
-    return nil
-  }
-
-  func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
-    if section == 0 {
-			let header = ContactDetailHeader()
-			header.updateDetails(title: chat.name, subtitle: chat.subtitle)
-			if let img = chat.profileImage {
-				header.setImage(img)
-			} else {
-				header.setBackupImage(name: chat.name, color: chat.color)
-			}
-			header.setVerified(isVerified: chat.isVerified)
-			return header
-		} else {
-      return nil
-    }
-  }
-
-  func numberOfSections(in _: UITableView) -> Int {
-    /*
-     section 0: config
-     section 1: members
-     section 2: leave group (optional - if user already left group this option will be hidden)
-     */
-
-    if currentUser == nil {
-      return 2
-    }
-    return 3
-  }
-
-  func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-    if section == 0 {
-      return 1
-    } else if section == 1 {
-      return groupMembers.count + staticCellCountMemberSection // first cell is addGroupMemberCell
-    } else if section == 2 {
-      return 1
-    } else {
-      return 0
-    }
-  }
-
-  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let section = indexPath.section
-    let row = indexPath.row
-
-    if section == 0 {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
-      cell.textLabel?.text = "Notifications"
-      cell.selectionStyle = .none
-      return cell
-    } else if section == 1 {
-      if row == 0 {
-        let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionCell
-        cell.actionTitle = "Add Members"
-        return cell
-      } else {
-        let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
-        let contact = groupMembers[row - staticCellCountMemberSection]
-        cell.nameLabel.text = contact.name
-        cell.emailLabel.text = contact.email
-        cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-        cell.setColor(contact.color)
-        return cell
-      }
-    } else if section == 2 {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionCell
-      cell.actionTitle = "Leave Group"
-      cell.actionColor = UIColor.red
-      return cell
-    }
-
-    return UITableViewCell(frame: .zero)
-  }
-
-  func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-    let section = indexPath.section
-    let row = indexPath.row
-    if section == 0 {
-      showNotificationSetup()
-    } else if section == 1 {
-      if row == 0 {
-        coordinator?.showAddGroupMember(chatId: chat.id)
-      }
-      // ignore for now - in Telegram tapping a contactCell leads into ContactDetail
-    } else if section == 2 {
-      leaveGroup()
-    }
-  }
-
-  func tableView(_: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
-    let section = indexPath.section
-    let row = indexPath.row
-
-    if let currentUser = currentUser {
-      if section == 1, row > 0, groupMembers[row - staticCellCountMemberSection].id != currentUser.id {
-        return true
-      }
-    }
-    return false
-  }
-
-  func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
-    let section = indexPath.section
-    let row = indexPath.row
-
-    // assigning swipe by delete to members (except for current user)
-    if section == 1, row >= staticCellCountMemberSection, groupMembers[row - staticCellCountMemberSection].id != currentUser?.id {
-      let delete = UITableViewRowAction(style: .destructive, title: "Delete") { [unowned self] _, indexPath in
-
-        let memberId = self.groupMembers[row - self.staticCellCountMemberSection].id
-        let success = dc_remove_contact_from_chat(mailboxPointer, UInt32(self.chat.id), UInt32(memberId))
-        if success == 1 {
-          self.groupMembers.remove(at: row - self.staticCellCountMemberSection)
-          tableView.deleteRows(at: [indexPath], with: .fade)
-          tableView.reloadData()
+    func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
+        if section == 1 {
+            return "Members:"
+        }
+        return nil
+    }
+
+    func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        if section == 0 {
+            let header = ContactDetailHeader()
+            header.updateDetails(title: chat.name, subtitle: chat.subtitle)
+            if let img = chat.profileImage {
+                header.setImage(img)
+            } else {
+                header.setBackupImage(name: chat.name, color: chat.color)
+            }
+            header.setVerified(isVerified: chat.isVerified)
+            return header
+        } else {
+            return nil
+        }
+    }
+
+    func numberOfSections(in _: UITableView) -> Int {
+        /*
+         section 0: config
+         section 1: members
+         section 2: leave group (optional - if user already left group this option will be hidden)
+         */
+
+        if currentUser == nil {
+            return 2
+        }
+        return 3
+    }
+
+    func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if section == 0 {
+            return 1
+        } else if section == 1 {
+            return groupMembers.count + staticCellCountMemberSection // first cell is addGroupMemberCell
+        } else if section == 2 {
+            return 1
+        } else {
+            return 0
+        }
+    }
+
+    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let section = indexPath.section
+        let row = indexPath.row
+
+        if section == 0 {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
+            cell.textLabel?.text = "Notifications"
+            cell.selectionStyle = .none
+            return cell
+        } else if section == 1 {
+            if row == 0 {
+                let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionCell
+                cell.actionTitle = "Add Members"
+                return cell
+            } else {
+                let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
+                let contact = groupMembers[row - staticCellCountMemberSection]
+                cell.nameLabel.text = contact.name
+                cell.emailLabel.text = contact.email
+                cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
+                cell.setColor(contact.color)
+                return cell
+            }
+        } else if section == 2 {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionCell
+            cell.actionTitle = "Leave Group"
+            cell.actionColor = UIColor.red
+            return cell
+        }
+
+        return UITableViewCell(frame: .zero)
+    }
+
+    func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let section = indexPath.section
+        let row = indexPath.row
+        if section == 0 {
+            showNotificationSetup()
+        } else if section == 1 {
+            if row == 0 {
+                coordinator?.showAddGroupMember(chatId: chat.id)
+            }
+            // ignore for now - in Telegram tapping a contactCell leads into ContactDetail
+        } else if section == 2 {
+            leaveGroup()
+        }
+    }
+
+    func tableView(_: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
+        let section = indexPath.section
+        let row = indexPath.row
+
+        if let currentUser = currentUser {
+            if section == 1, row > 0, groupMembers[row - staticCellCountMemberSection].id != currentUser.id {
+                return true
+            }
+        }
+        return false
+    }
+
+    func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
+        let section = indexPath.section
+        let row = indexPath.row
+
+        // assigning swipe by delete to members (except for current user)
+        if section == 1, row >= staticCellCountMemberSection, groupMembers[row - staticCellCountMemberSection].id != currentUser?.id {
+            let delete = UITableViewRowAction(style: .destructive, title: "Delete") { [unowned self] _, indexPath in
+
+                let memberId = self.groupMembers[row - self.staticCellCountMemberSection].id
+                let success = dc_remove_contact_from_chat(mailboxPointer, UInt32(self.chat.id), UInt32(memberId))
+                if success == 1 {
+                    self.groupMembers.remove(at: row - self.staticCellCountMemberSection)
+                    tableView.deleteRows(at: [indexPath], with: .fade)
+                    tableView.reloadData()
+                }
+            }
+            delete.backgroundColor = UIColor.red
+            return [delete]
+        } else {
+            return nil
         }
-      }
-      delete.backgroundColor = UIColor.red
-      return [delete]
-    } else {
-      return nil
     }
-  }
 }

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

@@ -1,157 +1,157 @@
 import UIKit
 
 class NewGroupViewController: GroupMembersViewController {
-  weak var coordinator: NewGroupCoordinator?
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    title = "New Group"
-    navigationController?.navigationBar.prefersLargeTitles = false
-    let groupCreationNextButton = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(nextButtonPressed))
-    navigationItem.rightBarButtonItem = groupCreationNextButton
-    contactIds = Utils.getContactIds()
-  }
-
-  override func didReceiveMemoryWarning() {
-    super.didReceiveMemoryWarning()
-  }
-
-  @objc func nextButtonPressed() {
-    coordinator?.showGroupNameController(contactIdsForGroup: selectedContactIds)
-  }
-}
-
-class AddGroupMembersViewController: GroupMembersViewController {
-  private var chatId: Int?
-
-  private lazy var resetButton: UIBarButtonItem = {
-    let button = UIBarButtonItem(title: "Reset", style: .plain, target: self, action: #selector(resetButtonPressed))
-    button.isEnabled = false
-    return button
-  }()
-
-  override var selectedContactIds: Set<Int> {
-    didSet {
-      resetButton.isEnabled = !selectedContactIds.isEmpty
+    weak var coordinator: NewGroupCoordinator?
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "New Group"
+        navigationController?.navigationBar.prefersLargeTitles = false
+        let groupCreationNextButton = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(nextButtonPressed))
+        navigationItem.rightBarButtonItem = groupCreationNextButton
+        contactIds = Utils.getContactIds()
     }
-  }
 
-  private lazy var chat: MRChat? = {
-    if let chatId = chatId {
-      return MRChat(id: chatId)
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
     }
-    return nil
-  }()
 
-  private lazy var chatMemberIds: [Int] = {
-    if let chat = chat {
-      return chat.contactIds
+    @objc func nextButtonPressed() {
+        coordinator?.showGroupNameController(contactIdsForGroup: selectedContactIds)
     }
-    return []
-  }()
+}
 
-  private lazy var memberCandidateIds: [Int] = {
-    var contactIds = Set(Utils.getContactIds()) // turn into set to speed up search
-    for member in chatMemberIds {
-      contactIds.remove(member)
+class AddGroupMembersViewController: GroupMembersViewController {
+    private var chatId: Int?
+
+    private lazy var resetButton: UIBarButtonItem = {
+        let button = UIBarButtonItem(title: "Reset", style: .plain, target: self, action: #selector(resetButtonPressed))
+        button.isEnabled = false
+        return button
+    }()
+
+    override var selectedContactIds: Set<Int> {
+        didSet {
+            resetButton.isEnabled = !selectedContactIds.isEmpty
+        }
     }
-    return Array(contactIds)
-  }()
-
-  init(chatId: Int) {
-    super.init()
-    self.chatId = chatId
-  }
 
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
+    private lazy var chat: MRChat? = {
+        if let chatId = chatId {
+            return MRChat(id: chatId)
+        }
+        return nil
+    }()
+
+    private lazy var chatMemberIds: [Int] = {
+        if let chat = chat {
+            return chat.contactIds
+        }
+        return []
+    }()
+
+    private lazy var memberCandidateIds: [Int] = {
+        var contactIds = Set(Utils.getContactIds()) // turn into set to speed up search
+        for member in chatMemberIds {
+            contactIds.remove(member)
+        }
+        return Array(contactIds)
+    }()
+
+    init(chatId: Int) {
+        super.init()
+        self.chatId = chatId
+    }
 
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    super.contactIds = memberCandidateIds
-    super.navigationItem.rightBarButtonItem = resetButton
-    title = "Add Group Members"
-    // Do any additional setup after loading the view.
-  }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-  override func viewWillDisappear(_: Bool) {
-    guard let chatId = chatId else {
-      return
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        super.contactIds = memberCandidateIds
+        super.navigationItem.rightBarButtonItem = resetButton
+        title = "Add Group Members"
+        // Do any additional setup after loading the view.
     }
-    for contactId in selectedContactIds {
-      dc_add_contact_to_chat(mailboxPointer, UInt32(chatId), UInt32(contactId))
+
+    override func viewWillDisappear(_: Bool) {
+        guard let chatId = chatId else {
+            return
+        }
+        for contactId in selectedContactIds {
+            dc_add_contact_to_chat(mailboxPointer, UInt32(chatId), UInt32(contactId))
+        }
     }
-  }
 
-  @objc func resetButtonPressed() {
-    selectedContactIds = []
-    tableView.reloadData()
-  }
+    @objc func resetButtonPressed() {
+        selectedContactIds = []
+        tableView.reloadData()
+    }
 }
 
 class GroupMembersViewController: UITableViewController {
-  let contactCellReuseIdentifier = "contactCell"
+    let contactCellReuseIdentifier = "contactCell"
 
-  var contactIds: [Int] = [] {
-    didSet {
-      tableView.reloadData()
+    var contactIds: [Int] = [] {
+        didSet {
+            tableView.reloadData()
+        }
     }
-  }
-
-  var selectedContactIds: Set<Int> = []
 
-  init() {
-    super.init(nibName: nil, bundle: nil)
-    hidesBottomBarWhenPushed = true
-  }
+    var selectedContactIds: Set<Int> = []
 
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
+    init() {
+        super.init(nibName: nil, bundle: nil)
+        hidesBottomBarWhenPushed = true
+    }
 
-  override func viewDidLoad() {
-    tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
-  }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-  override func numberOfSections(in _: UITableView) -> Int {
-    return 1
-  }
+    override func viewDidLoad() {
+        tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
+    }
 
-  override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-    return contactIds.count
-  }
+    override func numberOfSections(in _: UITableView) -> Int {
+        return 1
+    }
 
-  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    guard let cell: ContactCell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as? ContactCell else {
-      fatalError("shouldn't happen")
+    override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+        return contactIds.count
     }
 
-    let row = indexPath.row
-    let contactRow = row
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        guard let cell: ContactCell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as? ContactCell else {
+            fatalError("shouldn't happen")
+        }
 
-    let contact = MRContact(id: contactIds[contactRow])
-    cell.nameLabel.text = contact.name
-    cell.emailLabel.text = contact.email
-    cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-    cell.accessoryType = selectedContactIds.contains(contactIds[row]) ? .checkmark : .none
-    cell.setColor(contact.color)
+        let row = indexPath.row
+        let contactRow = row
 
-    return cell
-  }
+        let contact = MRContact(id: contactIds[contactRow])
+        cell.nameLabel.text = contact.name
+        cell.emailLabel.text = contact.email
+        cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
+        cell.accessoryType = selectedContactIds.contains(contactIds[row]) ? .checkmark : .none
+        cell.setColor(contact.color)
+
+        return cell
+    }
 
-  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-    let row = indexPath.row
-    if let cell = tableView.cellForRow(at: indexPath) {
-      tableView.deselectRow(at: indexPath, animated: true)
-      let contactId = contactIds[row]
-      if selectedContactIds.contains(contactId) {
-        selectedContactIds.remove(contactId)
-        cell.accessoryType = .none
-      } else {
-        selectedContactIds.insert(contactId)
-        cell.accessoryType = .checkmark
-      }
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let row = indexPath.row
+        if let cell = tableView.cellForRow(at: indexPath) {
+            tableView.deselectRow(at: indexPath, animated: true)
+            let contactId = contactIds[row]
+            if selectedContactIds.contains(contactId) {
+                selectedContactIds.remove(contactId)
+                cell.accessoryType = .none
+            } else {
+                selectedContactIds.insert(contactId)
+                cell.accessoryType = .checkmark
+            }
+        }
     }
-  }
 }

+ 81 - 81
deltachat-ios/Controller/GroupNameController.swift

@@ -1,101 +1,101 @@
 import UIKit
 
 class GroupNameController: UITableViewController {
-  weak var coordinator: GroupNameCoordinator?
-
-  var groupName: String = ""
-
-  var doneButton: UIBarButtonItem!
-  let contactIdsForGroup: Set<Int> // TODO: check if array is sufficient
-  let groupContactIds: [Int]
-
-  init(contactIdsForGroup: Set<Int>) {
-    self.contactIdsForGroup = contactIdsForGroup
-    groupContactIds = Array(contactIdsForGroup)
-    super.init(style: .grouped)
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    title = "New Group"
-    doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed))
-    navigationItem.rightBarButtonItem = doneButton
-    tableView.bounces = false
-    doneButton.isEnabled = false
-    tableView.register(GroupLabelCell.self, forCellReuseIdentifier: "groupLabelCell")
-    tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
-    // setupSubviews()
-  }
-
-  @objc func doneButtonPressed() {
-    let groupChatId = dc_create_group_chat(mailboxPointer, 0, groupName)
-    for contactId in contactIdsForGroup {
-      let success = dc_add_contact_to_chat(mailboxPointer, groupChatId, UInt32(contactId))
-      if success == 1 {
-        logger.info("successfully added \(contactId) to group \(groupName)")
-      } else {
-        // FIXME:
-        fatalError("failed to add \(contactId) to group \(groupName)")
-      }
+    weak var coordinator: GroupNameCoordinator?
+
+    var groupName: String = ""
+
+    var doneButton: UIBarButtonItem!
+    let contactIdsForGroup: Set<Int> // TODO: check if array is sufficient
+    let groupContactIds: [Int]
+
+    init(contactIdsForGroup: Set<Int>) {
+        self.contactIdsForGroup = contactIdsForGroup
+        groupContactIds = Array(contactIdsForGroup)
+        super.init(style: .grouped)
     }
 
-    coordinator?.showGroupChat(chatId: Int(groupChatId))
-  }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-  override func didReceiveMemoryWarning() {
-    super.didReceiveMemoryWarning()
-    // Dispose of any resources that can be recreated.
-  }
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "New Group"
+        doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed))
+        navigationItem.rightBarButtonItem = doneButton
+        tableView.bounces = false
+        doneButton.isEnabled = false
+        tableView.register(GroupLabelCell.self, forCellReuseIdentifier: "groupLabelCell")
+        tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
+        // setupSubviews()
+    }
 
-  override func numberOfSections(in _: UITableView) -> Int {
-    return 2
-  }
+    @objc func doneButtonPressed() {
+        let groupChatId = dc_create_group_chat(mailboxPointer, 0, groupName)
+        for contactId in contactIdsForGroup {
+            let success = dc_add_contact_to_chat(mailboxPointer, groupChatId, UInt32(contactId))
+            if success == 1 {
+                logger.info("successfully added \(contactId) to group \(groupName)")
+            } else {
+                // FIXME:
+                fatalError("failed to add \(contactId) to group \(groupName)")
+            }
+        }
+
+        coordinator?.showGroupChat(chatId: Int(groupChatId))
+    }
 
-  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let section = indexPath.section
-    let row = indexPath.row
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+        // Dispose of any resources that can be recreated.
+    }
+
+    override func numberOfSections(in _: UITableView) -> Int {
+        return 2
+    }
 
-    if section == 0 {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "groupLabelCell", for: indexPath) as! GroupLabelCell
-      cell.onTextChanged = updateGroupName
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let section = indexPath.section
+        let row = indexPath.row
 
-      return cell
+        if section == 0 {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "groupLabelCell", for: indexPath) as! GroupLabelCell
+            cell.onTextChanged = updateGroupName
 
-    } else {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
+            return cell
 
-      let contact = MRContact(id: groupContactIds[row])
-      cell.nameLabel.text = contact.name
-      cell.emailLabel.text = contact.email
-      cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-      cell.setColor(contact.color)
+        } else {
+            let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
 
-      return cell
+            let contact = MRContact(id: groupContactIds[row])
+            cell.nameLabel.text = contact.name
+            cell.emailLabel.text = contact.email
+            cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
+            cell.setColor(contact.color)
+
+            return cell
+        }
     }
-  }
 
-  override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-    if section == 0 {
-      return 1
-    } else {
-      return contactIdsForGroup.count
+    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if section == 0 {
+            return 1
+        } else {
+            return contactIdsForGroup.count
+        }
     }
-  }
 
-  override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
-    if section == 1 {
-      return "Group Members"
-    } else {
-      return nil
+    override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
+        if section == 1 {
+            return "Group Members"
+        } else {
+            return nil
+        }
     }
-  }
 
-  private func updateGroupName(name: String) {
-    groupName = name
-    doneButton.isEnabled = name.containsCharacters()
-  }
+    private func updateGroupName(name: String) {
+        groupName = name
+        doneButton.isEnabled = name.containsCharacters()
+    }
 }

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

@@ -1,18 +1,18 @@
 import UIKit
 
 class MailboxViewController: ChatViewController {
-  override init(chatId: Int, title: String? = nil) {
-    super.init(chatId: chatId, title: title)
-    hidesBottomBarWhenPushed = false
-  }
+    override init(chatId: Int, title: String? = nil) {
+        super.init(chatId: chatId, title: title)
+        hidesBottomBarWhenPushed = false
+    }
 
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-  override func viewDidLoad() {
-    super.viewDidLoad()
+    override func viewDidLoad() {
+        super.viewDidLoad()
 
-    // Do any additional setup after loading the view.
-  }
+        // Do any additional setup after loading the view.
+    }
 }

+ 91 - 91
deltachat-ios/Controller/MessageInfoViewController.swift

@@ -1,99 +1,99 @@
 import UIKit
 
 class MessageInfoViewController: UITableViewController {
-  var message: MRMessage
-
-  init(message: MRMessage) {
-    self.message = message
-
-    super.init(style: .plain)
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    title = "Message Info"
-    // Uncomment the following line to preserve selection between presentations
-    // self.clearsSelectionOnViewWillAppear = false
-
-    // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
-    // self.navigationItem.rightBarButtonItem = self.editButtonItem
-  }
-
-  // MARK: - Table view data source
-
-  override func numberOfSections(in _: UITableView) -> Int {
-    // #warning Incomplete implementation, return the number of sections
-    return 1
-  }
-
-  override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-    // #warning Incomplete implementation, return the number of rows
-    return 1
-  }
-
-  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let cell: UITableViewCell
-    if let c = tableView.dequeueReusableCell(withIdentifier: "MessageInfoCell") {
-      cell = c
-    } else {
-      cell = UITableViewCell(style: .default, reuseIdentifier: "MessageInfoCell")
+    var message: MRMessage
+
+    init(message: MRMessage) {
+        self.message = message
+
+        super.init(style: .plain)
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Message Info"
+        // Uncomment the following line to preserve selection between presentations
+        // self.clearsSelectionOnViewWillAppear = false
+
+        // Uncomment the following line to display an Edit button in the navigation bar for this view controller.
+        // self.navigationItem.rightBarButtonItem = self.editButtonItem
+    }
+
+    // MARK: - Table view data source
+
+    override func numberOfSections(in _: UITableView) -> Int {
+        // #warning Incomplete implementation, return the number of sections
+        return 1
+    }
+
+    override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+        // #warning Incomplete implementation, return the number of rows
+        return 1
     }
 
-    if indexPath.section == 0 {
-      if indexPath.row == 0 {
-        cell.textLabel?.text = message.text
-      }
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cell: UITableViewCell
+        if let c = tableView.dequeueReusableCell(withIdentifier: "MessageInfoCell") {
+            cell = c
+        } else {
+            cell = UITableViewCell(style: .default, reuseIdentifier: "MessageInfoCell")
+        }
+
+        if indexPath.section == 0 {
+            if indexPath.row == 0 {
+                cell.textLabel?.text = message.text
+            }
+        }
+
+        return cell
     }
 
-    return cell
-  }
-
-  /*
-   // Override to support conditional editing of the table view.
-   override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
-   // Return false if you do not want the specified item to be editable.
-   return true
-   }
-   */
-
-  /*
-   // Override to support editing the table view.
-   override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
-   if editingStyle == .delete {
-   // Delete the row from the data source
-   tableView.deleteRows(at: [indexPath], with: .fade)
-   } else if editingStyle == .insert {
-   // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
-   }
-   }
-   */
-
-  /*
-   // Override to support rearranging the table view.
-   override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
-
-   }
-   */
-
-  /*
-   // Override to support conditional rearranging of the table view.
-   override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
-   // Return false if you do not want the item to be re-orderable.
-   return true
-   }
-   */
-
-  /*
-   // MARK: - Navigation
-
-   // In a storyboard-based application, you will often want to do a little preparation before navigation
-   override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
-   // Get the new view controller using segue.destination.
-   // Pass the selected object to the new view controller.
-   }
-   */
+    /*
+     // Override to support conditional editing of the table view.
+     override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
+     // Return false if you do not want the specified item to be editable.
+     return true
+     }
+     */
+
+    /*
+     // Override to support editing the table view.
+     override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
+     if editingStyle == .delete {
+     // Delete the row from the data source
+     tableView.deleteRows(at: [indexPath], with: .fade)
+     } else if editingStyle == .insert {
+     // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
+     }
+     }
+     */
+
+    /*
+     // Override to support rearranging the table view.
+     override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
+
+     }
+     */
+
+    /*
+     // Override to support conditional rearranging of the table view.
+     override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
+     // Return false if you do not want the item to be re-orderable.
+     return true
+     }
+     */
+
+    /*
+     // MARK: - Navigation
+
+     // In a storyboard-based application, you will often want to do a little preparation before navigation
+     override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+     // Get the new view controller using segue.destination.
+     // Pass the selected object to the new view controller.
+     }
+     */
 }

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

@@ -3,387 +3,387 @@ import Contacts
 import UIKit
 
 class NewChatViewController: UITableViewController {
-  weak var coordinator: NewChatCoordinator?
-
-  private lazy var searchController: UISearchController = {
-    let searchController = UISearchController(searchResultsController: nil)
-    searchController.searchResultsUpdater = self
-    searchController.obscuresBackgroundDuringPresentation = false
-    searchController.searchBar.placeholder = "Search Contact"
-    return searchController
-  }()
-
-  var contactIds: [Int] = Utils.getContactIds() {
-    didSet {
-      tableView.reloadData()
+    weak var coordinator: NewChatCoordinator?
+
+    private lazy var searchController: UISearchController = {
+        let searchController = UISearchController(searchResultsController: nil)
+        searchController.searchResultsUpdater = self
+        searchController.obscuresBackgroundDuringPresentation = false
+        searchController.searchBar.placeholder = "Search Contact"
+        return searchController
+    }()
+
+    var contactIds: [Int] = Utils.getContactIds() {
+        didSet {
+            tableView.reloadData()
+        }
     }
-  }
 
-  // contactWithSearchResults.indexesToHightLight empty by default
-  var contacts: [ContactWithSearchResults] {
-    return contactIds.map { ContactWithSearchResults(contact: MRContact(id: $0), indexesToHighlight: []) }
-  }
+    // contactWithSearchResults.indexesToHightLight empty by default
+    var contacts: [ContactWithSearchResults] {
+        return contactIds.map { ContactWithSearchResults(contact: MRContact(id: $0), indexesToHighlight: []) }
+    }
 
-  // used when seachbar is active
-  var filteredContacts: [ContactWithSearchResults] = []
+    // used when seachbar is active
+    var filteredContacts: [ContactWithSearchResults] = []
 
-  // searchBar active?
-  func isFiltering() -> Bool {
-    return searchController.isActive && !searchBarIsEmpty()
-  }
+    // searchBar active?
+    func isFiltering() -> Bool {
+        return searchController.isActive && !searchBarIsEmpty()
+    }
 
-  // weak var chatDisplayer: ChatDisplayer?
+    // weak var chatDisplayer: ChatDisplayer?
 
-  var syncObserver: Any?
-  var hud: ProgressHud?
+    var syncObserver: Any?
+    var hud: ProgressHud?
 
-  lazy var deviceContactHandler: DeviceContactsHandler = {
-    let handler = DeviceContactsHandler()
-    handler.contactListDelegate = self
-    return handler
-  }()
+    lazy var deviceContactHandler: DeviceContactsHandler = {
+        let handler = DeviceContactsHandler()
+        handler.contactListDelegate = self
+        return handler
+    }()
 
-  var deviceContactAccessGranted: Bool = false {
-    didSet {
-      tableView.reloadData()
-    }
-  }
-
-  init() {
-    super.init(style: .grouped)
-    hidesBottomBarWhenPushed = true
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-
-    title = "New Chat"
-
-    deviceContactHandler.importDeviceContacts()
-    navigationItem.searchController = searchController
-    definesPresentationContext = true // to make sure searchbar will only be shown in this viewController
-  }
-
-  override func viewWillAppear(_ animated: Bool) {
-    super.viewWillAppear(animated)
-    deviceContactAccessGranted = CNContactStore.authorizationStatus(for: .contacts) == .authorized
-    contactIds = Utils.getContactIds()
-    // this will show the searchbar on launch -> will be set back to true on viewDidAppear
-    if #available(iOS 11.0, *) {
-      navigationItem.hidesSearchBarWhenScrolling = false
+    var deviceContactAccessGranted: Bool = false {
+        didSet {
+            tableView.reloadData()
+        }
     }
-  }
 
-  override func viewDidAppear(_ animated: Bool) {
-    super.viewDidAppear(animated)
-    if #available(iOS 11.0, *) {
-      navigationItem.hidesSearchBarWhenScrolling = true
+    init() {
+        super.init(style: .grouped)
+        hidesBottomBarWhenPushed = true
     }
 
-    let nc = NotificationCenter.default
-    syncObserver = nc.addObserver(
-      forName: dcNotificationSecureJoinerProgress,
-      object: nil,
-      queue: nil
-    ) {
-      notification in
-      if let ui = notification.userInfo {
-        if ui["error"] as! Bool {
-          self.hud?.error(ui["errorMessage"] as? String)
-        } else if ui["done"] as! Bool {
-          self.hud?.done()
-        } else {
-          self.hud?.progress(ui["progress"] as! Int)
-        }
-      }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
     }
-  }
 
-  override func viewWillDisappear(_: Bool) {
-    title = "Chats" /* hack: when navigating to chatView (removing this viewController), there was a delayed backButton update (showing 'New Chat' for a moment) */
-  }
+    override func viewDidLoad() {
+        super.viewDidLoad()
 
-  override func viewDidDisappear(_ animated: Bool) {
-    super.viewDidDisappear(animated)
+        title = "New Chat"
 
-    let nc = NotificationCenter.default
-    if let syncObserver = self.syncObserver {
-      nc.removeObserver(syncObserver)
+        deviceContactHandler.importDeviceContacts()
+        navigationItem.searchController = searchController
+        definesPresentationContext = true // to make sure searchbar will only be shown in this viewController
     }
-  }
-
-  @objc func cancelButtonPressed() {
-    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 {
-    return deviceContactAccessGranted ? 2 : 3
-  }
-
-  override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-    if section == 0 {
-      return 3
-    } else if section == 1 {
-      if deviceContactAccessGranted {
-        return isFiltering() ? filteredContacts.count : contacts.count
-      } else {
-        return 1
-      }
-    } else {
-      return isFiltering() ? filteredContacts.count : contacts.count
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        deviceContactAccessGranted = CNContactStore.authorizationStatus(for: .contacts) == .authorized
+        contactIds = Utils.getContactIds()
+        // this will show the searchbar on launch -> will be set back to true on viewDidAppear
+        if #available(iOS 11.0, *) {
+            navigationItem.hidesSearchBarWhenScrolling = false
+        }
     }
-  }
-
-  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let section = indexPath.section
-    let row = indexPath.row
-
-    if section == 0 {
-      if row == 0 {
-        // new group row
-        let cell: UITableViewCell
-        if let c = tableView.dequeueReusableCell(withIdentifier: "newContactCell") {
-          cell = c
-        } else {
-          cell = UITableViewCell(style: .default, reuseIdentifier: "newContactCell")
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+        if #available(iOS 11.0, *) {
+            navigationItem.hidesSearchBarWhenScrolling = true
         }
-        cell.textLabel?.text = "New Group"
-        cell.textLabel?.textColor = view.tintColor
-
-        return cell
-      }
-      if row == 1 {
-        // new contact row
-        let cell: UITableViewCell
-        if let c = tableView.dequeueReusableCell(withIdentifier: "scanGroupCell") {
-          cell = c
-        } else {
-          cell = UITableViewCell(style: .default, reuseIdentifier: "scanGroupCell")
+
+        let nc = NotificationCenter.default
+        syncObserver = nc.addObserver(
+            forName: dcNotificationSecureJoinerProgress,
+            object: nil,
+            queue: nil
+        ) {
+            notification in
+            if let ui = notification.userInfo {
+                if ui["error"] as! Bool {
+                    self.hud?.error(ui["errorMessage"] as? String)
+                } else if ui["done"] as! Bool {
+                    self.hud?.done()
+                } else {
+                    self.hud?.progress(ui["progress"] as! Int)
+                }
+            }
         }
-        cell.textLabel?.text = "Scan Group QR Code"
-        cell.textLabel?.textColor = view.tintColor
+    }
+
+    override func viewWillDisappear(_: Bool) {
+        title = "Chats" /* hack: when navigating to chatView (removing this viewController), there was a delayed backButton update (showing 'New Chat' for a moment) */
+    }
 
-        return cell
-      }
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
 
-      if row == 2 {
-        // new contact row
-        let cell: UITableViewCell
-        if let c = tableView.dequeueReusableCell(withIdentifier: "newContactCell") {
-          cell = c
-        } else {
-          cell = UITableViewCell(style: .default, reuseIdentifier: "newContactCell")
+        let nc = NotificationCenter.default
+        if let syncObserver = self.syncObserver {
+            nc.removeObserver(syncObserver)
         }
-        cell.textLabel?.text = "New Contact"
-        cell.textLabel?.textColor = view.tintColor
-
-        return cell
-      }
-    } else if section == 1 {
-      if deviceContactAccessGranted {
-        let cell: ContactCell
-        if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
-          cell = c
+    }
+
+    @objc func cancelButtonPressed() {
+        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 {
+        return deviceContactAccessGranted ? 2 : 3
+    }
+
+    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if section == 0 {
+            return 3
+        } else if section == 1 {
+            if deviceContactAccessGranted {
+                return isFiltering() ? filteredContacts.count : contacts.count
+            } else {
+                return 1
+            }
         } else {
-          cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
+            return isFiltering() ? filteredContacts.count : contacts.count
         }
-        let contact: ContactWithSearchResults = isFiltering() ? filteredContacts[row] : contacts[row]
-        updateContactCell(cell: cell, contactWithHighlight: contact)
-        return cell
-      } else {
-        let cell: ActionCell
-        if let c = tableView.dequeueReusableCell(withIdentifier: "actionCell") as? ActionCell {
-          cell = c
+    }
+
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let section = indexPath.section
+        let row = indexPath.row
+
+        if section == 0 {
+            if row == 0 {
+                // new group row
+                let cell: UITableViewCell
+                if let c = tableView.dequeueReusableCell(withIdentifier: "newContactCell") {
+                    cell = c
+                } else {
+                    cell = UITableViewCell(style: .default, reuseIdentifier: "newContactCell")
+                }
+                cell.textLabel?.text = "New Group"
+                cell.textLabel?.textColor = view.tintColor
+
+                return cell
+            }
+            if row == 1 {
+                // new contact row
+                let cell: UITableViewCell
+                if let c = tableView.dequeueReusableCell(withIdentifier: "scanGroupCell") {
+                    cell = c
+                } else {
+                    cell = UITableViewCell(style: .default, reuseIdentifier: "scanGroupCell")
+                }
+                cell.textLabel?.text = "Scan Group QR Code"
+                cell.textLabel?.textColor = view.tintColor
+
+                return cell
+            }
+
+            if row == 2 {
+                // new contact row
+                let cell: UITableViewCell
+                if let c = tableView.dequeueReusableCell(withIdentifier: "newContactCell") {
+                    cell = c
+                } else {
+                    cell = UITableViewCell(style: .default, reuseIdentifier: "newContactCell")
+                }
+                cell.textLabel?.text = "New Contact"
+                cell.textLabel?.textColor = view.tintColor
+
+                return cell
+            }
+        } else if section == 1 {
+            if deviceContactAccessGranted {
+                let cell: ContactCell
+                if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
+                    cell = c
+                } else {
+                    cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
+                }
+                let contact: ContactWithSearchResults = isFiltering() ? filteredContacts[row] : contacts[row]
+                updateContactCell(cell: cell, contactWithHighlight: contact)
+                return cell
+            } else {
+                let cell: ActionCell
+                if let c = tableView.dequeueReusableCell(withIdentifier: "actionCell") as? ActionCell {
+                    cell = c
+                } else {
+                    cell = ActionCell(style: .default, reuseIdentifier: "actionCell")
+                }
+                cell.actionTitle = "Import Device Contacts"
+                return cell
+            }
         } else {
-          cell = ActionCell(style: .default, reuseIdentifier: "actionCell")
+            // section 2
+            let cell: ContactCell
+            if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
+                cell = c
+            } else {
+                cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
+            }
+
+            let contact: ContactWithSearchResults = isFiltering() ? filteredContacts[row] : contacts[row]
+            updateContactCell(cell: cell, contactWithHighlight: contact)
+            return cell
         }
-        cell.actionTitle = "Import Device Contacts"
-        return cell
-      }
-    } else {
-      // section 2
-      let cell: ContactCell
-      if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
-        cell = c
-      } else {
-        cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
-      }
-
-      let contact: ContactWithSearchResults = isFiltering() ? filteredContacts[row] : contacts[row]
-      updateContactCell(cell: cell, contactWithHighlight: contact)
-      return cell
+        // will actually never get here but compiler not happy
+        return UITableViewCell(style: .default, reuseIdentifier: "cell")
     }
-    // will actually never get here but compiler not happy
-    return UITableViewCell(style: .default, reuseIdentifier: "cell")
-  }
-
-  override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-    let row = indexPath.row
-    let section = indexPath.section
-
-    if section == 0 {
-      if row == 0 {
-        coordinator?.showNewGroupController()
-      }
-      if row == 1 {
-        if UIImagePickerController.isSourceTypeAvailable(.camera) {
-          coordinator?.showQRCodeController()
+
+    override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+        let row = indexPath.row
+        let section = indexPath.section
+
+        if section == 0 {
+            if row == 0 {
+                coordinator?.showNewGroupController()
+            }
+            if row == 1 {
+                if UIImagePickerController.isSourceTypeAvailable(.camera) {
+                    coordinator?.showQRCodeController()
+                } else {
+                    let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
+                    alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+                        self.dismiss(animated: true, completion: nil)
+                    }))
+                    present(alert, animated: true, completion: nil)
+                }
+            }
+            if row == 2 {
+                coordinator?.showNewContactController()
+            }
+        } else if section == 1 {
+            if deviceContactAccessGranted {
+                if searchController.isActive {
+                    // edge case: when searchController is active but searchBar is empty -> filteredContacts is empty, so we fallback to contactIds
+                    let contactId = isFiltering() ? filteredContacts[row].contact.id : contactIds[row]
+                    searchController.dismiss(animated: false, completion: {
+                        self.coordinator?.showNewChat(contactId: contactId)
+                    })
+                } else {
+                    let contactId = contactIds[row]
+                    coordinator?.showNewChat(contactId: contactId)
+                }
+            } else {
+                showSettingsAlert()
+            }
         } else {
-          let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
-          alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-            self.dismiss(animated: true, completion: nil)
-          }))
-          present(alert, animated: true, completion: nil)
+            let contactIndex = row
+            let contactId = contactIds[contactIndex]
+            coordinator?.showNewChat(contactId: contactId)
         }
-      }
-      if row == 2 {
-        coordinator?.showNewContactController()
-      }
-    } else if section == 1 {
-      if deviceContactAccessGranted {
-        if searchController.isActive {
-          // edge case: when searchController is active but searchBar is empty -> filteredContacts is empty, so we fallback to contactIds
-          let contactId = isFiltering() ? filteredContacts[row].contact.id : contactIds[row]
-          searchController.dismiss(animated: false, completion: {
-            self.coordinator?.showNewChat(contactId: contactId)
-          })
+    }
+
+    private func updateContactCell(cell: ContactCell, contactWithHighlight: ContactWithSearchResults) {
+        let contact = contactWithHighlight.contact
+
+        if let nameHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .NAME }).first,
+            let emailHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .EMAIL }).first {
+            // gets here when contact is a result of current search -> highlights relevant indexes
+            let nameLabelFontSize = cell.nameLabel.font.pointSize
+            let emailLabelFontSize = cell.emailLabel.font.pointSize
+
+            cell.nameLabel.attributedText = contact.name.boldAt(indexes: nameHighlightedIndexes.indexes, fontSize: nameLabelFontSize)
+            cell.emailLabel.attributedText = contact.email.boldAt(indexes: emailHighlightedIndexes.indexes, fontSize: emailLabelFontSize)
         } else {
-          let contactId = contactIds[row]
-          coordinator?.showNewChat(contactId: contactId)
+            cell.nameLabel.text = contact.name
+            cell.emailLabel.text = contact.email
         }
-      } else {
-        showSettingsAlert()
-      }
-    } else {
-      let contactIndex = row
-      let contactId = contactIds[contactIndex]
-      coordinator?.showNewChat(contactId: contactId)
+        cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
+        cell.setColor(contact.color)
     }
-  }
-
-  private func updateContactCell(cell: ContactCell, contactWithHighlight: ContactWithSearchResults) {
-    let contact = contactWithHighlight.contact
-
-    if let nameHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .NAME }).first,
-      let emailHighlightedIndexes = contactWithHighlight.indexesToHighlight.filter({ $0.contactDetail == .EMAIL }).first {
-      // gets here when contact is a result of current search -> highlights relevant indexes
-      let nameLabelFontSize = cell.nameLabel.font.pointSize
-      let emailLabelFontSize = cell.emailLabel.font.pointSize
-
-      cell.nameLabel.attributedText = contact.name.boldAt(indexes: nameHighlightedIndexes.indexes, fontSize: nameLabelFontSize)
-      cell.emailLabel.attributedText = contact.email.boldAt(indexes: emailHighlightedIndexes.indexes, fontSize: emailLabelFontSize)
-    } else {
-      cell.nameLabel.text = contact.name
-      cell.emailLabel.text = contact.email
-    }
-    cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-    cell.setColor(contact.color)
-  }
-
-  private func searchBarIsEmpty() -> Bool {
-    return searchController.searchBar.text?.isEmpty ?? true
-  }
-
-  private func filterContentForSearchText(_ searchText: String, scope _: String = "All") {
-    let contactsWithHighlights: [ContactWithSearchResults] = contacts.map { contact in
-      let indexes = contact.contact.contains(searchText: searchText)
-      return ContactWithSearchResults(contact: contact.contact, indexesToHighlight: indexes)
+
+    private func searchBarIsEmpty() -> Bool {
+        return searchController.searchBar.text?.isEmpty ?? true
     }
 
-    filteredContacts = contactsWithHighlights.filter { !$0.indexesToHighlight.isEmpty }
-    tableView.reloadData()
-  }
+    private func filterContentForSearchText(_ searchText: String, scope _: String = "All") {
+        let contactsWithHighlights: [ContactWithSearchResults] = contacts.map { contact in
+            let indexes = contact.contact.contains(searchText: searchText)
+            return ContactWithSearchResults(contact: contact.contact, indexesToHighlight: indexes)
+        }
+
+        filteredContacts = contactsWithHighlights.filter { !$0.indexesToHighlight.isEmpty }
+        tableView.reloadData()
+    }
 }
 
 extension NewChatViewController: QrCodeReaderDelegate {
-  func handleQrCode(_ code: String) {
-    logger.info("decoded: \(code)")
-
-    let check = dc_check_qr(mailboxPointer, code)!
-    logger.info("got ver: \(check)")
-
-    if dc_lot_get_state(check) == DC_QR_ASK_VERIFYGROUP {
-      hud = ProgressHud("Synchronizing Account", in: view)
-      DispatchQueue.global(qos: .userInitiated).async {
-        let id = dc_join_securejoin(mailboxPointer, code)
-
-        DispatchQueue.main.async {
-          self.dismiss(animated: true) {
-            self.coordinator?.showChat(chatId: Int(id))
-            // self.chatDisplayer?.displayChatForId(chatId: Int(id))
-          }
+    func handleQrCode(_ code: String) {
+        logger.info("decoded: \(code)")
+
+        let check = dc_check_qr(mailboxPointer, code)!
+        logger.info("got ver: \(check)")
+
+        if dc_lot_get_state(check) == DC_QR_ASK_VERIFYGROUP {
+            hud = ProgressHud("Synchronizing Account", in: view)
+            DispatchQueue.global(qos: .userInitiated).async {
+                let id = dc_join_securejoin(mailboxPointer, code)
+
+                DispatchQueue.main.async {
+                    self.dismiss(animated: true) {
+                        self.coordinator?.showChat(chatId: Int(id))
+                        // self.chatDisplayer?.displayChatForId(chatId: Int(id))
+                    }
+                }
+            }
+        } else {
+            let alert = UIAlertController(title: "Not a valid group QR Code", message: code, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+                self.dismiss(animated: true, completion: nil)
+            }))
+            present(alert, animated: true, completion: nil)
         }
-      }
-    } else {
-      let alert = UIAlertController(title: "Not a valid group QR Code", message: code, preferredStyle: .alert)
-      alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-        self.dismiss(animated: true, completion: nil)
-      }))
-      present(alert, animated: true, completion: nil)
+        dc_lot_unref(check)
     }
-    dc_lot_unref(check)
-  }
 }
 
 extension NewChatViewController: ContactListDelegate {
-  func deviceContactsImported() {
-    contactIds = Utils.getContactIds()
-    //		tableView.reloadData()
-  }
-
-  func accessGranted() {
-    deviceContactAccessGranted = true
-  }
-
-  func accessDenied() {
-    deviceContactAccessGranted = false
-  }
-
-  private func showSettingsAlert() {
-    let alert = UIAlertController(
-      title: "Import Contacts from to your device",
-      message: "To chat with contacts from your device open the settings menu and enable the Contacts option",
-      preferredStyle: .alert
-    )
-    alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
-      UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
-    })
-    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
-    })
-    present(alert, animated: true)
-  }
+    func deviceContactsImported() {
+        contactIds = Utils.getContactIds()
+        //		tableView.reloadData()
+    }
+
+    func accessGranted() {
+        deviceContactAccessGranted = true
+    }
+
+    func accessDenied() {
+        deviceContactAccessGranted = false
+    }
+
+    private func showSettingsAlert() {
+        let alert = UIAlertController(
+            title: "Import Contacts from to your device",
+            message: "To chat with contacts from your device open the settings menu and enable the Contacts option",
+            preferredStyle: .alert
+        )
+        alert.addAction(UIAlertAction(title: "Open Settings", style: .default) { _ in
+            UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
+        })
+        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel) { _ in
+        })
+        present(alert, animated: true)
+    }
 }
 
 extension NewChatViewController: UISearchResultsUpdating {
-  func updateSearchResults(for searchController: UISearchController) {
-    if let searchText = searchController.searchBar.text {
-      filterContentForSearchText(searchText)
+    func updateSearchResults(for searchController: UISearchController) {
+        if let searchText = searchController.searchBar.text {
+            filterContentForSearchText(searchText)
+        }
     }
-  }
 }
 
 struct ContactHighlights {
-  let contactDetail: ContactDetail
-  let indexes: [Int]
+    let contactDetail: ContactDetail
+    let indexes: [Int]
 }
 
 enum ContactDetail {
-  case NAME
-  case EMAIL
+    case NAME
+    case EMAIL
 }
 
 struct ContactWithSearchResults {
-  let contact: MRContact
-  let indexesToHighlight: [ContactHighlights]
+    let contact: MRContact
+    let indexesToHighlight: [ContactHighlights]
 }

+ 102 - 102
deltachat-ios/Controller/NewContactController.swift

@@ -2,125 +2,125 @@ import UIKit
 
 class NewContactController: UITableViewController {
 
-	weak var coordinator: EditContactCoordinatorProtocol?
-
-  let emailCell = TextFieldCell.makeEmailCell()
-  let nameCell = TextFieldCell.makeNameCell()
-  var doneButton: UIBarButtonItem?
-  var cancelButton: UIBarButtonItem?
-
-  func contactIsValid() -> Bool {
-    return Utils.isValid(model.email)
-  }
-
-  var model: (name: String, email: String) = ("", "") {
-    didSet {
-      if contactIsValid() {
-        doneButton?.isEnabled = true
-      } else {
+    weak var coordinator: EditContactCoordinatorProtocol?
+
+    let emailCell = TextFieldCell.makeEmailCell()
+    let nameCell = TextFieldCell.makeNameCell()
+    var doneButton: UIBarButtonItem?
+    var cancelButton: UIBarButtonItem?
+
+    func contactIsValid() -> Bool {
+        return Utils.isValid(model.email)
+    }
+
+    var model: (name: String, email: String) = ("", "") {
+        didSet {
+            if contactIsValid() {
+                doneButton?.isEnabled = true
+            } else {
+                doneButton?.isEnabled = false
+            }
+        }
+    }
+
+    let cells: [UITableViewCell]
+
+    // for creating a new contact
+    init() {
+        cells = [emailCell, nameCell]
+        super.init(style: .grouped)
+        emailCell.textField.delegate = self
+        nameCell.textField.delegate = self
+
+        // always show return key with name field, because
+        // name is optional
+        nameCell.textField.enablesReturnKeyAutomatically = false
+        emailCell.textField.returnKeyType = .next
+        nameCell.textField.returnKeyType = .done
+
+        title = "New Contact"
+        doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(saveContactButtonPressed))
         doneButton?.isEnabled = false
-      }
+        navigationItem.rightBarButtonItem = doneButton
+
+        cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
+        navigationItem.leftBarButtonItem = cancelButton
+
+        emailCell.textField.addTarget(self, action: #selector(NewContactController.emailTextChanged), for: UIControl.Event.editingChanged)
+        nameCell.textField.addTarget(self, action: #selector(NewContactController.nameTextChanged), for: UIControl.Event.editingChanged)
     }
-  }
-
-  let cells: [UITableViewCell]
-
-  // for creating a new contact
-  init() {
-    cells = [emailCell, nameCell]
-    super.init(style: .grouped)
-    emailCell.textField.delegate = self
-    nameCell.textField.delegate = self
-
-    // always show return key with name field, because
-    // name is optional
-    nameCell.textField.enablesReturnKeyAutomatically = false
-    emailCell.textField.returnKeyType = .next
-    nameCell.textField.returnKeyType = .done
-
-    title = "New Contact"
-    doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(saveContactButtonPressed))
-    doneButton?.isEnabled = false
-    navigationItem.rightBarButtonItem = doneButton
-
-    cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
-    navigationItem.leftBarButtonItem = cancelButton
-
-    emailCell.textField.addTarget(self, action: #selector(NewContactController.emailTextChanged), for: UIControl.Event.editingChanged)
-    nameCell.textField.addTarget(self, action: #selector(NewContactController.nameTextChanged), for: UIControl.Event.editingChanged)
-  }
-
-  override func viewDidAppear(_: Bool) {
-    if emailCell.textField.isEnabled {
-      emailCell.textField.becomeFirstResponder()
-    } else {
-      nameCell.textField.becomeFirstResponder()
+
+    override func viewDidAppear(_: Bool) {
+        if emailCell.textField.isEnabled {
+            emailCell.textField.becomeFirstResponder()
+        } else {
+            nameCell.textField.becomeFirstResponder()
+        }
     }
-  }
 
-  override func viewWillAppear(_: Bool) {
-    navigationController?.setNavigationBarHidden(false, animated: false)
-    navigationController?.navigationBar.prefersLargeTitles = false
-  }
+    override func viewWillAppear(_: Bool) {
+        navigationController?.setNavigationBarHidden(false, animated: false)
+        navigationController?.navigationBar.prefersLargeTitles = false
+    }
 
-  @objc func emailTextChanged() {
-    let emailText = emailCell.textField.text ?? ""
-    model.email = emailText
-  }
+    @objc func emailTextChanged() {
+        let emailText = emailCell.textField.text ?? ""
+        model.email = emailText
+    }
 
-  @objc func nameTextChanged() {
-    let nameText = nameCell.textField.text ?? ""
-    model.name = nameText
-  }
+    @objc func nameTextChanged() {
+        let nameText = nameCell.textField.text ?? ""
+        model.name = nameText
+    }
 
-  @objc func saveContactButtonPressed() {
-		let contactId = dc_create_contact(mailboxPointer, model.name, model.email)
-		let chatId = Int(dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId)))
-		coordinator?.showChat(chatId: chatId)
-	}
+    @objc func saveContactButtonPressed() {
+        let contactId = dc_create_contact(mailboxPointer, model.name, model.email)
+        let chatId = Int(dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId)))
+        coordinator?.showChat(chatId: chatId)
+    }
 
-  @objc func cancelButtonPressed() {
-		coordinator?.navigateBack()
-  }
+    @objc func cancelButtonPressed() {
+        coordinator?.navigateBack()
+    }
 
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-  override func viewDidLoad() {
-    super.viewDidLoad()
+    override func viewDidLoad() {
+        super.viewDidLoad()
 
-    navigationController?.navigationBar.prefersLargeTitles = true
-  }
+        navigationController?.navigationBar.prefersLargeTitles = true
+    }
 
-  override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-    return cells.count
-  }
+    override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+        return cells.count
+    }
 
-  override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let row = indexPath.row
+    override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let row = indexPath.row
 
-    return cells[row]
-  }
+        return cells[row]
+    }
 
-  override func didReceiveMemoryWarning() {
-    super.didReceiveMemoryWarning()
-    // Dispose of any resources that can be recreated.
-  }
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+        // Dispose of any resources that can be recreated.
+    }
 }
 
 extension NewContactController: UITextFieldDelegate {
-  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
-    if textField == emailCell.textField {
-      // only switch to next line if email is valid
-      if contactIsValid() {
-        nameCell.textField.becomeFirstResponder()
-      }
-    } else if textField == nameCell.textField {
-      if contactIsValid() {
-        saveContactButtonPressed()
-      }
+    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+        if textField == emailCell.textField {
+            // only switch to next line if email is valid
+            if contactIsValid() {
+                nameCell.textField.becomeFirstResponder()
+            }
+        } else if textField == nameCell.textField {
+            if contactIsValid() {
+                saveContactButtonPressed()
+            }
+        }
+        return true
     }
-    return true
-  }
 }

+ 14 - 14
deltachat-ios/Controller/PreviewController.swift

@@ -2,21 +2,21 @@ import QuickLook
 import UIKit
 
 class PreviewController: QLPreviewControllerDataSource {
-	var urls: [URL]
-	var qlController: QLPreviewController
+    var urls: [URL]
+    var qlController: QLPreviewController
 
-	init(currentIndex: Int, urls: [URL]) {
-		self.urls = urls
-		qlController = QLPreviewController()
-		qlController.dataSource = self
-		qlController.currentPreviewItemIndex = currentIndex
-	}
+    init(currentIndex: Int, urls: [URL]) {
+        self.urls = urls
+        qlController = QLPreviewController()
+        qlController.dataSource = self
+        qlController.currentPreviewItemIndex = currentIndex
+    }
 
-	func numberOfPreviewItems(in _: QLPreviewController) -> Int {
-		return urls.count
-	}
+    func numberOfPreviewItems(in _: QLPreviewController) -> Int {
+        return urls.count
+    }
 
-	func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
-		return urls[index] as QLPreviewItem
-	}
+    func previewController(_: QLPreviewController, previewItemAt index: Int) -> QLPreviewItem {
+        return urls[index] as QLPreviewItem
+    }
 }

+ 121 - 121
deltachat-ios/Controller/ProfileViewController.swift

@@ -1,158 +1,158 @@
 import UIKit
 
 class ProfileViewController: UITableViewController {
-  weak var coordinator: ProfileCoordinator?
+    weak var coordinator: ProfileCoordinator?
 
-  var contact: MRContact? {
-    // This is nil if we do not have an account setup yet
-    if !MRConfig.configured {
-      return nil
-    }
-    return MRContact(id: Int(DC_CONTACT_ID_SELF))
-  }
-
-  var fingerprint: String? {
-    if !MRConfig.configured {
-      return nil
+    var contact: MRContact? {
+        // This is nil if we do not have an account setup yet
+        if !MRConfig.configured {
+            return nil
+        }
+        return MRContact(id: Int(DC_CONTACT_ID_SELF))
     }
 
-    if let cString = dc_get_securejoin_qr(mailboxPointer, 0) {
-      return String(cString: cString)
-    }
+    var fingerprint: String? {
+        if !MRConfig.configured {
+            return nil
+        }
 
-    return nil
-  }
+        if let cString = dc_get_securejoin_qr(mailboxPointer, 0) {
+            return String(cString: cString)
+        }
 
-  init() {
-    super.init(style: .plain)
-  }
+        return nil
+    }
 
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
+    init() {
+        super.init(style: .plain)
+    }
 
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    title = "My Profile"
-  }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-  override func viewWillAppear(_: Bool) {
-   navigationController?.navigationBar.prefersLargeTitles = false
-    tableView.reloadData()
-  }
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "My Profile"
+    }
 
-  func displayNewChat(contactId: Int) {
-    let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
-    let chatVC = ChatViewController(chatId: Int(chatId))
+    override func viewWillAppear(_: Bool) {
+        navigationController?.navigationBar.prefersLargeTitles = false
+        tableView.reloadData()
+    }
 
-    chatVC.hidesBottomBarWhenPushed = true
-    navigationController?.pushViewController(chatVC, animated: true)
-  }
+    func displayNewChat(contactId: Int) {
+        let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
+        let chatVC = ChatViewController(chatId: Int(chatId))
 
-  override func didReceiveMemoryWarning() {
-    super.didReceiveMemoryWarning()
-    // Dispose of any resources that can be recreated.
-  }
+        chatVC.hidesBottomBarWhenPushed = true
+        navigationController?.pushViewController(chatVC, animated: true)
+    }
 
-  // MARK: - Table view data source
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+        // Dispose of any resources that can be recreated.
+    }
 
-  override func numberOfSections(in _: UITableView) -> Int {
-    return 2
-  }
+    // MARK: - Table view data source
 
-  override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-    if section == 0 {
-      return 2
+    override func numberOfSections(in _: UITableView) -> Int {
+        return 2
     }
 
-    return 0
-  }
+    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+        if section == 0 {
+            return 2
+        }
 
-  override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let row = indexPath.row
+        return 0
+    }
 
-    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
-    if indexPath.section == 0 {
-      if row == 0 {
-        if let fingerprint = self.fingerprint {
-          cell.textLabel?.text = "Fingerprint: \(fingerprint)"
-          cell.textLabel?.textAlignment = .center
+    override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let row = indexPath.row
+
+        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
+        if indexPath.section == 0 {
+            if row == 0 {
+                if let fingerprint = self.fingerprint {
+                    cell.textLabel?.text = "Fingerprint: \(fingerprint)"
+                    cell.textLabel?.textAlignment = .center
+                }
+            }
+            if row == 1 {
+                if let fingerprint = self.fingerprint {
+                    let width: CGFloat = 130
+
+                    let frame = CGRect(origin: .zero, size: .init(width: width, height: width))
+                    let imageView = QRCodeView(frame: frame)
+                    imageView.generateCode(
+                        fingerprint,
+                        foregroundColor: .darkText,
+                        backgroundColor: .white
+                    )
+                    imageView.translatesAutoresizingMaskIntoConstraints = false
+                    // imageView.center = cell.center
+                    cell.addSubview(imageView)
+
+                    imageView.centerXAnchor.constraint(equalTo: cell.centerXAnchor).isActive = true
+                    imageView.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
+                    imageView.widthAnchor.constraint(equalToConstant: width).isActive = true
+                    imageView.heightAnchor.constraint(equalToConstant: width).isActive = true
+                }
+            }
         }
-      }
-      if row == 1 {
-        if let fingerprint = self.fingerprint {
-          let width: CGFloat = 130
-
-          let frame = CGRect(origin: .zero, size: .init(width: width, height: width))
-          let imageView = QRCodeView(frame: frame)
-          imageView.generateCode(
-            fingerprint,
-            foregroundColor: .darkText,
-            backgroundColor: .white
-          )
-          imageView.translatesAutoresizingMaskIntoConstraints = false
-          // imageView.center = cell.center
-          cell.addSubview(imageView)
-
-          imageView.centerXAnchor.constraint(equalTo: cell.centerXAnchor).isActive = true
-          imageView.centerYAnchor.constraint(equalTo: cell.centerYAnchor).isActive = true
-          imageView.widthAnchor.constraint(equalToConstant: width).isActive = true
-          imageView.heightAnchor.constraint(equalToConstant: width).isActive = true
+
+        if indexPath.section == 1 {
+            if row == 0 {}
         }
-      }
-    }
 
-    if indexPath.section == 1 {
-      if row == 0 {}
+        return cell
     }
 
-    return cell
-  }
+    override func tableView(_: UITableView, didSelectRowAt _: IndexPath) {}
 
-  override func tableView(_: UITableView, didSelectRowAt _: IndexPath) {}
+    override func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        if section == 0 {
+            return 80
+        }
 
-  override func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
-    if section == 0 {
-      return 80
+        return 20
     }
 
-    return 20
-  }
+    override func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        if indexPath.row == 1 {
+            return 150
+        }
 
-  override func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
-    if indexPath.row == 1 {
-      return 150
+        return 46
     }
 
-    return 46
-  }
-
-  override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
-    let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
-    if section == 0 {
-      let contactCell = ContactCell()
-      if let contact = self.contact {
-        let name = MRConfig.displayname ?? contact.name
-        contactCell.backgroundColor = bg
-        contactCell.nameLabel.text = name
-        contactCell.emailLabel.text = contact.email
-        contactCell.darkMode = false
-        contactCell.selectionStyle = .none
-        if let img = contact.profileImage {
-          contactCell.setImage(img)
-        } else {
-          contactCell.setBackupImage(name: name, color: contact.color)
+    override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+        let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
+        if section == 0 {
+            let contactCell = ContactCell()
+            if let contact = self.contact {
+                let name = MRConfig.displayname ?? contact.name
+                contactCell.backgroundColor = bg
+                contactCell.nameLabel.text = name
+                contactCell.emailLabel.text = contact.email
+                contactCell.darkMode = false
+                contactCell.selectionStyle = .none
+                if let img = contact.profileImage {
+                    contactCell.setImage(img)
+                } else {
+                    contactCell.setBackupImage(name: name, color: contact.color)
+                }
+                contactCell.setVerified(isVerified: contact.isVerified)
+            } else {
+                contactCell.nameLabel.text = "No Account set up"
+            }
+            return contactCell
         }
-        contactCell.setVerified(isVerified: contact.isVerified)
-      } else {
-        contactCell.nameLabel.text = "No Account set up"
-      }
-      return contactCell
-    }
 
-    let vw = UIView()
-    vw.backgroundColor = bg
+        let vw = UIView()
+        vw.backgroundColor = bg
 
-    return vw
-  }
+        return vw
+    }
 }

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

@@ -2,90 +2,90 @@ import AVFoundation
 import UIKit
 
 class QrCodeReaderController: UIViewController {
-  var captureSession = AVCaptureSession()
+    var captureSession = AVCaptureSession()
 
-  var videoPreviewLayer: AVCaptureVideoPreviewLayer?
-  var qrCodeFrameView: UIView?
+    var videoPreviewLayer: AVCaptureVideoPreviewLayer?
+    var qrCodeFrameView: UIView?
 
-  weak var delegate: QrCodeReaderDelegate?
+    weak var delegate: QrCodeReaderDelegate?
 
-  private let supportedCodeTypes = [
-    AVMetadataObject.ObjectType.qr,
-  ]
+    private let supportedCodeTypes = [
+        AVMetadataObject.ObjectType.qr,
+    ]
 
-  override func viewDidLoad() {
-    super.viewDidLoad()
+    override func viewDidLoad() {
+        super.viewDidLoad()
 
-    let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(
-      deviceTypes: [.builtInDualCamera],
-      mediaType: AVMediaType.video,
-      position: .back
-    )
+        let deviceDiscoverySession = AVCaptureDevice.DiscoverySession(
+            deviceTypes: [.builtInDualCamera],
+            mediaType: AVMediaType.video,
+            position: .back
+        )
 
-    guard let captureDevice = deviceDiscoverySession.devices.first else {
-      print("Failed to get the camera device")
-      return
-    }
+        guard let captureDevice = deviceDiscoverySession.devices.first else {
+            print("Failed to get the camera device")
+            return
+        }
 
-    do {
-      let input = try AVCaptureDeviceInput(device: captureDevice)
-      captureSession.addInput(input)
+        do {
+            let input = try AVCaptureDeviceInput(device: captureDevice)
+            captureSession.addInput(input)
 
-      let captureMetadataOutput = AVCaptureMetadataOutput()
-      captureSession.addOutput(captureMetadataOutput)
+            let captureMetadataOutput = AVCaptureMetadataOutput()
+            captureSession.addOutput(captureMetadataOutput)
 
-      captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
-      captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
-    } catch {
-      // If any error occurs, simply print it out and don't continue any more.
-      logger.error("failed to setup QR Code Scanner: \(error)")
-      return
-    }
+            captureMetadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
+            captureMetadataOutput.metadataObjectTypes = supportedCodeTypes
+        } catch {
+            // If any error occurs, simply print it out and don't continue any more.
+            logger.error("failed to setup QR Code Scanner: \(error)")
+            return
+        }
 
-    videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
-    videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
-    videoPreviewLayer?.frame = view.layer.bounds
-    view.layer.addSublayer(videoPreviewLayer!)
+        videoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
+        videoPreviewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
+        videoPreviewLayer?.frame = view.layer.bounds
+        view.layer.addSublayer(videoPreviewLayer!)
 
-    captureSession.startRunning()
+        captureSession.startRunning()
 
-    qrCodeFrameView = UIView()
+        qrCodeFrameView = UIView()
 
-    if let qrCodeFrameView = qrCodeFrameView {
-      qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
-      qrCodeFrameView.layer.borderWidth = 2
-      view.addSubview(qrCodeFrameView)
-      view.bringSubviewToFront(qrCodeFrameView)
+        if let qrCodeFrameView = qrCodeFrameView {
+            qrCodeFrameView.layer.borderColor = UIColor.green.cgColor
+            qrCodeFrameView.layer.borderWidth = 2
+            view.addSubview(qrCodeFrameView)
+            view.bringSubviewToFront(qrCodeFrameView)
+        }
     }
-  }
 
-  override func didReceiveMemoryWarning() {
-    super.didReceiveMemoryWarning()
-    // Dispose of any resources that can be recreated.
-  }
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+        // Dispose of any resources that can be recreated.
+    }
 }
 
 extension QrCodeReaderController: AVCaptureMetadataOutputObjectsDelegate {
-  func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
-    if metadataObjects.isEmpty {
-      qrCodeFrameView?.frame = CGRect.zero
-      return
-    }
+    func metadataOutput(_: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from _: AVCaptureConnection) {
+        if metadataObjects.isEmpty {
+            qrCodeFrameView?.frame = CGRect.zero
+            return
+        }
 
-    let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
+        let metadataObj = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
 
-    if supportedCodeTypes.contains(metadataObj.type) {
-      let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
-      qrCodeFrameView?.frame = barCodeObject!.bounds
+        if supportedCodeTypes.contains(metadataObj.type) {
+            let barCodeObject = videoPreviewLayer?.transformedMetadataObject(for: metadataObj)
+            qrCodeFrameView?.frame = barCodeObject!.bounds
 
-      if metadataObj.stringValue != nil {
-        DispatchQueue.main.async {
-          self.captureSession.stopRunning()
-          self.dismiss(animated: true) {
-            self.delegate?.handleQrCode(metadataObj.stringValue!)
-          }
+            if metadataObj.stringValue != nil {
+                DispatchQueue.main.async {
+                    self.captureSession.stopRunning()
+                    self.dismiss(animated: true) {
+                        self.delegate?.handleQrCode(metadataObj.stringValue!)
+                    }
+                }
+            }
         }
-      }
     }
-  }
 }

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

@@ -3,226 +3,226 @@ import QuickTableViewController
 import UIKit
 
 internal final class SettingsViewController: QuickTableViewController {
-	weak var coordinator: SettingsCoordinator?
-
-	let documentInteractionController = UIDocumentInteractionController()
-	var backupProgressObserver: Any?
-	var configureProgressObserver: Any?
-
-	private lazy var hudHandler: HudHandler = {
-		let hudHandler = HudHandler(parentView: self.view)
-		return hudHandler
-	}()
-
-	override func viewDidLoad() {
-		super.viewDidLoad()
-		title = "Settings"
-		documentInteractionController.delegate = self as? UIDocumentInteractionControllerDelegate
-	}
-
-	override func viewDidAppear(_ animated: Bool) {
-
-		super.viewDidAppear(animated)
-		let nc = NotificationCenter.default
-		backupProgressObserver = nc.addObserver(
-			forName: dcNotificationBackupProgress,
-			object: nil,
-			queue: nil
-		) { notification in
-			if let ui = notification.userInfo {
-				if ui["error"] as! Bool {
-					self.hudHandler.setHudError(ui["errorMessage"] as? String)
-				} else if ui["done"] as! Bool {
-					self.hudHandler.setHudDone(callback: nil)
-				} else {
-					self.hudHandler.setHudProgress(ui["progress"] as! Int)
-				}
-			}
-		}
-		configureProgressObserver = nc.addObserver(
-			forName: dcNotificationConfigureProgress,
-			object: nil,
-			queue: nil
-		) { notification in
-			if let ui = notification.userInfo {
-				if ui["error"] as! Bool {
-					self.hudHandler.setHudError(ui["errorMessage"] as? String)
-				} else if ui["done"] as! Bool {
-					self.hudHandler.setHudDone(callback: nil)
-				} else {
-					self.hudHandler.setHudProgress(ui["progress"] as! Int)
-				}
-			}
-		}
-	}
-
-	override func viewWillAppear(_ animated: Bool) {
-		super.viewWillAppear(animated)
-		setTable()
-		if #available(iOS 11.0, *) {
-			navigationController?.navigationBar.prefersLargeTitles = true
-		}
-	}
-
-	override func viewWillDisappear(_ animated: Bool) {
-		super.viewWillDisappear(animated)
-		if #available(iOS 11.0, *) {
-			navigationController?.navigationBar.prefersLargeTitles = false
-		}
-	}
-
-	override func viewDidDisappear(_ animated: Bool) {
-		super.viewDidDisappear(animated)
-
-		let nc = NotificationCenter.default
-		if let backupProgressObserver = self.backupProgressObserver {
-			nc.removeObserver(backupProgressObserver)
-		}
-		if let configureProgressObserver = self.configureProgressObserver {
-			nc.removeObserver(configureProgressObserver)
-		}
-	}
-
-	private func setTable() {
-		var backupRows = [
-			TapActionRow(text: "Create backup", action: { [weak self] in self?.createBackup($0) }),
-		]
-
-		let deleteRow = TapActionRow(text: "Delete Account", action: { [weak self] in self?.deleteAccount($0) })
-
-		tableContents = [
-			Section(
-				title: "User Details",
-				rows: [
-					NavigationRow(text: "Display Name", detailText: .value1(MRConfig.displayname ?? ""), action: { [weak self] in self?.editNameAndStatus($0)}),
-					NavigationRow(text: "Status", detailText: .value1(MRConfig.selfstatus ?? ""), action: { [weak self] in self?.editNameAndStatus($0)}),
-					TapActionRow(text: "Configure my Account", action: { [weak self] in self?.presentAccountSetup($0) }),
-				]
-			),
-			Section(
-				title: "Flags",
-				rows: [
-					SwitchRow(text: "E2EE enabled", switchValue: MRConfig.e2eeEnabled, action: editCell()),
-					SwitchRow(text: "Read Receipts", switchValue: MRConfig.mdnsEnabled, action: editCell()),
-					SwitchRow(text: "Watch Inbox", switchValue: MRConfig.inboxWatch, action: editCell()),
-					SwitchRow(text: "Watch Sentbox", switchValue: MRConfig.sentboxWatch, action: editCell()),
-					SwitchRow(text: "Watch Mvbox", switchValue: MRConfig.mvboxWatch, action: editCell()),
-					SwitchRow(text: "Move to Mvbox", switchValue: MRConfig.mvboxMove, action: editCell()),
-					SwitchRow(text: "Save Mime Headers", switchValue: MRConfig.saveMimeHeaders, action: editCell()),
-				]
-			),
-
-			Section(
-				title: "Backup",
-				rows: backupRows
-			),
-
-			Section(title: "Danger", rows: [
-				deleteRow,
-				]),
-		]
-	}
-
-	// FIXME: simplify this method
-	// swiftlint:disable cyclomatic_complexity
-	private func editCell() -> (Row) -> Void {
-		return { [weak self] sender in
-			logger.info("row edit", sender.text)
-
-			let title = sender.text
-			let subtitle: String = sender.detailText?.text ?? ""
-
-
-			if let sender = sender as? SwitchRow {
-				logger.info("got bool switch")
-				let value = sender.switchValue
-
-				switch title {
-				case "E2EE enabled":
-					MRConfig.e2eeEnabled = value
-				case "Read Receipts":
-					MRConfig.mdnsEnabled = value
-				case "Watch Inbox":
-					MRConfig.inboxWatch = value
-				case "Watch Sentbox":
-					MRConfig.sentboxWatch = value
-				case "Watch Mvbox":
-					MRConfig.mvboxWatch = value
-				case "Move to Mvbox":
-					MRConfig.mvboxMove = value
-				case "Save Mime Headers":
-					MRConfig.saveMimeHeaders = value
-				default:
-					logger.info("unknown title", title)
-				}
-				return
-			}
-		}
-	}
-
-	private func createBackup(_: Row) {
-		// if let documents = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.delta.chat.ios")?.path {
-
-		let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
-		if !documents.isEmpty {
-			logger.info("create backup in \(documents)")
-			hudHandler.showBackupHud("Creating Backup")
-			DispatchQueue.main.async {
-				dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
-			}
-		} else {
-			logger.error("document directory not found")
-		}
-	}
-
-	private func configure(_: Row) {
-		hudHandler.showBackupHud("Configuring account")
-		dc_configure(mailboxPointer)
-	}
-
-	private func deleteAccount(_: Row) {
-		logger.info("deleting account")
-		guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
-			return
-		}
-
-		let dbfile = appDelegate.dbfile()
-		let dburl = URL(fileURLWithPath: dbfile, isDirectory: false)
-		let alert = UIAlertController(title: "Delete Account", message: "Are you sure you wante to delete your account data?", preferredStyle: .actionSheet)
-
-		alert.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { _ in
-			appDelegate.stop()
-			appDelegate.close()
-			do {
-				try FileManager.default.removeItem(at: dburl)
-			} catch {
-				logger.error("failed to delete db: \(error)")
-			}
-
-			appDelegate.open()
-			appDelegate.start()
-
-			// refresh our view
-			self.setTable()
-			self.tableView.reloadData()
-			self.dismiss(animated: false, completion: nil)
-			self.coordinator?.showLoginController()
-		}))
-		alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
-		present(alert, animated: true, completion: nil)
-	}
-
-	private func presentAccountSetup(_: Row) {
-		coordinator?.showAccountSetupController()
-	}
-
-	private func editNameAndStatus(_ row: Row) {
-		guard let option = SettingsEditOption(rawValue: row.text) else { return }
-		coordinator?.showEditSettingsController(option: option)
-	}
+    weak var coordinator: SettingsCoordinator?
+
+    let documentInteractionController = UIDocumentInteractionController()
+    var backupProgressObserver: Any?
+    var configureProgressObserver: Any?
+
+    private lazy var hudHandler: HudHandler = {
+        let hudHandler = HudHandler(parentView: self.view)
+        return hudHandler
+    }()
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Settings"
+        documentInteractionController.delegate = self as? UIDocumentInteractionControllerDelegate
+    }
+
+    override func viewDidAppear(_ animated: Bool) {
+
+        super.viewDidAppear(animated)
+        let nc = NotificationCenter.default
+        backupProgressObserver = nc.addObserver(
+            forName: dcNotificationBackupProgress,
+            object: nil,
+            queue: nil
+        ) { notification in
+            if let ui = notification.userInfo {
+                if ui["error"] as! Bool {
+                    self.hudHandler.setHudError(ui["errorMessage"] as? String)
+                } else if ui["done"] as! Bool {
+                    self.hudHandler.setHudDone(callback: nil)
+                } else {
+                    self.hudHandler.setHudProgress(ui["progress"] as! Int)
+                }
+            }
+        }
+        configureProgressObserver = nc.addObserver(
+            forName: dcNotificationConfigureProgress,
+            object: nil,
+            queue: nil
+        ) { notification in
+            if let ui = notification.userInfo {
+                if ui["error"] as! Bool {
+                    self.hudHandler.setHudError(ui["errorMessage"] as? String)
+                } else if ui["done"] as! Bool {
+                    self.hudHandler.setHudDone(callback: nil)
+                } else {
+                    self.hudHandler.setHudProgress(ui["progress"] as! Int)
+                }
+            }
+        }
+    }
+
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        setTable()
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = true
+        }
+    }
+
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+    }
+
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
+
+        let nc = NotificationCenter.default
+        if let backupProgressObserver = self.backupProgressObserver {
+            nc.removeObserver(backupProgressObserver)
+        }
+        if let configureProgressObserver = self.configureProgressObserver {
+            nc.removeObserver(configureProgressObserver)
+        }
+    }
+
+    private func setTable() {
+        var backupRows = [
+            TapActionRow(text: "Create backup", action: { [weak self] in self?.createBackup($0) }),
+        ]
+
+        let deleteRow = TapActionRow(text: "Delete Account", action: { [weak self] in self?.deleteAccount($0) })
+
+        tableContents = [
+            Section(
+                title: "User Details",
+                rows: [
+                    NavigationRow(text: "Display Name", detailText: .value1(MRConfig.displayname ?? ""), action: { [weak self] in self?.editNameAndStatus($0)}),
+                    NavigationRow(text: "Status", detailText: .value1(MRConfig.selfstatus ?? ""), action: { [weak self] in self?.editNameAndStatus($0)}),
+                    TapActionRow(text: "Configure my Account", action: { [weak self] in self?.presentAccountSetup($0) }),
+                ]
+            ),
+            Section(
+                title: "Flags",
+                rows: [
+                    SwitchRow(text: "E2EE enabled", switchValue: MRConfig.e2eeEnabled, action: editCell()),
+                    SwitchRow(text: "Read Receipts", switchValue: MRConfig.mdnsEnabled, action: editCell()),
+                    SwitchRow(text: "Watch Inbox", switchValue: MRConfig.inboxWatch, action: editCell()),
+                    SwitchRow(text: "Watch Sentbox", switchValue: MRConfig.sentboxWatch, action: editCell()),
+                    SwitchRow(text: "Watch Mvbox", switchValue: MRConfig.mvboxWatch, action: editCell()),
+                    SwitchRow(text: "Move to Mvbox", switchValue: MRConfig.mvboxMove, action: editCell()),
+                    SwitchRow(text: "Save Mime Headers", switchValue: MRConfig.saveMimeHeaders, action: editCell()),
+                ]
+            ),
+
+            Section(
+                title: "Backup",
+                rows: backupRows
+            ),
+
+            Section(title: "Danger", rows: [
+                deleteRow,
+            ]),
+        ]
+    }
+
+    // FIXME: simplify this method
+    // swiftlint:disable cyclomatic_complexity
+    private func editCell() -> (Row) -> Void {
+        return { [weak self] sender in
+            logger.info("row edit", sender.text)
+
+            let title = sender.text
+            let subtitle: String = sender.detailText?.text ?? ""
+
+
+            if let sender = sender as? SwitchRow {
+                logger.info("got bool switch")
+                let value = sender.switchValue
+
+                switch title {
+                case "E2EE enabled":
+                    MRConfig.e2eeEnabled = value
+                case "Read Receipts":
+                    MRConfig.mdnsEnabled = value
+                case "Watch Inbox":
+                    MRConfig.inboxWatch = value
+                case "Watch Sentbox":
+                    MRConfig.sentboxWatch = value
+                case "Watch Mvbox":
+                    MRConfig.mvboxWatch = value
+                case "Move to Mvbox":
+                    MRConfig.mvboxMove = value
+                case "Save Mime Headers":
+                    MRConfig.saveMimeHeaders = value
+                default:
+                    logger.info("unknown title", title)
+                }
+                return
+            }
+        }
+    }
+
+    private func createBackup(_: Row) {
+        // if let documents = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.delta.chat.ios")?.path {
+
+        let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
+        if !documents.isEmpty {
+            logger.info("create backup in \(documents)")
+            hudHandler.showBackupHud("Creating Backup")
+            DispatchQueue.main.async {
+                dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
+            }
+        } else {
+            logger.error("document directory not found")
+        }
+    }
+
+    private func configure(_: Row) {
+        hudHandler.showBackupHud("Configuring account")
+        dc_configure(mailboxPointer)
+    }
+
+    private func deleteAccount(_: Row) {
+        logger.info("deleting account")
+        guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
+            return
+        }
+
+        let dbfile = appDelegate.dbfile()
+        let dburl = URL(fileURLWithPath: dbfile, isDirectory: false)
+        let alert = UIAlertController(title: "Delete Account", message: "Are you sure you wante to delete your account data?", preferredStyle: .actionSheet)
+
+        alert.addAction(UIAlertAction(title: "Delete", style: .destructive, handler: { _ in
+            appDelegate.stop()
+            appDelegate.close()
+            do {
+                try FileManager.default.removeItem(at: dburl)
+            } catch {
+                logger.error("failed to delete db: \(error)")
+            }
+
+            appDelegate.open()
+            appDelegate.start()
+
+            // refresh our view
+            self.setTable()
+            self.tableView.reloadData()
+            self.dismiss(animated: false, completion: nil)
+            self.coordinator?.showLoginController()
+        }))
+        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+        present(alert, animated: true, completion: nil)
+    }
+
+    private func presentAccountSetup(_: Row) {
+        coordinator?.showAccountSetupController()
+    }
+
+    private func editNameAndStatus(_ row: Row) {
+        guard let option = SettingsEditOption(rawValue: row.text) else { return }
+        coordinator?.showEditSettingsController(option: option)
+    }
 }
 
 enum SettingsEditOption: String {
-	case DISPLAYNAME = "Display Name"
-	case STATUS = "Status"
+    case DISPLAYNAME = "Display Name"
+    case STATUS = "Status"
 }

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

@@ -4,626 +4,626 @@ import Photos
 import MobileCoreServices
 
 class AppCoordinator: NSObject, Coordinator {
-	private let window: UIWindow
-
-	var rootViewController: UIViewController {
-		return tabBarController
-	}
-
-	private var childCoordinators: [Coordinator] = []
-
-	private lazy var tabBarController: UITabBarController = {
-		let tabBarController = UITabBarController()
-		tabBarController.viewControllers = [contactListController, mailboxController, profileController, chatListController, settingsController]
-		// put viewControllers here
-		tabBarController.delegate = self
-		tabBarController.tabBar.tintColor = DCColors.primary
-		return tabBarController
-	}()
-
-	// MARK: viewControllers
-
-	private lazy var contactListController: UIViewController = {
-		let controller = ContactListController()
-		let nav = DCNavigationController(rootViewController: controller)
-		let settingsImage = UIImage(named: "contacts")
-		nav.tabBarItem = UITabBarItem(title: "Contacts", image: settingsImage, tag: 0)
-		let coordinator = ContactListCoordinator(navigationController: nav)
-		self.childCoordinators.append(coordinator)
-		controller.coordinator = coordinator
-		return nav
-	}()
-
-	private lazy var mailboxController: UIViewController = {
-		let controller = MailboxViewController(chatId: Int(DC_CHAT_ID_DEADDROP), title: "Mailbox")
-		controller.disableWriting = true
-		let nav = DCNavigationController(rootViewController: controller)
-		let settingsImage = UIImage(named: "message")
-		nav.tabBarItem = UITabBarItem(title: "Mailbox", image: settingsImage, tag: 1)
-		let coordinator = MailboxCoordinator(navigationController: nav)
-		self.childCoordinators.append(coordinator)
-		controller.coordinator = coordinator
-		return nav
-	}()
-
-	private lazy var profileController: UIViewController = {
-		let controller = ProfileViewController()
-		let nav = DCNavigationController(rootViewController: controller)
-		let settingsImage = UIImage(named: "report_card")
-		nav.tabBarItem = UITabBarItem(title: "My Profile", image: settingsImage, tag: 2)
-		let coordinator = ProfileCoordinator(rootViewController: nav)
-		self.childCoordinators.append(coordinator)
-		controller.coordinator = coordinator
-		return nav
-	}()
-
-	private lazy var chatListController: UIViewController = {
-		let controller = ChatListController()
-		let nav = DCNavigationController(rootViewController: controller)
-		let settingsImage = UIImage(named: "chat")
-		nav.tabBarItem = UITabBarItem(title: "Chats", image: settingsImage, tag: 3)
-		let coordinator = ChatListCoordinator(navigationController: nav)
-		self.childCoordinators.append(coordinator)
-		controller.coordinator = coordinator
-		return nav
-	}()
-
-	private lazy var settingsController: UIViewController = {
-		let controller = SettingsViewController()
-		let nav = DCNavigationController(rootViewController: controller)
-		let settingsImage = UIImage(named: "settings")
-		nav.tabBarItem = UITabBarItem(title: "Settings", image: settingsImage, tag: 4)
-		let coordinator = SettingsCoordinator(navigationController: nav)
-		self.childCoordinators.append(coordinator)
-		controller.coordinator = coordinator
-		return nav
-	}()
-
-	init(window: UIWindow) {
-		self.window = window
-		super.init()
-		window.rootViewController = rootViewController
-		window.makeKeyAndVisible()
-	}
-
-	public func start() {
-	print(tabBarController.selectedIndex)
-		showTab(index: 3)
-	}
-
-	func showTab(index: Int) {
-		tabBarController.selectedIndex = index
-	}
-
-	func presentLoginController() {
-		let accountSetupController = AccountSetupController()
-		let accountSetupNav = DCNavigationController(rootViewController: accountSetupController)
-    let coordinator = AccountSetupCoordinator(navigationController: accountSetupNav)
-    childCoordinators.append(coordinator)
-    accountSetupController.coordinator = coordinator
-		rootViewController.present(accountSetupNav, animated: false, completion: nil)
-	}
+    private let window: UIWindow
+
+    var rootViewController: UIViewController {
+        return tabBarController
+    }
+
+    private var childCoordinators: [Coordinator] = []
+
+    private lazy var tabBarController: UITabBarController = {
+        let tabBarController = UITabBarController()
+        tabBarController.viewControllers = [contactListController, mailboxController, profileController, chatListController, settingsController]
+        // put viewControllers here
+        tabBarController.delegate = self
+        tabBarController.tabBar.tintColor = DCColors.primary
+        return tabBarController
+    }()
+
+    // MARK: viewControllers
+
+    private lazy var contactListController: UIViewController = {
+        let controller = ContactListController()
+        let nav = DCNavigationController(rootViewController: controller)
+        let settingsImage = UIImage(named: "contacts")
+        nav.tabBarItem = UITabBarItem(title: "Contacts", image: settingsImage, tag: 0)
+        let coordinator = ContactListCoordinator(navigationController: nav)
+        self.childCoordinators.append(coordinator)
+        controller.coordinator = coordinator
+        return nav
+    }()
+
+    private lazy var mailboxController: UIViewController = {
+        let controller = MailboxViewController(chatId: Int(DC_CHAT_ID_DEADDROP), title: "Mailbox")
+        controller.disableWriting = true
+        let nav = DCNavigationController(rootViewController: controller)
+        let settingsImage = UIImage(named: "message")
+        nav.tabBarItem = UITabBarItem(title: "Mailbox", image: settingsImage, tag: 1)
+        let coordinator = MailboxCoordinator(navigationController: nav)
+        self.childCoordinators.append(coordinator)
+        controller.coordinator = coordinator
+        return nav
+    }()
+
+    private lazy var profileController: UIViewController = {
+        let controller = ProfileViewController()
+        let nav = DCNavigationController(rootViewController: controller)
+        let settingsImage = UIImage(named: "report_card")
+        nav.tabBarItem = UITabBarItem(title: "My Profile", image: settingsImage, tag: 2)
+        let coordinator = ProfileCoordinator(rootViewController: nav)
+        self.childCoordinators.append(coordinator)
+        controller.coordinator = coordinator
+        return nav
+    }()
+
+    private lazy var chatListController: UIViewController = {
+        let controller = ChatListController()
+        let nav = DCNavigationController(rootViewController: controller)
+        let settingsImage = UIImage(named: "chat")
+        nav.tabBarItem = UITabBarItem(title: "Chats", image: settingsImage, tag: 3)
+        let coordinator = ChatListCoordinator(navigationController: nav)
+        self.childCoordinators.append(coordinator)
+        controller.coordinator = coordinator
+        return nav
+    }()
+
+    private lazy var settingsController: UIViewController = {
+        let controller = SettingsViewController()
+        let nav = DCNavigationController(rootViewController: controller)
+        let settingsImage = UIImage(named: "settings")
+        nav.tabBarItem = UITabBarItem(title: "Settings", image: settingsImage, tag: 4)
+        let coordinator = SettingsCoordinator(navigationController: nav)
+        self.childCoordinators.append(coordinator)
+        controller.coordinator = coordinator
+        return nav
+    }()
+
+    init(window: UIWindow) {
+        self.window = window
+        super.init()
+        window.rootViewController = rootViewController
+        window.makeKeyAndVisible()
+    }
+
+    public func start() {
+        print(tabBarController.selectedIndex)
+        showTab(index: 3)
+    }
+
+    func showTab(index: Int) {
+        tabBarController.selectedIndex = index
+    }
+
+    func presentLoginController() {
+        let accountSetupController = AccountSetupController()
+        let accountSetupNav = DCNavigationController(rootViewController: accountSetupController)
+        let coordinator = AccountSetupCoordinator(navigationController: accountSetupNav)
+        childCoordinators.append(coordinator)
+        accountSetupController.coordinator = coordinator
+        rootViewController.present(accountSetupNav, animated: false, completion: nil)
+    }
 }
 
 extension AppCoordinator: UITabBarControllerDelegate {
-	func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
-		if let dcNav = viewController as? DCNavigationController {
-			switch tabBarController.selectedIndex {
-			case 0,3,4:
-				dcNav.navigationBar.prefersLargeTitles = true
-			case 1,2:
-				dcNav.navigationBar.prefersLargeTitles = false
-			default:
-				// should never get here
-				dcNav.navigationBar.prefersLargeTitles = false
-			}
-		}
-	}
+    func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
+        if let dcNav = viewController as? DCNavigationController {
+            switch tabBarController.selectedIndex {
+            case 0,3,4:
+                dcNav.navigationBar.prefersLargeTitles = true
+            case 1,2:
+                dcNav.navigationBar.prefersLargeTitles = false
+            default:
+                // should never get here
+                dcNav.navigationBar.prefersLargeTitles = false
+            }
+        }
+    }
 }
 
 
 class ContactListCoordinator: Coordinator {
-	let navigationController: UINavigationController
-
-	var childCoordinators: [Coordinator] = []
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showContactDetail(contactId: Int) {
-		let contactDetailController = ContactDetailViewController(contactId: contactId)
-		contactDetailController.showChatCell = true
-		let coordinator = ContactDetailCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		contactDetailController.coordinator = coordinator
-		navigationController.pushViewController(contactDetailController, animated: true)
-	}
-
-	func showChat(chatId: Int) {
-		let chatVC = ChatViewController(chatId: chatId)
-		let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
-		childCoordinators.append(coordinator)
-		chatVC.coordinator = coordinator
-		navigationController.pushViewController(chatVC, animated: true)
-	}
-
-	func showNewContactController() {
-		let newContactController = NewContactController()
-		let coordinator = EditContactCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		newContactController.coordinator = coordinator
-		newContactController.hidesBottomBarWhenPushed = true
-		navigationController.pushViewController(newContactController, animated: true)
-	}
+    let navigationController: UINavigationController
+
+    var childCoordinators: [Coordinator] = []
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showContactDetail(contactId: Int) {
+        let contactDetailController = ContactDetailViewController(contactId: contactId)
+        contactDetailController.showChatCell = true
+        let coordinator = ContactDetailCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        contactDetailController.coordinator = coordinator
+        navigationController.pushViewController(contactDetailController, animated: true)
+    }
+
+    func showChat(chatId: Int) {
+        let chatVC = ChatViewController(chatId: chatId)
+        let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
+        childCoordinators.append(coordinator)
+        chatVC.coordinator = coordinator
+        navigationController.pushViewController(chatVC, animated: true)
+    }
+
+    func showNewContactController() {
+        let newContactController = NewContactController()
+        let coordinator = EditContactCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        newContactController.coordinator = coordinator
+        newContactController.hidesBottomBarWhenPushed = true
+        navigationController.pushViewController(newContactController, animated: true)
+    }
 }
 
 // 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(navigationController: UINavigationController) {
-		super.init(navigationController: navigationController, chatId: -1)
-	}
+    init(navigationController: UINavigationController) {
+        super.init(navigationController: navigationController, chatId: -1)
+    }
 
-	override func showChatDetail(chatId _: Int) {
-		// ignore for now
-	}
+    override func showChatDetail(chatId _: Int) {
+        // ignore for now
+    }
 
-	override func showCameraViewController() {
-		// ignore
-	}
+    override func showCameraViewController() {
+        // ignore
+    }
 }
 
 class ProfileCoordinator: Coordinator {
-	var rootViewController: UIViewController
+    var rootViewController: UIViewController
 
-	init(rootViewController: UIViewController) {
-		self.rootViewController = rootViewController
-	}
+    init(rootViewController: UIViewController) {
+        self.rootViewController = rootViewController
+    }
 }
 
 class ChatListCoordinator: Coordinator {
-	let navigationController: UINavigationController
-
-	var childCoordinators: [Coordinator] = []
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showNewChatController() {
-		let newChatVC = NewChatViewController()
-		let coordinator = NewChatCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		newChatVC.coordinator = coordinator
-		navigationController.pushViewController(newChatVC, animated: true)
-	}
-
-	func showChat(chatId: Int) {
-		let chatVC = ChatViewController(chatId: chatId)
-		let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
-		childCoordinators.append(coordinator)
-		chatVC.coordinator = coordinator
-		navigationController.pushViewController(chatVC, animated: true)
-	}
+    let navigationController: UINavigationController
+
+    var childCoordinators: [Coordinator] = []
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showNewChatController() {
+        let newChatVC = NewChatViewController()
+        let coordinator = NewChatCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        newChatVC.coordinator = coordinator
+        navigationController.pushViewController(newChatVC, animated: true)
+    }
+
+    func showChat(chatId: Int) {
+        let chatVC = ChatViewController(chatId: chatId)
+        let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
+        childCoordinators.append(coordinator)
+        chatVC.coordinator = coordinator
+        navigationController.pushViewController(chatVC, animated: true)
+    }
 }
 
 class SettingsCoordinator: Coordinator {
-	let navigationController: UINavigationController
-
-	var childCoordinators:[Coordinator] = []
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showAccountSetupController() {
-		let accountSetupVC = AccountSetupController()
-		let coordinator = AccountSetupCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		accountSetupVC.coordinator = coordinator
-		navigationController.pushViewController(accountSetupVC, animated: true)
-	}
-
-	func showEditSettingsController(option: SettingsEditOption) {
-		let editController = EditSettingsController()
-		editController.activateField(option: option)
-		navigationController.pushViewController(editController, animated: true)
-	}
-
-	func showLoginController() {
-		let accountSetupVC = AccountSetupController()
-		let coordinator = AccountSetupCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		accountSetupVC.coordinator = coordinator
-		let accountSetupNavigationController = DCNavigationController(rootViewController: accountSetupVC)
-		navigationController.present(accountSetupNavigationController, animated: true, completion: nil)
-	}
+    let navigationController: UINavigationController
+
+    var childCoordinators:[Coordinator] = []
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showAccountSetupController() {
+        let accountSetupVC = AccountSetupController()
+        let coordinator = AccountSetupCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        accountSetupVC.coordinator = coordinator
+        navigationController.pushViewController(accountSetupVC, animated: true)
+    }
+
+    func showEditSettingsController(option: SettingsEditOption) {
+        let editController = EditSettingsController()
+        editController.activateField(option: option)
+        navigationController.pushViewController(editController, animated: true)
+    }
+
+    func showLoginController() {
+        let accountSetupVC = AccountSetupController()
+        let coordinator = AccountSetupCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        accountSetupVC.coordinator = coordinator
+        let accountSetupNavigationController = DCNavigationController(rootViewController: accountSetupVC)
+        navigationController.present(accountSetupNavigationController, animated: true, completion: nil)
+    }
 }
 
 class AccountSetupCoordinator: Coordinator {
-	let navigationController: UINavigationController
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showImapPortOptions() {
-		let currentMailPort = MRConfig.mailPort ?? MRConfig.configuredMailPort
-		let currentPort = Int(currentMailPort)
-		let portSettingsController = PortSettingsController(sectionTitle: "IMAP Port", ports: [143, 993], currentPort: currentPort)
-		portSettingsController.onDismiss = {
-			port in
-			MRConfig.mailPort = port
-		}
-		navigationController.pushViewController(portSettingsController, animated: true)
-	}
-
-	func showImapSecurityOptions() {
-		let currentSecurityOption = MRConfig.getImapSecurity()
-		let convertedOption = SecurityConverter.convertHexToString(type: .IMAPSecurity, hex: currentSecurityOption)
-		let securitySettingsController = SecuritySettingsController(title: "IMAP Security", options: ["Automatic", "SSL / TLS", "STARTTLS", "OFF"], selectedOption: convertedOption)
-		securitySettingsController.onDismiss = {
-			option in
-			if let secValue = SecurityValue(rawValue: option) {
-				let value = SecurityConverter.convertValueToInt(type: .IMAPSecurity, value: secValue)
-				MRConfig.setImapSecurity(imapFlags: value)
-			}
-		}
-		navigationController.pushViewController(securitySettingsController, animated: true)
-	}
-
-	func showSmtpPortsOptions() {
-		let currentMailPort = MRConfig.sendPort ?? MRConfig.configuredSendPort
-		let currentPort = Int(currentMailPort)
-		let portSettingsController = PortSettingsController(sectionTitle: "SMTP Port", ports: [25, 465, 587], currentPort: currentPort)
-		portSettingsController.onDismiss = {
-			port in
-			MRConfig.sendPort = port
-		}
-		navigationController.pushViewController(portSettingsController, animated: true)
-	}
-
-	func showSmptpSecurityOptions() {
-		let currentSecurityOption = MRConfig.getSmtpSecurity()
-		let convertedOption = SecurityConverter.convertHexToString(type: .SMTPSecurity, hex: currentSecurityOption)
-		let securitySettingsController = SecuritySettingsController(title: "IMAP Security", options: ["Automatic", "SSL / TLS", "STARTTLS", "OFF"], selectedOption: convertedOption)
-		securitySettingsController.onDismiss = {
-			option in
-			if let secValue = SecurityValue(rawValue: option) {
-				let value = SecurityConverter.convertValueToInt(type: .SMTPSecurity, value: secValue)
-				MRConfig.setSmtpSecurity(smptpFlags: value)
-			}
-		}
-		navigationController.pushViewController(securitySettingsController, animated: true)
-	}
+    let navigationController: UINavigationController
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showImapPortOptions() {
+        let currentMailPort = MRConfig.mailPort ?? MRConfig.configuredMailPort
+        let currentPort = Int(currentMailPort)
+        let portSettingsController = PortSettingsController(sectionTitle: "IMAP Port", ports: [143, 993], currentPort: currentPort)
+        portSettingsController.onDismiss = {
+            port in
+            MRConfig.mailPort = port
+        }
+        navigationController.pushViewController(portSettingsController, animated: true)
+    }
+
+    func showImapSecurityOptions() {
+        let currentSecurityOption = MRConfig.getImapSecurity()
+        let convertedOption = SecurityConverter.convertHexToString(type: .IMAPSecurity, hex: currentSecurityOption)
+        let securitySettingsController = SecuritySettingsController(title: "IMAP Security", options: ["Automatic", "SSL / TLS", "STARTTLS", "OFF"], selectedOption: convertedOption)
+        securitySettingsController.onDismiss = {
+            option in
+            if let secValue = SecurityValue(rawValue: option) {
+                let value = SecurityConverter.convertValueToInt(type: .IMAPSecurity, value: secValue)
+                MRConfig.setImapSecurity(imapFlags: value)
+            }
+        }
+        navigationController.pushViewController(securitySettingsController, animated: true)
+    }
+
+    func showSmtpPortsOptions() {
+        let currentMailPort = MRConfig.sendPort ?? MRConfig.configuredSendPort
+        let currentPort = Int(currentMailPort)
+        let portSettingsController = PortSettingsController(sectionTitle: "SMTP Port", ports: [25, 465, 587], currentPort: currentPort)
+        portSettingsController.onDismiss = {
+            port in
+            MRConfig.sendPort = port
+        }
+        navigationController.pushViewController(portSettingsController, animated: true)
+    }
+
+    func showSmptpSecurityOptions() {
+        let currentSecurityOption = MRConfig.getSmtpSecurity()
+        let convertedOption = SecurityConverter.convertHexToString(type: .SMTPSecurity, hex: currentSecurityOption)
+        let securitySettingsController = SecuritySettingsController(title: "IMAP Security", options: ["Automatic", "SSL / TLS", "STARTTLS", "OFF"], selectedOption: convertedOption)
+        securitySettingsController.onDismiss = {
+            option in
+            if let secValue = SecurityValue(rawValue: option) {
+                let value = SecurityConverter.convertValueToInt(type: .SMTPSecurity, value: secValue)
+                MRConfig.setSmtpSecurity(smptpFlags: value)
+            }
+        }
+        navigationController.pushViewController(securitySettingsController, animated: true)
+    }
 }
 
 class NewChatCoordinator: Coordinator {
-	let navigationController: UINavigationController
-
-	private var childCoordinators: [Coordinator] = []
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showNewGroupController() {
-		let newGroupController = NewGroupViewController()
-		let coordinator = NewGroupCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		newGroupController.coordinator = coordinator
-		navigationController.pushViewController(newGroupController, animated: true)
-	}
-
-	func showQRCodeController() {
-		let controller = QrCodeReaderController()
-		// controller.delegate = self
-		// present(controller, animated: true, completion: nil)
-	}
-
-	func showNewContactController() {
-		let newContactController = NewContactController()
-		let coordinator = EditContactCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		newContactController.coordinator = coordinator
-		navigationController.pushViewController(newContactController, animated: true)
-	}
-
-	func showNewChat(contactId: Int) {
-		let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
-		showChat(chatId: Int(chatId))
-	}
-
-	func showChat(chatId: Int) {
-		let chatViewController = ChatViewController(chatId: chatId)
-		let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
-		childCoordinators.append(coordinator)
-		chatViewController.coordinator = coordinator
-		navigationController.pushViewController(chatViewController, animated: true)
-		navigationController.viewControllers.remove(at: 1)
-	}
+    let navigationController: UINavigationController
+
+    private var childCoordinators: [Coordinator] = []
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showNewGroupController() {
+        let newGroupController = NewGroupViewController()
+        let coordinator = NewGroupCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        newGroupController.coordinator = coordinator
+        navigationController.pushViewController(newGroupController, animated: true)
+    }
+
+    func showQRCodeController() {
+        let controller = QrCodeReaderController()
+        // controller.delegate = self
+        // present(controller, animated: true, completion: nil)
+    }
+
+    func showNewContactController() {
+        let newContactController = NewContactController()
+        let coordinator = EditContactCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        newContactController.coordinator = coordinator
+        navigationController.pushViewController(newContactController, animated: true)
+    }
+
+    func showNewChat(contactId: Int) {
+        let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
+        showChat(chatId: Int(chatId))
+    }
+
+    func showChat(chatId: Int) {
+        let chatViewController = ChatViewController(chatId: chatId)
+        let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
+        childCoordinators.append(coordinator)
+        chatViewController.coordinator = coordinator
+        navigationController.pushViewController(chatViewController, animated: true)
+        navigationController.viewControllers.remove(at: 1)
+    }
 }
 
 class GroupChatDetailCoordinator: Coordinator {
-	let navigationController: UINavigationController
-
-	private var childCoordinators: [Coordinator] = []
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showSingleChatEdit(contactId: Int) {
-		let editContactController = EditContactController(contactIdForUpdate: contactId)
-		let coordinator = EditContactCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		editContactController.coordinator = coordinator
-		navigationController.pushViewController(editContactController, animated: true)
-	}
-
-	func showAddGroupMember(chatId: Int) {
-		let groupMemberViewController = AddGroupMembersViewController(chatId: chatId)
-		navigationController.pushViewController(groupMemberViewController, animated: true)
-	}
-
-	func showGroupChatEdit(chat: MRChat) {
-		let editGroupViewController = EditGroupViewController(chat: chat)
-		let coordinator = EditGroupCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		editGroupViewController.coordinator = coordinator
-		navigationController.pushViewController(editGroupViewController, animated: true)
-	}
+    let navigationController: UINavigationController
+
+    private var childCoordinators: [Coordinator] = []
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showSingleChatEdit(contactId: Int) {
+        let editContactController = EditContactController(contactIdForUpdate: contactId)
+        let coordinator = EditContactCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        editContactController.coordinator = coordinator
+        navigationController.pushViewController(editContactController, animated: true)
+    }
+
+    func showAddGroupMember(chatId: Int) {
+        let groupMemberViewController = AddGroupMembersViewController(chatId: chatId)
+        navigationController.pushViewController(groupMemberViewController, animated: true)
+    }
+
+    func showGroupChatEdit(chat: MRChat) {
+        let editGroupViewController = EditGroupViewController(chat: chat)
+        let coordinator = EditGroupCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        editGroupViewController.coordinator = coordinator
+        navigationController.pushViewController(editGroupViewController, animated: true)
+    }
 }
 
 class ChatViewCoordinator: NSObject, Coordinator {
-	let navigationController: UINavigationController
-	let chatId: Int
-	var chatViewController: ChatViewController!
-
-	var childCoordinators: [Coordinator] = []
-
-	init(navigationController: UINavigationController, chatId: Int) {
-		self.navigationController = navigationController
-		self.chatId = chatId
-	}
-
-	func showChatDetail(chatId: Int) {
-		let chat = MRChat(id: chatId)
-		switch chat.chatType {
-		case .SINGLE:
-			if let contactId = chat.contactIds.first {
-				let contactDetailController = ContactDetailViewController(contactId: contactId)
-				let coordinator = ContactDetailCoordinator(navigationController: navigationController)
-				childCoordinators.append(coordinator)
-				contactDetailController.coordinator = coordinator
-				navigationController.pushViewController(contactDetailController, animated: true)
-			}
-		case .GROUP, .VERYFIEDGROUP:
-			let groupChatDetailViewController = GroupChatDetailViewController(chatId: chatId) // inherits from ChatDetailViewController
-			let coordinator = GroupChatDetailCoordinator(navigationController: navigationController)
-			childCoordinators.append(coordinator)
-			groupChatDetailViewController.coordinator = coordinator
-			navigationController.pushViewController(groupChatDetailViewController, animated: true)
-		}
-	}
-
-	func showContactDetail(of contactId: Int) {
-		let contactDetailController = ContactDetailViewController(contactId: contactId)
-		contactDetailController.showChatCell = true
-		//let nav = UINavigationController(rootViewController: contactDetailController)
-		let coordinator = ContactDetailCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		contactDetailController.coordinator = coordinator
-		navigationController.pushViewController(contactDetailController, animated: true)
-		// navigationController.present(nav, animated: true, completion: nil)
-	}
-
-	private func sendImage(_ image:UIImage) {
-		DispatchQueue.global().async {
-			if let compressedImage = image.dcCompress() {
-				// at this point image is compressed by 85% by default
-				let pixelSize = compressedImage.imageSizeInPixel()
-				let width = Int32(exactly: pixelSize.width)!
-				let height =  Int32(exactly: pixelSize.height)!
-				let path = Utils.saveImage(image: compressedImage)
-				let msg = dc_msg_new(mailboxPointer, DC_MSG_IMAGE)
-				dc_msg_set_file(msg, path, "image/jpeg")
-				dc_msg_set_dimension(msg, width, height)
-				dc_send_msg(mailboxPointer, UInt32(self.chatId), msg)
-				// cleanup
-				dc_msg_unref(msg)
-			}
-		}
-	}
-
-	private func sendVideo(url: NSURL) {
-		let msg = dc_msg_new(mailboxPointer, DC_MSG_VIDEO)
-		if let path = url.relativePath?.cString(using: .utf8) { //absoluteString?.cString(using: .utf8) {
-			dc_msg_set_file(msg, path, "video/mov")
-			dc_send_msg(mailboxPointer, UInt32(chatId), msg)
-			dc_msg_unref(msg)
-		}
-	}
-
-	private func handleMediaMessageSuccess() {
-		if let chatViewController = self.navigationController.visibleViewController as? MediaSendHandler {
-			DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
-				chatViewController.onSuccess()
-			}
-		}
-	}
-
-	func showCameraViewController() {
-		if UIImagePickerController.isSourceTypeAvailable(.camera) {
-			let cameraViewController = CameraViewController { [weak self] image, _ in
-				self?.navigationController.dismiss(animated: true, completion: {
-					self?.handleMediaMessageSuccess()
-				})
-				if let image = image {
-					self?.sendImage(image)
-				}
-			}
-
-			navigationController.present(cameraViewController, animated: true, completion: nil)
-		} else {
-			let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
-			alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-				self.navigationController.dismiss(animated: true, completion: nil)
-			}))
-			navigationController.present(alert, animated: true, completion: nil)
-		}
-
-	}
-	func showVideoLibrary() {
-		if PHPhotoLibrary.authorizationStatus() != .authorized {
-			PHPhotoLibrary.requestAuthorization { status in
-				DispatchQueue.main.async {
-					[weak self] in
-					switch status {
-					case  .denied, .notDetermined, .restricted:
-						print("denied")
-					case .authorized:
-						self?.presentVideoLibrary()
-					}
-				}
-			}
-		} else {
-			presentVideoLibrary()
-		}
-	}
-
-	private func presentVideoLibrary() {
-		if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
-			let videoPicker = UIImagePickerController()
-			videoPicker.title = "Videos"
-			videoPicker.delegate = self
-			videoPicker.sourceType = .photoLibrary
-			videoPicker.mediaTypes = [kUTTypeMovie as String, kUTTypeVideo as String]
-			navigationController.present(videoPicker, animated: true, completion: nil)
-		}
-	}
+    let navigationController: UINavigationController
+    let chatId: Int
+    var chatViewController: ChatViewController!
+
+    var childCoordinators: [Coordinator] = []
+
+    init(navigationController: UINavigationController, chatId: Int) {
+        self.navigationController = navigationController
+        self.chatId = chatId
+    }
+
+    func showChatDetail(chatId: Int) {
+        let chat = MRChat(id: chatId)
+        switch chat.chatType {
+        case .SINGLE:
+            if let contactId = chat.contactIds.first {
+                let contactDetailController = ContactDetailViewController(contactId: contactId)
+                let coordinator = ContactDetailCoordinator(navigationController: navigationController)
+                childCoordinators.append(coordinator)
+                contactDetailController.coordinator = coordinator
+                navigationController.pushViewController(contactDetailController, animated: true)
+            }
+        case .GROUP, .VERYFIEDGROUP:
+            let groupChatDetailViewController = GroupChatDetailViewController(chatId: chatId) // inherits from ChatDetailViewController
+            let coordinator = GroupChatDetailCoordinator(navigationController: navigationController)
+            childCoordinators.append(coordinator)
+            groupChatDetailViewController.coordinator = coordinator
+            navigationController.pushViewController(groupChatDetailViewController, animated: true)
+        }
+    }
+
+    func showContactDetail(of contactId: Int) {
+        let contactDetailController = ContactDetailViewController(contactId: contactId)
+        contactDetailController.showChatCell = true
+        //let nav = UINavigationController(rootViewController: contactDetailController)
+        let coordinator = ContactDetailCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        contactDetailController.coordinator = coordinator
+        navigationController.pushViewController(contactDetailController, animated: true)
+        // navigationController.present(nav, animated: true, completion: nil)
+    }
+
+    private func sendImage(_ image:UIImage) {
+        DispatchQueue.global().async {
+            if let compressedImage = image.dcCompress() {
+                // at this point image is compressed by 85% by default
+                let pixelSize = compressedImage.imageSizeInPixel()
+                let width = Int32(exactly: pixelSize.width)!
+                let height =  Int32(exactly: pixelSize.height)!
+                let path = Utils.saveImage(image: compressedImage)
+                let msg = dc_msg_new(mailboxPointer, DC_MSG_IMAGE)
+                dc_msg_set_file(msg, path, "image/jpeg")
+                dc_msg_set_dimension(msg, width, height)
+                dc_send_msg(mailboxPointer, UInt32(self.chatId), msg)
+                // cleanup
+                dc_msg_unref(msg)
+            }
+        }
+    }
+
+    private func sendVideo(url: NSURL) {
+        let msg = dc_msg_new(mailboxPointer, DC_MSG_VIDEO)
+        if let path = url.relativePath?.cString(using: .utf8) { //absoluteString?.cString(using: .utf8) {
+            dc_msg_set_file(msg, path, "video/mov")
+            dc_send_msg(mailboxPointer, UInt32(chatId), msg)
+            dc_msg_unref(msg)
+        }
+    }
+
+    private func handleMediaMessageSuccess() {
+        if let chatViewController = self.navigationController.visibleViewController as? MediaSendHandler {
+            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
+                chatViewController.onSuccess()
+            }
+        }
+    }
+
+    func showCameraViewController() {
+        if UIImagePickerController.isSourceTypeAvailable(.camera) {
+            let cameraViewController = CameraViewController { [weak self] image, _ in
+                self?.navigationController.dismiss(animated: true, completion: {
+                    self?.handleMediaMessageSuccess()
+                })
+                if let image = image {
+                    self?.sendImage(image)
+                }
+            }
+
+            navigationController.present(cameraViewController, animated: true, completion: nil)
+        } else {
+            let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
+            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+                self.navigationController.dismiss(animated: true, completion: nil)
+            }))
+            navigationController.present(alert, animated: true, completion: nil)
+        }
+
+    }
+    func showVideoLibrary() {
+        if PHPhotoLibrary.authorizationStatus() != .authorized {
+            PHPhotoLibrary.requestAuthorization { status in
+                DispatchQueue.main.async {
+                    [weak self] in
+                    switch status {
+                    case  .denied, .notDetermined, .restricted:
+                        print("denied")
+                    case .authorized:
+                        self?.presentVideoLibrary()
+                    }
+                }
+            }
+        } else {
+            presentVideoLibrary()
+        }
+    }
+
+    private func presentVideoLibrary() {
+        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) {
+            let videoPicker = UIImagePickerController()
+            videoPicker.title = "Videos"
+            videoPicker.delegate = self
+            videoPicker.sourceType = .photoLibrary
+            videoPicker.mediaTypes = [kUTTypeMovie as String, kUTTypeVideo as String]
+            navigationController.present(videoPicker, animated: true, completion: nil)
+        }
+    }
 }
 
 extension ChatViewCoordinator: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
 
-	func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
-		if let videoUrl = info[UIImagePickerController.InfoKey.mediaURL] as? NSURL {
-			sendVideo(url: videoUrl)
-		}
-		navigationController.dismiss(animated: true) {
-				self.handleMediaMessageSuccess()
-		}
-	}
+    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
+        if let videoUrl = info[UIImagePickerController.InfoKey.mediaURL] as? NSURL {
+            sendVideo(url: videoUrl)
+        }
+        navigationController.dismiss(animated: true) {
+            self.handleMediaMessageSuccess()
+        }
+    }
 }
 
 class NewGroupCoordinator: Coordinator {
-	let navigationController: UINavigationController
+    let navigationController: UINavigationController
 
-	private var childCoordinators: [Coordinator] = []
+    private var childCoordinators: [Coordinator] = []
 
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
 
-	func showGroupNameController(contactIdsForGroup: Set<Int>) {
-		let groupNameController = GroupNameController(contactIdsForGroup: contactIdsForGroup)
-		let coordinator = GroupNameCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		groupNameController.coordinator = coordinator
-		navigationController.pushViewController(groupNameController, animated: true)
-	}
+    func showGroupNameController(contactIdsForGroup: Set<Int>) {
+        let groupNameController = GroupNameController(contactIdsForGroup: contactIdsForGroup)
+        let coordinator = GroupNameCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        groupNameController.coordinator = coordinator
+        navigationController.pushViewController(groupNameController, animated: true)
+    }
 }
 
 class GroupNameCoordinator: Coordinator {
-	let navigationController: UINavigationController
-
-	private var childCoordinators: [Coordinator] = []
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showGroupChat(chatId: Int) {
-		let chatViewController = ChatViewController(chatId: chatId)
-		let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
-		childCoordinators.append(coordinator)
-		chatViewController.coordinator = coordinator
-		navigationController.popToRootViewController(animated: false)
-		navigationController.pushViewController(chatViewController, animated: true)
-	}
+    let navigationController: UINavigationController
+
+    private var childCoordinators: [Coordinator] = []
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showGroupChat(chatId: Int) {
+        let chatViewController = ChatViewController(chatId: chatId)
+        let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
+        childCoordinators.append(coordinator)
+        chatViewController.coordinator = coordinator
+        navigationController.popToRootViewController(animated: false)
+        navigationController.pushViewController(chatViewController, animated: true)
+    }
 }
 
 class ContactDetailCoordinator: Coordinator, ContactDetailCoordinatorProtocol {
-	let navigationController: UINavigationController
-
-	private var childCoordinators: [Coordinator] = []
-
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
-
-	func showChat(chatId: Int) {
-		let chatViewController = ChatViewController(chatId: chatId)
-		let coordinator = ChatViewCoordinator(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(contactIdForUpdate: contactId)
-		let coordinator = EditContactCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		editContactController.coordinator = coordinator
-		navigationController.pushViewController(editContactController, animated: true)
-	}
+    let navigationController: UINavigationController
+
+    private var childCoordinators: [Coordinator] = []
+
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
+
+    func showChat(chatId: Int) {
+        let chatViewController = ChatViewController(chatId: chatId)
+        let coordinator = ChatViewCoordinator(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(contactIdForUpdate: contactId)
+        let coordinator = EditContactCoordinator(navigationController: navigationController)
+        childCoordinators.append(coordinator)
+        editContactController.coordinator = coordinator
+        navigationController.pushViewController(editContactController, animated: true)
+    }
 }
 
 class EditGroupCoordinator: Coordinator {
-	let navigationController: UINavigationController
+    let navigationController: UINavigationController
 
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
 
-	func navigateBack() {
-		navigationController.popViewController(animated: true)
-	}
+    func navigateBack() {
+        navigationController.popViewController(animated: true)
+    }
 }
 
 class EditContactCoordinator: Coordinator, EditContactCoordinatorProtocol {
 
-	let navigationController: UINavigationController
+    let navigationController: UINavigationController
 
-	var childCoordinators: [Coordinator] = []
+    var childCoordinators: [Coordinator] = []
 
-	init(navigationController: UINavigationController) {
-		self.navigationController = navigationController
-	}
+    init(navigationController: UINavigationController) {
+        self.navigationController = navigationController
+    }
 
-	func navigateBack() {
-		navigationController.popViewController(animated: true)
-	}
+    func navigateBack() {
+        navigationController.popViewController(animated: true)
+    }
 
-	func showChat(chatId: Int) {
-		let chatViewController = ChatViewController(chatId: chatId)
-		let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
-		coordinator.chatViewController = chatViewController
-		childCoordinators.append(coordinator)
-		chatViewController.coordinator = coordinator
-		navigationController.popToRootViewController(animated: false)
-		navigationController.pushViewController(chatViewController, animated: true)
-	}
+    func showChat(chatId: Int) {
+        let chatViewController = ChatViewController(chatId: chatId)
+        let coordinator = ChatViewCoordinator(navigationController: navigationController, chatId: chatId)
+        coordinator.chatViewController = chatViewController
+        childCoordinators.append(coordinator)
+        chatViewController.coordinator = coordinator
+        navigationController.popToRootViewController(animated: false)
+        navigationController.pushViewController(chatViewController, animated: true)
+    }
 }
 
 protocol ContactDetailCoordinatorProtocol: class {
-	func showEditContact(contactId: Int)
-	func showChat(chatId: Int)
+    func showEditContact(contactId: Int)
+    func showChat(chatId: Int)
 }
 
 protocol EditContactCoordinatorProtocol: class {
-	func navigateBack()
-	func showChat(chatId: Int)
+    func navigateBack()
+    func showChat(chatId: Int)
 }

+ 803 - 803
deltachat-ios/DC/Wrapper.swift

@@ -3,910 +3,910 @@ import MessageKit
 import UIKit
 
 enum MessageViewType: CustomStringConvertible {
-  case audio
-  case file
-  case gif
-  case image
-  case text
-  case video
-  case voice
-
-  var description: String {
-    switch self {
-    // Use Internationalization, as appropriate.
-    case .audio: return "Audio"
-    case .file: return "File"
-    case .gif: return "GIF"
-    case .image: return "Image"
-    case .text: return "Text"
-    case .video: return "Video"
-    case .voice: return "Voice"
-    }
-  }
+    case audio
+    case file
+    case gif
+    case image
+    case text
+    case video
+    case voice
+
+    var description: String {
+        switch self {
+        // Use Internationalization, as appropriate.
+        case .audio: return "Audio"
+        case .file: return "File"
+        case .gif: return "GIF"
+        case .image: return "Image"
+        case .text: return "Text"
+        case .video: return "Video"
+        case .voice: return "Voice"
+        }
+    }
 }
 
 class MRContact {
-  private var contactPointer: OpaquePointer
-
-  var name: String {
-    return String(cString: dc_contact_get_name(contactPointer))
-  }
-
-  var email: String {
-    return String(cString: dc_contact_get_addr(contactPointer))
-  }
-
-  var isVerified: Bool {
-    return dc_contact_is_verified(contactPointer) > 0
-  }
-
-  var isBlocked: Bool {
-    return dc_contact_is_blocked(contactPointer) == 1
-  }
-
-  lazy var profileImage: UIImage? = { [unowned self] in
-    let file = dc_contact_get_profile_image(contactPointer)
-    if let cFile = file {
-      let filename = String(cString: cFile)
-      let path: URL = URL(fileURLWithPath: filename, isDirectory: false)
-      if path.isFileURL {
-        do {
-          let data = try Data(contentsOf: path)
-          return UIImage(data: data)
-        } catch {
-          logger.warning("failed to load image: \(filename), \(error)")
-          return nil
-        }
-      }
-      return nil
-    }
-
-    return nil
-  }()
-
-  var color: UIColor {
-    return UIColor(netHex: Int(dc_contact_get_color(contactPointer)))
-  }
-
-  var id: Int {
-    return Int(dc_contact_get_id(contactPointer))
-  }
-
-  init(id: Int) {
-    contactPointer = dc_get_contact(mailboxPointer, UInt32(id))
-  }
-
-  deinit {
-    dc_contact_unref(contactPointer)
-  }
-
-  func block() {
-    dc_block_contact(mailboxPointer, UInt32(id), 1)
-  }
-
-  func unblock() {
-    dc_block_contact(mailboxPointer, UInt32(id), 0)
-  }
-
-  func marknoticed() {
-    dc_marknoticed_contact(mailboxPointer, UInt32(id))
-  }
+    private var contactPointer: OpaquePointer
+
+    var name: String {
+        return String(cString: dc_contact_get_name(contactPointer))
+    }
+
+    var email: String {
+        return String(cString: dc_contact_get_addr(contactPointer))
+    }
+
+    var isVerified: Bool {
+        return dc_contact_is_verified(contactPointer) > 0
+    }
+
+    var isBlocked: Bool {
+        return dc_contact_is_blocked(contactPointer) == 1
+    }
+
+    lazy var profileImage: UIImage? = { [unowned self] in
+        let file = dc_contact_get_profile_image(contactPointer)
+        if let cFile = file {
+            let filename = String(cString: cFile)
+            let path: URL = URL(fileURLWithPath: filename, isDirectory: false)
+            if path.isFileURL {
+                do {
+                    let data = try Data(contentsOf: path)
+                    return UIImage(data: data)
+                } catch {
+                    logger.warning("failed to load image: \(filename), \(error)")
+                    return nil
+                }
+            }
+            return nil
+        }
+
+        return nil
+    }()
+
+    var color: UIColor {
+        return UIColor(netHex: Int(dc_contact_get_color(contactPointer)))
+    }
+
+    var id: Int {
+        return Int(dc_contact_get_id(contactPointer))
+    }
+
+    init(id: Int) {
+        contactPointer = dc_get_contact(mailboxPointer, UInt32(id))
+    }
+
+    deinit {
+        dc_contact_unref(contactPointer)
+    }
+
+    func block() {
+        dc_block_contact(mailboxPointer, UInt32(id), 1)
+    }
+
+    func unblock() {
+        dc_block_contact(mailboxPointer, UInt32(id), 0)
+    }
+
+    func marknoticed() {
+        dc_marknoticed_contact(mailboxPointer, UInt32(id))
+    }
 }
 
 class MRMessage: MessageType {
-  private var messagePointer: OpaquePointer
-
-  lazy var sender: SenderType = {
-    Sender(id: "\(fromContactId)", displayName: fromContact.name)
-  }()
-
-  lazy var sentDate: Date = {
-    Date(timeIntervalSince1970: Double(timestamp))
-  }()
-
-  let localDateFormatter: DateFormatter = {
-    let result = DateFormatter()
-    result.dateStyle = .none
-    result.timeStyle = .short
-    return result
-  }()
-
-  func formattedSentDate() -> String {
-    return localDateFormatter.string(from: sentDate)
-  }
-
-  lazy var kind: MessageKind = {
-    if isInfo {
-      let text = NSAttributedString(string: self.text ?? "", attributes: [
-        NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12),
-        NSAttributedString.Key.foregroundColor: UIColor.darkGray,
-      ])
-      return MessageKind.attributedText(text)
-    }
-
-    let text = self.text ?? ""
-
-    if self.viewtype == nil {
-      return MessageKind.text(text)
-    }
-
-    switch self.viewtype! {
-    case .image:
-      return MessageKind.photo(Media(image: image))
-    case .video:
-      return MessageKind.video(Media(url: fileURL))
-    default:
-      // TODO: custom views for audio, etc
-      if let filename = self.filename {
-        return MessageKind.text("File: \(self.filename ?? "") (\(self.filesize) bytes)")
-      }
-      return MessageKind.text(text)
-    }
-  }()
-
-  var messageId: String {
-    return "\(id)"
-  }
-
-  var id: Int {
-    return Int(dc_msg_get_id(messagePointer))
-  }
-
-  var fromContactId: Int {
-    return Int(dc_msg_get_from_id(messagePointer))
-  }
-
-  lazy var fromContact: MRContact = {
-    MRContact(id: fromContactId)
-  }()
-
-  var chatId: Int {
-    return Int(dc_msg_get_chat_id(messagePointer))
-  }
-
-  var text: String? {
-    guard let result = dc_msg_get_text(messagePointer) else { return nil }
-
-    return String(cString: result)
-  }
-
-  var viewtype: MessageViewType? {
-    switch dc_msg_get_viewtype(messagePointer) {
-    case 0:
-      return nil
-    case DC_MSG_AUDIO:
-      return .audio
-    case DC_MSG_FILE:
-      return .file
-    case DC_MSG_GIF:
-      return .gif
-    case DC_MSG_TEXT:
-      return .text
-    case DC_MSG_IMAGE:
-      return .image
-    case DC_MSG_VIDEO:
-      return .video
-    case DC_MSG_VOICE:
-      return .voice
-    default:
-      return nil
-    }
-  }
-
-  var fileURL: URL? {
-    if let file = self.file {
-      return URL(fileURLWithPath: file, isDirectory: false)
-    }
-    return nil
-  }
-
-  private lazy var image: UIImage? = { [unowned self] in
-    let filetype = dc_msg_get_viewtype(messagePointer)
-    if let path = fileURL, filetype == DC_MSG_IMAGE {
-      if path.isFileURL {
-        do {
-          let data = try Data(contentsOf: path)
-          let image = UIImage(data: data)
-          return image
-        } catch {
-          logger.warning("failed to load image: \(path), \(error)")
-          return nil
-        }
-      }
-      return nil
-    } else {
-      return nil
-    }
-  }()
-
-  var file: String? {
-    if let cStr = dc_msg_get_file(messagePointer) {
-      let str = String(cString: cStr)
-
-      return str == "" ? nil : str
-    }
-
-    return nil
-  }
-
-  var filemime: String? {
-    if let cStr = dc_msg_get_filemime(messagePointer) {
-      let str = String(cString: cStr)
-
-      return str == "" ? nil : str
-    }
-
-    return nil
-  }
-
-  var filename: String? {
-    if let cStr = dc_msg_get_filename(messagePointer) {
-      let str = String(cString: cStr)
-
-      return str == "" ? nil : str
-    }
-
-    return nil
-  }
-
-  var filesize: Int {
-    return Int(dc_msg_get_filebytes(messagePointer))
-  }
-
-  // MR_MSG_*
-  var type: Int {
-    return Int(dc_msg_get_viewtype(messagePointer))
-  }
-
-  // MR_STATE_*
-  var state: Int {
-    return Int(dc_msg_get_state(messagePointer))
-  }
-
-  func stateDescription() -> String {
-    switch Int32(state) {
-    case DC_STATE_IN_FRESH:
-      return "Fresh"
-    case DC_STATE_IN_NOTICED:
-      return "Noticed"
-    case DC_STATE_IN_SEEN:
-      return "Seen"
-    case DC_STATE_OUT_DRAFT:
-      return "Draft"
-    case DC_STATE_OUT_PENDING:
-      return "Pending"
-    case DC_STATE_OUT_DELIVERED:
-      return "Sent"
-    case DC_STATE_OUT_MDN_RCVD:
-      return "Read"
-    case DC_STATE_OUT_FAILED:
-      return "Failed"
-    default:
-      return "Unknown"
-    }
-  }
-
-  var timestamp: Int64 {
-    return Int64(dc_msg_get_timestamp(messagePointer))
-  }
+    private var messagePointer: OpaquePointer
+
+    lazy var sender: SenderType = {
+        Sender(id: "\(fromContactId)", displayName: fromContact.name)
+    }()
+
+    lazy var sentDate: Date = {
+        Date(timeIntervalSince1970: Double(timestamp))
+    }()
+
+    let localDateFormatter: DateFormatter = {
+        let result = DateFormatter()
+        result.dateStyle = .none
+        result.timeStyle = .short
+        return result
+    }()
+
+    func formattedSentDate() -> String {
+        return localDateFormatter.string(from: sentDate)
+    }
+
+    lazy var kind: MessageKind = {
+        if isInfo {
+            let text = NSAttributedString(string: self.text ?? "", attributes: [
+                NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 12),
+                NSAttributedString.Key.foregroundColor: UIColor.darkGray,
+            ])
+            return MessageKind.attributedText(text)
+        }
+
+        let text = self.text ?? ""
+
+        if self.viewtype == nil {
+            return MessageKind.text(text)
+        }
+
+        switch self.viewtype! {
+        case .image:
+            return MessageKind.photo(Media(image: image))
+        case .video:
+            return MessageKind.video(Media(url: fileURL))
+        default:
+            // TODO: custom views for audio, etc
+            if let filename = self.filename {
+                return MessageKind.text("File: \(self.filename ?? "") (\(self.filesize) bytes)")
+            }
+            return MessageKind.text(text)
+        }
+    }()
+
+    var messageId: String {
+        return "\(id)"
+    }
+
+    var id: Int {
+        return Int(dc_msg_get_id(messagePointer))
+    }
+
+    var fromContactId: Int {
+        return Int(dc_msg_get_from_id(messagePointer))
+    }
+
+    lazy var fromContact: MRContact = {
+        MRContact(id: fromContactId)
+    }()
+
+    var chatId: Int {
+        return Int(dc_msg_get_chat_id(messagePointer))
+    }
+
+    var text: String? {
+        guard let result = dc_msg_get_text(messagePointer) else { return nil }
+
+        return String(cString: result)
+    }
+
+    var viewtype: MessageViewType? {
+        switch dc_msg_get_viewtype(messagePointer) {
+        case 0:
+            return nil
+        case DC_MSG_AUDIO:
+            return .audio
+        case DC_MSG_FILE:
+            return .file
+        case DC_MSG_GIF:
+            return .gif
+        case DC_MSG_TEXT:
+            return .text
+        case DC_MSG_IMAGE:
+            return .image
+        case DC_MSG_VIDEO:
+            return .video
+        case DC_MSG_VOICE:
+            return .voice
+        default:
+            return nil
+        }
+    }
+
+    var fileURL: URL? {
+        if let file = self.file {
+            return URL(fileURLWithPath: file, isDirectory: false)
+        }
+        return nil
+    }
+
+    private lazy var image: UIImage? = { [unowned self] in
+        let filetype = dc_msg_get_viewtype(messagePointer)
+        if let path = fileURL, filetype == DC_MSG_IMAGE {
+            if path.isFileURL {
+                do {
+                    let data = try Data(contentsOf: path)
+                    let image = UIImage(data: data)
+                    return image
+                } catch {
+                    logger.warning("failed to load image: \(path), \(error)")
+                    return nil
+                }
+            }
+            return nil
+        } else {
+            return nil
+        }
+    }()
 
-  var isInfo: Bool {
-    return dc_msg_is_info(messagePointer) == 1
-  }
-
-  init(id: Int) {
-    messagePointer = dc_get_msg(mailboxPointer, UInt32(id))
-  }
-
-  func summary(chars: Int) -> String? {
-    guard let result = dc_msg_get_summarytext(messagePointer, Int32(chars)) else { return nil }
-
-    return String(cString: result)
-  }
-
-  func createChat() -> MRChat {
-    let chatId = dc_create_chat_by_msg_id(mailboxPointer, UInt32(id))
-    return MRChat(id: Int(chatId))
-  }
+    var file: String? {
+        if let cStr = dc_msg_get_file(messagePointer) {
+            let str = String(cString: cStr)
 
-  deinit {
-    dc_msg_unref(messagePointer)
-  }
+            return str == "" ? nil : str
+        }
+
+        return nil
+    }
+
+    var filemime: String? {
+        if let cStr = dc_msg_get_filemime(messagePointer) {
+            let str = String(cString: cStr)
+
+            return str == "" ? nil : str
+        }
+
+        return nil
+    }
+
+    var filename: String? {
+        if let cStr = dc_msg_get_filename(messagePointer) {
+            let str = String(cString: cStr)
+
+            return str == "" ? nil : str
+        }
+
+        return nil
+    }
+
+    var filesize: Int {
+        return Int(dc_msg_get_filebytes(messagePointer))
+    }
+
+    // MR_MSG_*
+    var type: Int {
+        return Int(dc_msg_get_viewtype(messagePointer))
+    }
+
+    // MR_STATE_*
+    var state: Int {
+        return Int(dc_msg_get_state(messagePointer))
+    }
+
+    func stateDescription() -> String {
+        switch Int32(state) {
+        case DC_STATE_IN_FRESH:
+            return "Fresh"
+        case DC_STATE_IN_NOTICED:
+            return "Noticed"
+        case DC_STATE_IN_SEEN:
+            return "Seen"
+        case DC_STATE_OUT_DRAFT:
+            return "Draft"
+        case DC_STATE_OUT_PENDING:
+            return "Pending"
+        case DC_STATE_OUT_DELIVERED:
+            return "Sent"
+        case DC_STATE_OUT_MDN_RCVD:
+            return "Read"
+        case DC_STATE_OUT_FAILED:
+            return "Failed"
+        default:
+            return "Unknown"
+        }
+    }
+
+    var timestamp: Int64 {
+        return Int64(dc_msg_get_timestamp(messagePointer))
+    }
+
+    var isInfo: Bool {
+        return dc_msg_is_info(messagePointer) == 1
+    }
+
+    init(id: Int) {
+        messagePointer = dc_get_msg(mailboxPointer, UInt32(id))
+    }
+
+    func summary(chars: Int) -> String? {
+        guard let result = dc_msg_get_summarytext(messagePointer, Int32(chars)) else { return nil }
+
+        return String(cString: result)
+    }
+
+    func createChat() -> MRChat {
+        let chatId = dc_create_chat_by_msg_id(mailboxPointer, UInt32(id))
+        return MRChat(id: Int(chatId))
+    }
+
+    deinit {
+        dc_msg_unref(messagePointer)
+    }
 }
 
 enum ChatType: Int {
-  case SINGLE = 100
-  case GROUP = 120
-  case VERYFIEDGROUP = 130
+    case SINGLE = 100
+    case GROUP = 120
+    case VERYFIEDGROUP = 130
 }
 
 class MRChat {
-  var chatPointer: OpaquePointer
-
-  var id: Int {
-    return Int(dc_chat_get_id(chatPointer))
-  }
-
-  var name: String {
-    return String(cString: dc_chat_get_name(chatPointer))
-  }
-
-  var type: Int {
-    return Int(dc_chat_get_type(chatPointer))
-  }
-
-  var chatType: ChatType {
-    return ChatType(rawValue: type) ?? ChatType.GROUP // group as fallback - shouldn't get here
-  }
-
-  var color: UIColor {
-    return UIColor(netHex: Int(dc_chat_get_color(chatPointer)))
-  }
-
-  var isVerified: Bool {
-    return dc_chat_is_verified(chatPointer) > 0
-  }
-
-  var contactIds: [Int] {
-    return Utils.copyAndFreeArray(inputArray: dc_get_chat_contacts(mailboxPointer, UInt32(id)))
-  }
-
-  lazy var profileImage: UIImage? = { [unowned self] in
-    let file = dc_chat_get_profile_image(chatPointer)
-    if let cFile = file {
-      let filename = String(cString: cFile)
-      let path: URL = URL(fileURLWithPath: filename, isDirectory: false)
-      if path.isFileURL {
-        do {
-          let data = try Data(contentsOf: path)
-          let image = UIImage(data: data)
-          return image
-        } catch {
-          logger.warning("failed to load image: \(filename), \(error)")
-          return nil
-        }
-      }
-      return nil
-    }
-
-    return nil
-  }()
-
-  var subtitle: String? {
-    if let cString = dc_chat_get_subtitle(chatPointer) {
-      let str = String(cString: cString)
-      return str == "" ? nil : str
-    }
-    return nil
-  }
-
-  init(id: Int) {
-    if let p = dc_get_chat(mailboxPointer, UInt32(id)) {
-      chatPointer = p
-    } else {
-      fatalError("Invalid chatID opened \(id)")
-    }
-  }
-
-  deinit {
-    dc_chat_unref(chatPointer)
-  }
+    var chatPointer: OpaquePointer
+
+    var id: Int {
+        return Int(dc_chat_get_id(chatPointer))
+    }
+
+    var name: String {
+        return String(cString: dc_chat_get_name(chatPointer))
+    }
+
+    var type: Int {
+        return Int(dc_chat_get_type(chatPointer))
+    }
+
+    var chatType: ChatType {
+        return ChatType(rawValue: type) ?? ChatType.GROUP // group as fallback - shouldn't get here
+    }
+
+    var color: UIColor {
+        return UIColor(netHex: Int(dc_chat_get_color(chatPointer)))
+    }
+
+    var isVerified: Bool {
+        return dc_chat_is_verified(chatPointer) > 0
+    }
+
+    var contactIds: [Int] {
+        return Utils.copyAndFreeArray(inputArray: dc_get_chat_contacts(mailboxPointer, UInt32(id)))
+    }
+
+    lazy var profileImage: UIImage? = { [unowned self] in
+        let file = dc_chat_get_profile_image(chatPointer)
+        if let cFile = file {
+            let filename = String(cString: cFile)
+            let path: URL = URL(fileURLWithPath: filename, isDirectory: false)
+            if path.isFileURL {
+                do {
+                    let data = try Data(contentsOf: path)
+                    let image = UIImage(data: data)
+                    return image
+                } catch {
+                    logger.warning("failed to load image: \(filename), \(error)")
+                    return nil
+                }
+            }
+            return nil
+        }
+
+        return nil
+    }()
+
+    var subtitle: String? {
+        if let cString = dc_chat_get_subtitle(chatPointer) {
+            let str = String(cString: cString)
+            return str == "" ? nil : str
+        }
+        return nil
+    }
+
+    init(id: Int) {
+        if let p = dc_get_chat(mailboxPointer, UInt32(id)) {
+            chatPointer = p
+        } else {
+            fatalError("Invalid chatID opened \(id)")
+        }
+    }
+
+    deinit {
+        dc_chat_unref(chatPointer)
+    }
 }
 
 class MRPoorText {
-  private var poorTextPointer: OpaquePointer
+    private var poorTextPointer: OpaquePointer
 
-  var text1: String? {
-    let text1 = dc_lot_get_text1(poorTextPointer)
-    if text1 == nil {
-      return nil
+    var text1: String? {
+        let text1 = dc_lot_get_text1(poorTextPointer)
+        if text1 == nil {
+            return nil
+        }
+        return String(cString: text1!)
     }
-    return String(cString: text1!)
-  }
 
-  var text2: String? {
-    let text2 = dc_lot_get_text2(poorTextPointer)
-    if text2 == nil {
-      return nil
+    var text2: String? {
+        let text2 = dc_lot_get_text2(poorTextPointer)
+        if text2 == nil {
+            return nil
+        }
+        return String(cString: text2!)
     }
-    return String(cString: text2!)
-  }
 
-  var text1Meaning: Int {
-    return Int(dc_lot_get_text1_meaning(poorTextPointer))
-  }
+    var text1Meaning: Int {
+        return Int(dc_lot_get_text1_meaning(poorTextPointer))
+    }
 
-  var timeStamp: Int {
-    return Int(dc_lot_get_timestamp(poorTextPointer))
-  }
+    var timeStamp: Int {
+        return Int(dc_lot_get_timestamp(poorTextPointer))
+    }
 
-  var state: Int {
-    return Int(dc_lot_get_state(poorTextPointer))
-  }
+    var state: Int {
+        return Int(dc_lot_get_state(poorTextPointer))
+    }
 
-  // takes ownership of specified pointer
-  init(poorTextPointer: OpaquePointer) {
-    self.poorTextPointer = poorTextPointer
-  }
+    // takes ownership of specified pointer
+    init(poorTextPointer: OpaquePointer) {
+        self.poorTextPointer = poorTextPointer
+    }
 
-  deinit {
-    dc_lot_unref(poorTextPointer)
-  }
+    deinit {
+        dc_lot_unref(poorTextPointer)
+    }
 }
 
 class MRChatList {
-  private var chatListPointer: OpaquePointer
+    private var chatListPointer: OpaquePointer
 
-  var length: Int {
-    return dc_chatlist_get_cnt(chatListPointer)
-    // return Int(chatListPointer.pointee.m_cnt)
-  }
+    var length: Int {
+        return dc_chatlist_get_cnt(chatListPointer)
+        // return Int(chatListPointer.pointee.m_cnt)
+    }
 
-  // takes ownership of specified pointer
-  init(chatListPointer: OpaquePointer) {
-    self.chatListPointer = chatListPointer
-  }
+    // takes ownership of specified pointer
+    init(chatListPointer: OpaquePointer) {
+        self.chatListPointer = chatListPointer
+    }
 
-  func getChatId(index: Int) -> Int {
-    return Int(dc_chatlist_get_chat_id(chatListPointer, index))
-  }
+    func getChatId(index: Int) -> Int {
+        return Int(dc_chatlist_get_chat_id(chatListPointer, index))
+    }
 
-  func getMessageId(index: Int) -> Int {
-    return Int(dc_chatlist_get_msg_id(chatListPointer, index))
-  }
+    func getMessageId(index: Int) -> Int {
+        return Int(dc_chatlist_get_msg_id(chatListPointer, index))
+    }
 
-  func summary(index: Int) -> MRPoorText {
-    guard let poorTextPointer = dc_chatlist_get_summary(self.chatListPointer, index, nil) else {
-      fatalError("poor text pointer was nil")
+    func summary(index: Int) -> MRPoorText {
+        guard let poorTextPointer = dc_chatlist_get_summary(self.chatListPointer, index, nil) else {
+            fatalError("poor text pointer was nil")
+        }
+        return MRPoorText(poorTextPointer: poorTextPointer)
     }
-    return MRPoorText(poorTextPointer: poorTextPointer)
-  }
 
-  deinit {
-    dc_chatlist_unref(chatListPointer)
-  }
+    deinit {
+        dc_chatlist_unref(chatListPointer)
+    }
 }
 
 func strToBool(_ value: String?) -> Bool {
-  if let vStr = value {
-    if let vInt = Int(vStr) {
-      return vInt == 1
+    if let vStr = value {
+        if let vInt = Int(vStr) {
+            return vInt == 1
+        }
+        return false
     }
-    return false
-  }
 
-  return false
+    return false
 }
 
 class MRConfig {
-  private class func getOptStr(_ key: String) -> String? {
-    let p = dc_get_config(mailboxPointer, key)
+    private class func getOptStr(_ key: String) -> String? {
+        let p = dc_get_config(mailboxPointer, key)
+
+        if let pSafe = p {
+            let c = String(cString: pSafe)
+            if c.isEmpty {
+                return nil
+            }
+            return c
+        }
 
-    if let pSafe = p {
-      let c = String(cString: pSafe)
-      if c.isEmpty {
         return nil
-      }
-      return c
     }
 
-    return nil
-  }
+    private class func setOptStr(_ key: String, _ value: String?) {
+        if let v = value {
+            dc_set_config(mailboxPointer, key, v)
+        }
+        else {
+            dc_set_config(mailboxPointer, key, nil)
+        }
+    }
+
+    private class func getBool(_ key: String) -> Bool {
+        return strToBool(getOptStr(key))
+    }
+
+    private class func setBool(_ key: String, _ value: Bool) {
+        let vStr = value ? "1" : "0"
+        setOptStr(key, vStr)
+    }
+
+    private class func getInt(_ key: String) -> Int {
+        let vStr = getOptStr(key)
+        if vStr == nil {
+            return 0
+        }
+        let vInt = Int(vStr!)
+        if vInt == nil {
+            return 0
+        }
+        return vInt!
+    }
+
+    private class func setInt(_ key: String, _ value: Int) {
+        setOptStr(key, String(value))
+    }
+
+    /**
+     *  Address to display (always needed)
+     */
+    class var addr: String? {
+        set {
+            setOptStr("addr", newValue)
+        }
+        get {
+            return getOptStr("addr")
+        }
+    }
+
+    /**
+     *  IMAP-server, guessed if left out
+     */
+    class var mailServer: String? {
+        set {
+            setOptStr("mail_server", newValue)
+        }
+        get {
+            return getOptStr("mail_server")
+        }
+    }
+
+    /**
+     *  IMAP-username, guessed if left out
+     */
+    class var mailUser: String? {
+        set {
+            setOptStr("mail_user", newValue)
+        }
+        get {
+            return getOptStr("mail_user")
+        }
+    }
+
+    /**
+     *  IMAP-password (always needed)
+     */
+    class var mailPw: String? {
+        set {
+            setOptStr("mail_pw", newValue)
+        }
+        get {
+            return getOptStr("mail_pw")
+        }
+    }
+
+    /**
+     *  IMAP-port, guessed if left out
+     */
+    class var mailPort: String? {
+        set {
+            setOptStr("mail_port", newValue)
+        }
+        get {
+            return getOptStr("mail_port")
+        }
+    }
+
+    /**
+     *  SMTP-server, guessed if left out
+     */
+    class var sendServer: String? {
+        set {
+            setOptStr("send_server", newValue)
+        }
+        get {
+            return getOptStr("send_server")
+        }
+    }
 
-  private class func setOptStr(_ key: String, _ value: String?) {
-    if let v = value {
-      dc_set_config(mailboxPointer, key, v)
+    /**
+     *  SMTP-user, guessed if left out
+     */
+    class var sendUser: String? {
+        set {
+            setOptStr("send_user", newValue)
+        }
+        get {
+            return getOptStr("send_user")
+        }
     }
-    else {
-      dc_set_config(mailboxPointer, key, nil)
+
+    /**
+     *  SMTP-password, guessed if left out
+     */
+    class var sendPw: String? {
+        set {
+            setOptStr("send_pw", newValue)
+        }
+        get {
+            return getOptStr("send_pw")
+        }
     }
-  }
 
-  private class func getBool(_ key: String) -> Bool {
-    return strToBool(getOptStr(key))
-  }
+    /**
+     * SMTP-port, guessed if left out
+     */
+    class var sendPort: String? {
+        set {
+            setOptStr("send_port", newValue)
+        }
+        get {
+            return getOptStr("send_port")
+        }
+    }
 
-  private class func setBool(_ key: String, _ value: Bool) {
-    let vStr = value ? "1" : "0"
-    setOptStr(key, vStr)
-  }
+    /**
+     * IMAP-/SMTP-flags as a combination of DC_LP flags, guessed if left out
+     */
 
-  private class func getInt(_ key: String) -> Int {
-    let vStr = getOptStr(key)
-    if vStr == nil {
-      return 0
+    private class var serverFlags: Int {
+        set {
+            setOptStr("server_flags", "\(newValue)")
+        }
+        get {
+            if let str = getOptStr("server_flags") {
+                return Int(str) ?? 0
+            } else {
+                return 0
+            }
+        }
     }
-    let vInt = Int(vStr!)
-    if vInt == nil {
-      return 0
+
+    class func setImapSecurity(imapFlags flags: Int) {
+        var sf = serverFlags
+        sf = sf & ~0x700 // TODO: should be DC_LP_IMAP_SOCKET_FLAGS - could not be found
+        sf = sf | flags
+        serverFlags = sf
     }
-    return vInt!
-  }
 
-  private class func setInt(_ key: String, _ value: Int) {
-    setOptStr(key, String(value))
-  }
+    class func setSmtpSecurity(smptpFlags flags: Int) {
+        var sf = serverFlags
+        sf = sf & ~0x70000 // TODO: should be DC_LP_SMTP_SOCKET_FLAGS - could not be found
+        sf = sf | flags
+        serverFlags = sf
+    }
 
-  /**
-   *  Address to display (always needed)
-   */
-  class var addr: String? {
-    set {
-      setOptStr("addr", newValue)
+    class func setAuthFlags(flags: Int) {
+        var sf = serverFlags
+        sf = sf & ~0x6 // TODO: should be DC_LP_AUTH_FLAGS - could not be found
+        sf = sf | flags
+        serverFlags = sf
     }
-    get {
-      return getOptStr("addr")
+
+    // returns one of DC_LP_IMAP_SOCKET_STARTTLS, DC_LP_IMAP_SOCKET_SSL,
+    class func getImapSecurity() -> Int {
+        var sf = serverFlags
+        sf = sf & 0x700
+        return sf
     }
-  }
 
-  /**
-   *  IMAP-server, guessed if left out
-   */
-  class var mailServer: String? {
-    set {
-      setOptStr("mail_server", newValue)
+    // returns one of DC_LP_SMTP_SOCKET_STARTTLS, DC_LP_SMTP_SOCKET_SSL,
+    class func getSmtpSecurity() -> Int {
+        var sf = serverFlags
+        sf = sf & 0x70000
+        return sf
     }
-    get {
-      return getOptStr("mail_server")
+
+    // returns on of DC_LP_AUTH_OAUTH2 or 0
+    class func getAuthFlags() -> Int {
+        var sf = serverFlags
+        sf = sf & 0x6
+        serverFlags = sf
+        return sf
     }
-  }
 
-  /**
-   *  IMAP-username, guessed if left out
-   */
-  class var mailUser: String? {
-    set {
-      setOptStr("mail_user", newValue)
+    /**
+     * Own name to use when sending messages. MUAs are allowed to spread this way eg. using CC, defaults to empty
+     */
+    class var displayname: String? {
+        set {
+            setOptStr("displayname", newValue)
+        }
+        get {
+            return getOptStr("displayname")
+        }
     }
-    get {
-      return getOptStr("mail_user")
+
+    /**
+     * Own status to display eg. in email footers, defaults to a standard text
+     */
+    class var selfstatus: String? {
+        set {
+            setOptStr("selfstatus", newValue)
+        }
+        get {
+            return getOptStr("selfstatus")
+        }
     }
-  }
 
-  /**
-   *  IMAP-password (always needed)
-   */
-  class var mailPw: String? {
-    set {
-      setOptStr("mail_pw", newValue)
-    }
-    get {
-      return getOptStr("mail_pw")
+    /**
+     * File containing avatar. Will be copied to blob directory. NULL to remove the avatar. It is planned for future versions to send this image together with the next messages.
+     */
+    class var selfavatar: String? {
+        set {
+            setOptStr("selfavatar", newValue)
+        }
+        get {
+            return getOptStr("selfavatar")
+        }
     }
-  }
-
-  /**
-   *  IMAP-port, guessed if left out
-   */
-  class var mailPort: String? {
-    set {
-      setOptStr("mail_port", newValue)
-    }
-    get {
-      return getOptStr("mail_port")
-    }
-  }
-
-  /**
-   *  SMTP-server, guessed if left out
-   */
-  class var sendServer: String? {
-    set {
-      setOptStr("send_server", newValue)
-    }
-    get {
-      return getOptStr("send_server")
-    }
-  }
-
-  /**
-   *  SMTP-user, guessed if left out
-   */
-  class var sendUser: String? {
-    set {
-      setOptStr("send_user", newValue)
-    }
-    get {
-      return getOptStr("send_user")
-    }
-  }
-
-  /**
-   *  SMTP-password, guessed if left out
-   */
-  class var sendPw: String? {
-    set {
-      setOptStr("send_pw", newValue)
-    }
-    get {
-      return getOptStr("send_pw")
-    }
-  }
-
-  /**
-   * SMTP-port, guessed if left out
-   */
-  class var sendPort: String? {
-    set {
-      setOptStr("send_port", newValue)
-    }
-    get {
-      return getOptStr("send_port")
-    }
-  }
-
-  /**
-   * IMAP-/SMTP-flags as a combination of DC_LP flags, guessed if left out
-   */
-
-  private class var serverFlags: Int {
-    set {
-      setOptStr("server_flags", "\(newValue)")
-    }
-    get {
-      if let str = getOptStr("server_flags") {
-        return Int(str) ?? 0
-      } else {
-        return 0
-      }
-    }
-  }
-
-  class func setImapSecurity(imapFlags flags: Int) {
-    var sf = serverFlags
-    sf = sf & ~0x700 // TODO: should be DC_LP_IMAP_SOCKET_FLAGS - could not be found
-    sf = sf | flags
-    serverFlags = sf
-  }
-
-  class func setSmtpSecurity(smptpFlags flags: Int) {
-    var sf = serverFlags
-    sf = sf & ~0x70000 // TODO: should be DC_LP_SMTP_SOCKET_FLAGS - could not be found
-    sf = sf | flags
-    serverFlags = sf
-  }
-
-  class func setAuthFlags(flags: Int) {
-    var sf = serverFlags
-    sf = sf & ~0x6 // TODO: should be DC_LP_AUTH_FLAGS - could not be found
-    sf = sf | flags
-    serverFlags = sf
-  }
-
-  // returns one of DC_LP_IMAP_SOCKET_STARTTLS, DC_LP_IMAP_SOCKET_SSL,
-  class func getImapSecurity() -> Int {
-    var sf = serverFlags
-    sf = sf & 0x700
-    return sf
-  }
-
-  // returns one of DC_LP_SMTP_SOCKET_STARTTLS, DC_LP_SMTP_SOCKET_SSL,
-  class func getSmtpSecurity() -> Int {
-    var sf = serverFlags
-    sf = sf & 0x70000
-    return sf
-  }
-
-  // returns on of DC_LP_AUTH_OAUTH2 or 0
-  class func getAuthFlags() -> Int {
-    var sf = serverFlags
-    sf = sf & 0x6
-    serverFlags = sf
-    return sf
-  }
-
-  /**
-   * Own name to use when sending messages. MUAs are allowed to spread this way eg. using CC, defaults to empty
-   */
-  class var displayname: String? {
-    set {
-      setOptStr("displayname", newValue)
-    }
-    get {
-      return getOptStr("displayname")
-    }
-  }
-
-  /**
-   * Own status to display eg. in email footers, defaults to a standard text
-   */
-  class var selfstatus: String? {
-    set {
-      setOptStr("selfstatus", newValue)
-    }
-    get {
-      return getOptStr("selfstatus")
-    }
-  }
-
-  /**
-   * File containing avatar. Will be copied to blob directory. NULL to remove the avatar. It is planned for future versions to send this image together with the next messages.
-   */
-  class var selfavatar: String? {
-    set {
-      setOptStr("selfavatar", newValue)
-    }
-    get {
-      return getOptStr("selfavatar")
-    }
-  }
-
-  /**
-   * 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
-   */
-  class var e2eeEnabled: Bool {
-    set {
-      setBool("e2ee_enabled", newValue)
-    }
-    get {
-      return getBool("e2ee_enabled")
-    }
-  }
-
-  /**
-   * 0=do not send or request read receipts, 1=send and request read receipts (default)
-   */
-  class var mdnsEnabled: Bool {
-    set {
-      setBool("mdns_enabled", newValue)
-    }
-    get {
-      return getBool("mdns_enabled")
-    }
-  }
-
-  /**
-   * 1=watch INBOX-folder for changes (default), 0=do not watch the INBOX-folder
-   */
-  class var inboxWatch: Bool {
-    set {
-      setBool("inbox_watch", newValue)
-    }
-    get {
-      return getBool("inbox_watch")
-    }
-  }
-
-  /**
-   * 1=watch Sent-folder for changes (default), 0=do not watch the Sent-folder
-   */
-  class var sentboxWatch: Bool {
-    set {
-      setBool("sentbox_watch", newValue)
-    }
-    get {
-      return getBool("sentbox_watch")
-    }
-  }
 
-  /**
-   * 1=watch DeltaChat-folder for changes (default), 0=do not watch the DeltaChat-folder
-   */
-  class var mvboxWatch: Bool {
-    set {
-      setBool("mvbox_watch", newValue)
+    /**
+     * 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
+     */
+    class var e2eeEnabled: Bool {
+        set {
+            setBool("e2ee_enabled", newValue)
+        }
+        get {
+            return getBool("e2ee_enabled")
+        }
     }
-    get {
-      return getBool("mvbox_watch")
+
+    /**
+     * 0=do not send or request read receipts, 1=send and request read receipts (default)
+     */
+    class var mdnsEnabled: Bool {
+        set {
+            setBool("mdns_enabled", newValue)
+        }
+        get {
+            return getBool("mdns_enabled")
+        }
     }
-  }
 
-  /**
-   * 1=heuristically detect chat-messages and move them to the DeltaChat-folder, 0=do not move chat-messages
-   */
-  class var mvboxMove: Bool {
-    set {
-      setBool("mvbox_move", newValue)
+    /**
+     * 1=watch INBOX-folder for changes (default), 0=do not watch the INBOX-folder
+     */
+    class var inboxWatch: Bool {
+        set {
+            setBool("inbox_watch", newValue)
+        }
+        get {
+            return getBool("inbox_watch")
+        }
     }
-    get {
-      return getBool("mvbox_move")
+
+    /**
+     * 1=watch Sent-folder for changes (default), 0=do not watch the Sent-folder
+     */
+    class var sentboxWatch: Bool {
+        set {
+            setBool("sentbox_watch", newValue)
+        }
+        get {
+            return getBool("sentbox_watch")
+        }
     }
-  }
 
-  /**
-   *  DC_SHOW_EMAILS_OFF (0)= show direct replies to chats only (default),
-   *  DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)= also show all mails of confirmed contacts,
-   *  DC_SHOW_EMAILS_ALL (2)= also show mails of unconfirmed contacts in the deaddrop.
-   */
-  class var showEmails: Bool {
-    set {
-      setBool("show_emails", newValue)
+    /**
+     * 1=watch DeltaChat-folder for changes (default), 0=do not watch the DeltaChat-folder
+     */
+    class var mvboxWatch: Bool {
+        set {
+            setBool("mvbox_watch", newValue)
+        }
+        get {
+            return getBool("mvbox_watch")
+        }
     }
-    get {
-      return getBool("show_emails")
+
+    /**
+     * 1=heuristically detect chat-messages and move them to the DeltaChat-folder, 0=do not move chat-messages
+     */
+    class var mvboxMove: Bool {
+        set {
+            setBool("mvbox_move", newValue)
+        }
+        get {
+            return getBool("mvbox_move")
+        }
     }
-  }
 
-  /**
-   * 1=save mime headers and make dc_get_mime_headers() work for subsequent calls, 0=do not save mime headers (default)
-   */
-  class var saveMimeHeaders: Bool {
-    set {
-      setBool("save_mime_headers", newValue)
+    /**
+     *  DC_SHOW_EMAILS_OFF (0)= show direct replies to chats only (default),
+     *  DC_SHOW_EMAILS_ACCEPTED_CONTACTS (1)= also show all mails of confirmed contacts,
+     *  DC_SHOW_EMAILS_ALL (2)= also show mails of unconfirmed contacts in the deaddrop.
+     */
+    class var showEmails: Bool {
+        set {
+            setBool("show_emails", newValue)
+        }
+        get {
+            return getBool("show_emails")
+        }
     }
-    get {
-      return getBool("save_mime_headers")
+
+    /**
+     * 1=save mime headers and make dc_get_mime_headers() work for subsequent calls, 0=do not save mime headers (default)
+     */
+    class var saveMimeHeaders: Bool {
+        set {
+            setBool("save_mime_headers", newValue)
+        }
+        get {
+            return getBool("save_mime_headers")
+        }
     }
-  }
 
-  class var configuredEmail: String {
-    get {
-      return getOptStr("configured_addr") ?? ""
+    class var configuredEmail: String {
+        get {
+            return getOptStr("configured_addr") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredMailServer: String {
-    get {
-      return getOptStr("configured_mail_server") ?? ""
+    class var configuredMailServer: String {
+        get {
+            return getOptStr("configured_mail_server") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredMailUser: String {
-    get {
-      return getOptStr("configured_mail_user") ?? ""
+    class var configuredMailUser: String {
+        get {
+            return getOptStr("configured_mail_user") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredMailPw: String {
-    get {
-      return getOptStr("configured_mail_pw") ?? ""
+    class var configuredMailPw: String {
+        get {
+            return getOptStr("configured_mail_pw") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredMailPort: String {
-    get {
-      return getOptStr("configured_mail_port") ?? ""
+    class var configuredMailPort: String {
+        get {
+            return getOptStr("configured_mail_port") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredSendServer: String {
-    get {
-      return getOptStr("configured_send_server") ?? ""
+    class var configuredSendServer: String {
+        get {
+            return getOptStr("configured_send_server") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredSendUser: String {
-    get {
-      return getOptStr("configured_send_user") ?? ""
+    class var configuredSendUser: String {
+        get {
+            return getOptStr("configured_send_user") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredSendPw: String {
-    get {
-      return getOptStr("configured_send_pw") ?? ""
+    class var configuredSendPw: String {
+        get {
+            return getOptStr("configured_send_pw") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredSendPort: String {
-    get {
-      return getOptStr("configured_send_port") ?? ""
+    class var configuredSendPort: String {
+        get {
+            return getOptStr("configured_send_port") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  class var configuredServerFlags: String {
-    get {
-      return getOptStr("configured_server_flags") ?? ""
+    class var configuredServerFlags: String {
+        get {
+            return getOptStr("configured_server_flags") ?? ""
+        }
+        set {}
     }
-    set {}
-  }
 
-  /**
-   * Was configured executed beforeß
-   */
-  class var configured: Bool {
-    get {
-      return getBool("configured")
+    /**
+     * Was configured executed beforeß
+     */
+    class var configured: Bool {
+        get {
+            return getBool("configured")
+        }
+        set {}
     }
-    set {}
-  }
 }
 

+ 162 - 162
deltachat-ios/DC/events.swift

@@ -13,170 +13,170 @@ let dcNotificationViewChat = Notification.Name(rawValue: "MrEventViewChat")
 @_silgen_name("callbackSwift")
 
 public func callbackSwift(event: CInt, data1: CUnsignedLong, data2: CUnsignedLong, data1String: UnsafePointer<Int8>, data2String: UnsafePointer<Int8>) -> UnsafePointer<Int8>? {
-  switch event {
-  case DC_EVENT_INFO:
-    let s = String(cString: data2String)
-    logger.info("event: \(s)")
-  case DC_EVENT_WARNING:
-    let s = String(cString: data2String)
-    logger.warning("event: \(s)")
-  case DC_EVENT_ERROR:
-    let s = String(cString: data2String)
-    AppDelegate.lastErrorDuringConfig = s
-    logger.error("event: \(s)")
-  // TODO:
-  // check online state, return
-  // - 0 when online
-  // - 1 when offline
-  case DC_EVENT_CONFIGURE_PROGRESS:
-    logger.info("configure progress: \(Int(data1)) \(Int(data2))")
-    let nc = NotificationCenter.default
-    DispatchQueue.main.async {
-      let done = Int(data1) == 1000
-
-      nc.post(
-        name: dcNotificationConfigureProgress,
-        object: nil,
-        userInfo: [
-          "progress": Int(data1),
-          "error": Int(data1) == 0,
-          "done": done,
-          "errorMessage": AppDelegate.lastErrorDuringConfig,
+    switch event {
+    case DC_EVENT_INFO:
+        let s = String(cString: data2String)
+        logger.info("event: \(s)")
+    case DC_EVENT_WARNING:
+        let s = String(cString: data2String)
+        logger.warning("event: \(s)")
+    case DC_EVENT_ERROR:
+        let s = String(cString: data2String)
+        AppDelegate.lastErrorDuringConfig = s
+        logger.error("event: \(s)")
+    // TODO:
+    // check online state, return
+    // - 0 when online
+    // - 1 when offline
+    case DC_EVENT_CONFIGURE_PROGRESS:
+        logger.info("configure progress: \(Int(data1)) \(Int(data2))")
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            let done = Int(data1) == 1000
+
+            nc.post(
+                name: dcNotificationConfigureProgress,
+                object: nil,
+                userInfo: [
+                    "progress": Int(data1),
+                    "error": Int(data1) == 0,
+                    "done": done,
+                    "errorMessage": AppDelegate.lastErrorDuringConfig,
+                ]
+            )
+
+            if done {
+                UserDefaults.standard.set(true, forKey: Constants.Keys.deltachatUserProvidedCredentialsKey)
+                UserDefaults.standard.synchronize()
+                let appDelegate = UIApplication.shared.delegate as! AppDelegate
+                // appDelegate.appCoordinator?.showTab(index: 3)
+                AppDelegate.lastErrorDuringConfig = nil
+            }
+        }
+    case DC_EVENT_ERROR_NETWORK:
+        let msg = String(cString: data2String)
+        if data1 == 1 {
+            AppDelegate.lastErrorDuringConfig = msg
+            logger.error("network: \(msg)")
+        } else {
+            logger.warning("network: \(msg)")
+        }
+
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            DispatchQueue.main.async {
+                nc.post(name: dcNotificationStateChanged,
+                        object: nil,
+                        userInfo: ["state": "offline"])
+            }
+        }
+    case DC_EVENT_IMAP_CONNECTED, DC_EVENT_SMTP_CONNECTED:
+        logger.warning("network: \(String(cString: data2String))")
+
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            nc.post(name: dcNotificationStateChanged,
+                    object: nil,
+                    userInfo: ["state": "online"])
+        }
+    case DC_EVENT_MSGS_CHANGED, DC_EVENT_MSG_READ, DC_EVENT_MSG_DELIVERED:
+        logger.info("change: \(event)")
+
+        let nc = NotificationCenter.default
+
+        DispatchQueue.main.async {
+            nc.post(
+                name: dcNotificationChanged,
+                object: nil,
+                userInfo: [
+                    "message_id": Int(data2),
+                    "chat_id": Int(data1),
+                    "date": Date(),
+                ]
+            )
+        }
+    case DC_EVENT_INCOMING_MSG:
+        let nc = NotificationCenter.default
+        let userInfo = [
+            "message_id": Int(data2),
+            "chat_id": Int(data1),
         ]
-      )
-
-      if done {
-        UserDefaults.standard.set(true, forKey: Constants.Keys.deltachatUserProvidedCredentialsKey)
-        UserDefaults.standard.synchronize()
-        let appDelegate = UIApplication.shared.delegate as! AppDelegate
-       // appDelegate.appCoordinator?.showTab(index: 3)
-        AppDelegate.lastErrorDuringConfig = nil
-      }
-    }
-  case DC_EVENT_ERROR_NETWORK:
-    let msg = String(cString: data2String)
-    if data1 == 1 {
-      AppDelegate.lastErrorDuringConfig = msg
-      logger.error("network: \(msg)")
-    } else {
-      logger.warning("network: \(msg)")
-    }
 
-    let nc = NotificationCenter.default
-    DispatchQueue.main.async {
-      DispatchQueue.main.async {
-        nc.post(name: dcNotificationStateChanged,
+        DispatchQueue.main.async {
+            nc.post(name: dcNotificationIncoming,
+                    object: nil,
+                    userInfo: userInfo)
+
+            let content = UNMutableNotificationContent()
+            let msg = MRMessage(id: Int(data2))
+            content.title = msg.fromContact.name
+            content.body = msg.summary(chars: 40) ?? ""
+            content.badge = 1
+            content.userInfo = userInfo
+            content.sound = .default
+
+            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
+
+            let request = UNNotificationRequest(identifier: Constants.notificationIdentifier, content: content, trigger: trigger)
+            UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
+            logger.info("notifications: added \(content)")
+        }
+    case DC_EVENT_SMTP_MESSAGE_SENT:
+        logger.info("network: \(String(cString: data2String))")
+    case DC_EVENT_MSG_DELIVERED:
+        logger.info("message delivered: \(data1)-\(data2)")
+    case DC_EVENT_IMEX_PROGRESS:
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            nc.post(
+                name: dcNotificationBackupProgress,
                 object: nil,
-                userInfo: ["state": "offline"])
-      }
-    }
-  case DC_EVENT_IMAP_CONNECTED, DC_EVENT_SMTP_CONNECTED:
-    logger.warning("network: \(String(cString: data2String))")
-
-    let nc = NotificationCenter.default
-    DispatchQueue.main.async {
-      nc.post(name: dcNotificationStateChanged,
-              object: nil,
-              userInfo: ["state": "online"])
-    }
-  case DC_EVENT_MSGS_CHANGED, DC_EVENT_MSG_READ, DC_EVENT_MSG_DELIVERED:
-    logger.info("change: \(event)")
-
-    let nc = NotificationCenter.default
-
-    DispatchQueue.main.async {
-      nc.post(
-        name: dcNotificationChanged,
-        object: nil,
-        userInfo: [
-          "message_id": Int(data2),
-          "chat_id": Int(data1),
-          "date": Date(),
-        ]
-      )
-    }
-  case DC_EVENT_INCOMING_MSG:
-    let nc = NotificationCenter.default
-    let userInfo = [
-      "message_id": Int(data2),
-      "chat_id": Int(data1),
-    ]
-
-    DispatchQueue.main.async {
-      nc.post(name: dcNotificationIncoming,
-              object: nil,
-              userInfo: userInfo)
-
-      let content = UNMutableNotificationContent()
-      let msg = MRMessage(id: Int(data2))
-      content.title = msg.fromContact.name
-      content.body = msg.summary(chars: 40) ?? ""
-      content.badge = 1
-      content.userInfo = userInfo
-      content.sound = .default
-
-      let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.1, repeats: false)
-
-      let request = UNNotificationRequest(identifier: Constants.notificationIdentifier, content: content, trigger: trigger)
-      UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
-      logger.info("notifications: added \(content)")
-    }
-  case DC_EVENT_SMTP_MESSAGE_SENT:
-    logger.info("network: \(String(cString: data2String))")
-  case DC_EVENT_MSG_DELIVERED:
-    logger.info("message delivered: \(data1)-\(data2)")
-  case DC_EVENT_IMEX_PROGRESS:
-    let nc = NotificationCenter.default
-    DispatchQueue.main.async {
-      nc.post(
-        name: dcNotificationBackupProgress,
-        object: nil,
-        userInfo: [
-          "progress": Int(data1),
-          "error": Int(data1) == 0,
-          "done": Int(data1) == 1000,
-          "errorMessage": AppDelegate.lastErrorDuringConfig,
-        ]
-      )
-    }
-  case DC_EVENT_IMEX_FILE_WRITTEN:
-    logger.info("backup file written: \(String(cString: data1String))")
-
-  case DC_EVENT_SECUREJOIN_INVITER_PROGRESS:
-    logger.info("securejoin inviter progress \(data1)")
-
-    let nc = NotificationCenter.default
-    DispatchQueue.main.async {
-      nc.post(
-        name: dcNotificationSecureInviterProgress,
-        object: nil,
-        userInfo: [
-          "progress": Int(data2),
-          "error": Int(data2) == 0,
-          "done": Int(data2) == 1000,
-        ]
-      )
-    }
-  case DC_EVENT_SECUREJOIN_JOINER_PROGRESS:
-    logger.info("securejoin joiner progress \(data1)")
-    let nc = NotificationCenter.default
-    DispatchQueue.main.async {
-      nc.post(
-        name: dcNotificationSecureJoinerProgress,
-        object: nil,
-        userInfo: [
-          "progress": Int(data2),
-          "error": Int(data2) == 0,
-          "done": Int(data2) == 1000,
-        ]
-      )
+                userInfo: [
+                    "progress": Int(data1),
+                    "error": Int(data1) == 0,
+                    "done": Int(data1) == 1000,
+                    "errorMessage": AppDelegate.lastErrorDuringConfig,
+                ]
+            )
+        }
+    case DC_EVENT_IMEX_FILE_WRITTEN:
+        logger.info("backup file written: \(String(cString: data1String))")
+
+    case DC_EVENT_SECUREJOIN_INVITER_PROGRESS:
+        logger.info("securejoin inviter progress \(data1)")
+
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            nc.post(
+                name: dcNotificationSecureInviterProgress,
+                object: nil,
+                userInfo: [
+                    "progress": Int(data2),
+                    "error": Int(data2) == 0,
+                    "done": Int(data2) == 1000,
+                ]
+            )
+        }
+    case DC_EVENT_SECUREJOIN_JOINER_PROGRESS:
+        logger.info("securejoin joiner progress \(data1)")
+        let nc = NotificationCenter.default
+        DispatchQueue.main.async {
+            nc.post(
+                name: dcNotificationSecureJoinerProgress,
+                object: nil,
+                userInfo: [
+                    "progress": Int(data2),
+                    "error": Int(data2) == 0,
+                    "done": Int(data2) == 1000,
+                ]
+            )
+        }
+    case DC_EVENT_GET_STRING:
+        // nothing to do for now
+        break
+    default:
+        logger.warning("unknown event: \(event)")
     }
-  case DC_EVENT_GET_STRING:
-    // nothing to do for now
-    break
-  default:
-    logger.warning("unknown event: \(event)")
-  }
-
-  return nil
+
+    return nil
 }

+ 58 - 58
deltachat-ios/Handler/DeviceContactsHandler.swift

@@ -2,73 +2,73 @@ import Contacts
 import UIKit
 
 class DeviceContactsHandler {
-  private let store = CNContactStore()
-  weak var contactListDelegate: ContactListDelegate?
+    private let store = CNContactStore()
+    weak var contactListDelegate: ContactListDelegate?
 
-  private func makeContactString(contacts: [CNContact]) -> String {
-    var contactString: String = ""
-    for contact in contacts {
-      let displayName: String = "\(contact.givenName) \(contact.familyName)"
-      // cnContact can have multiple email addresses -> create contact for each email address
-      for emailAddress in contact.emailAddresses {
-        contactString += "\(displayName)\n\(emailAddress.value)\n"
-      }
+    private func makeContactString(contacts: [CNContact]) -> String {
+        var contactString: String = ""
+        for contact in contacts {
+            let displayName: String = "\(contact.givenName) \(contact.familyName)"
+            // cnContact can have multiple email addresses -> create contact for each email address
+            for emailAddress in contact.emailAddresses {
+                contactString += "\(displayName)\n\(emailAddress.value)\n"
+            }
+        }
+        return contactString
     }
-    return contactString
-  }
 
-  private func addContactsToCore() {
-    let storedContacts = fetchContactsWithEmailFromDevice()
-    let contactString = makeContactString(contacts: storedContacts)
-    dc_add_address_book(mailboxPointer, contactString)
-    contactListDelegate?.deviceContactsImported()
-  }
+    private func addContactsToCore() {
+        let storedContacts = fetchContactsWithEmailFromDevice()
+        let contactString = makeContactString(contacts: storedContacts)
+        dc_add_address_book(mailboxPointer, contactString)
+        contactListDelegate?.deviceContactsImported()
+    }
 
-  private func fetchContactsWithEmailFromDevice() -> [CNContact] {
-    var fetchedContacts: [CNContact] = []
+    private func fetchContactsWithEmailFromDevice() -> [CNContact] {
+        var fetchedContacts: [CNContact] = []
 
-    // takes id from userDefaults (system settings)
-    let defaultContainerId = store.defaultContainerIdentifier()
-    let predicates = CNContact.predicateForContactsInContainer(withIdentifier: defaultContainerId)
-    let keys = [CNContactFamilyNameKey, CNContactGivenNameKey, CNContactEmailAddressesKey]
-    let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
-    request.mutableObjects = true
-    request.unifyResults = true
-    request.sortOrder = .userDefault
-    request.predicate = predicates
+        // takes id from userDefaults (system settings)
+        let defaultContainerId = store.defaultContainerIdentifier()
+        let predicates = CNContact.predicateForContactsInContainer(withIdentifier: defaultContainerId)
+        let keys = [CNContactFamilyNameKey, CNContactGivenNameKey, CNContactEmailAddressesKey]
+        let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
+        request.mutableObjects = true
+        request.unifyResults = true
+        request.sortOrder = .userDefault
+        request.predicate = predicates
 
-    do {
-      try store.enumerateContacts(with: request) { contact, _ in
-        if !contact.emailAddresses.isEmpty {
-          fetchedContacts.append(contact)
+        do {
+            try store.enumerateContacts(with: request) { contact, _ in
+                if !contact.emailAddresses.isEmpty {
+                    fetchedContacts.append(contact)
+                }
+            }
+        } catch {
+            print(error)
         }
-      }
-    } catch {
-      print(error)
+        return fetchedContacts
     }
-    return fetchedContacts
-  }
 
-  public func importDeviceContacts() {
-    switch CNContactStore.authorizationStatus(for: .contacts) {
-    case .authorized:
-      addContactsToCore()
-      contactListDelegate?.accessGranted()
-    case .denied:
-      contactListDelegate?.accessDenied()
-    case .restricted, .notDetermined:
-      store.requestAccess(for: .contacts) { [unowned self] granted, _ in
-        if granted {
-          DispatchQueue.main.async {
-            self.addContactsToCore()
-            self.contactListDelegate?.accessGranted()
-          }
-        } else {
-          DispatchQueue.main.async {
-            self.contactListDelegate?.accessDenied()
-          }
+    public func importDeviceContacts() {
+        switch CNContactStore.authorizationStatus(for: .contacts) {
+        case .authorized:
+            addContactsToCore()
+            contactListDelegate?.accessGranted()
+        case .denied:
+            contactListDelegate?.accessDenied()
+        case .restricted, .notDetermined:
+            store.requestAccess(for: .contacts) { [unowned self] granted, _ in
+                if granted {
+                    DispatchQueue.main.async {
+                        self.addContactsToCore()
+                        self.contactListDelegate?.accessGranted()
+                    }
+                } else {
+                    DispatchQueue.main.async {
+                        self.contactListDelegate?.accessDenied()
+                    }
+                }
+            }
         }
-      }
     }
-  }
 }

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

@@ -2,65 +2,65 @@ import JGProgressHUD
 import UIKit
 
 class HudHandler {
-	var backupHud: JGProgressHUD?
-	unowned var view: UIView
+    var backupHud: JGProgressHUD?
+    unowned var view: UIView
 
-	init(parentView: UIView) {
-		view = parentView
-	}
+    init(parentView: UIView) {
+        view = parentView
+    }
 
-	func setHudProgress(_ progress: Int) {
-		if let hud = self.backupHud {
-			hud.progress = Float(progress) / 1000.0
-			hud.detailTextLabel.text = "\(progress / 10)% Complete"
-		}
-	}
+    func setHudProgress(_ progress: Int) {
+        if let hud = self.backupHud {
+            hud.progress = Float(progress) / 1000.0
+            hud.detailTextLabel.text = "\(progress / 10)% Complete"
+        }
+    }
 
-	func showBackupHud(_ text: String) {
-		DispatchQueue.main.async {
-			let hud = JGProgressHUD(style: .dark)
-			hud.vibrancyEnabled = true
-			hud.indicatorView = JGProgressHUDPieIndicatorView()
-			hud.detailTextLabel.text = "0% Complete"
-			hud.textLabel.text = text
-			hud.show(in: self.view)
-			self.backupHud = hud
-		}
-	}
+    func showBackupHud(_ text: String) {
+        DispatchQueue.main.async {
+            let hud = JGProgressHUD(style: .dark)
+            hud.vibrancyEnabled = true
+            hud.indicatorView = JGProgressHUDPieIndicatorView()
+            hud.detailTextLabel.text = "0% Complete"
+            hud.textLabel.text = text
+            hud.show(in: self.view)
+            self.backupHud = hud
+        }
+    }
 
-	func setHudError(_ message: String?) {
-		if let hud = self.backupHud {
-			DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
-				UIView.animate(
-					withDuration: 0.1, animations: {
-						hud.textLabel.text = message ?? "Error"
-						hud.detailTextLabel.text = nil
-						hud.indicatorView = JGProgressHUDErrorIndicatorView()
-				}
-				)
-				hud.dismiss(afterDelay: 5.0)
-			}
-		}
-	}
+    func setHudError(_ message: String?) {
+        if let hud = self.backupHud {
+            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+                UIView.animate(
+                    withDuration: 0.1, animations: {
+                        hud.textLabel.text = message ?? "Error"
+                        hud.detailTextLabel.text = nil
+                        hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                    }
+                )
+                hud.dismiss(afterDelay: 5.0)
+            }
+        }
+    }
 
-	func setHudDone(callback: (() -> Void)?) {
-		let delay = 1.0
+    func setHudDone(callback: (() -> Void)?) {
+        let delay = 1.0
 
-		if let hud = self.backupHud {
-			DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
-				UIView.animate(
-					withDuration: 0.1, animations: {
-						hud.textLabel.text = "Success"
-						hud.detailTextLabel.text = nil
-						hud.indicatorView = JGProgressHUDSuccessIndicatorView()
-				}
-				)
-			}
+        if let hud = self.backupHud {
+            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+                UIView.animate(
+                    withDuration: 0.1, animations: {
+                        hud.textLabel.text = "Success"
+                        hud.detailTextLabel.text = nil
+                        hud.indicatorView = JGProgressHUDSuccessIndicatorView()
+                    }
+                )
+            }
 
-			DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
-				callback?()
-				hud.dismiss()
-			}
-		}
-	}
+            DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
+                callback?()
+                hud.dismiss()
+            }
+        }
+    }
 }

+ 38 - 38
deltachat-ios/Helper/Colors.swift

@@ -1,52 +1,52 @@
 import UIKit
 
 struct DCColors {
-  static let primary = UIColor.systemBlue
-  // static let accent =
+    static let primary = UIColor.systemBlue
+    // static let accent =
 
-  static let messagePrimaryColor = UIColor.rgb(red: 220, green: 248, blue: 198)
-  static let messageSecondaryColor = UIColor.rgb(red: 245, green: 245, blue: 245)
-  static let chatBackgroundColor = UIColor.rgb(red: 236, green: 229, blue: 221)
+    static let messagePrimaryColor = UIColor.rgb(red: 220, green: 248, blue: 198)
+    static let messageSecondaryColor = UIColor.rgb(red: 245, green: 245, blue: 245)
+    static let chatBackgroundColor = UIColor.rgb(red: 236, green: 229, blue: 221)
 }
 
 enum SystemColor {
-  case red
-  case orange
-  case yellow
-  case green
-  case tealBlue
-  case blue
-  case purple
-  case pink
+    case red
+    case orange
+    case yellow
+    case green
+    case tealBlue
+    case blue
+    case purple
+    case pink
 
-  var uiColor: UIColor {
-    switch self {
-    case .red:
-      return UIColor(red: 255 / 255, green: 59 / 255, blue: 48 / 255, alpha: 1)
-    case .orange:
-      return UIColor(red: 255 / 255, green: 149 / 255, blue: 0 / 255, alpha: 1)
-    case .yellow:
-      return UIColor(red: 255 / 255, green: 204 / 255, blue: 0 / 255, alpha: 1)
-    case .green:
-      return UIColor(red: 76 / 255, green: 217 / 255, blue: 100 / 255, alpha: 1)
-    case .tealBlue:
-      return UIColor(red: 90 / 255, green: 200 / 255, blue: 250 / 255, alpha: 1)
-    case .blue:
-      return UIColor(red: 0 / 255, green: 122 / 255, blue: 255 / 255, alpha: 1)
-    case .purple:
-      return UIColor(red: 88 / 255, green: 86 / 255, blue: 214 / 255, alpha: 1)
-    case .pink:
-      return UIColor(red: 255 / 255, green: 45 / 255, blue: 85 / 255, alpha: 1)
+    var uiColor: UIColor {
+        switch self {
+        case .red:
+            return UIColor(red: 255 / 255, green: 59 / 255, blue: 48 / 255, alpha: 1)
+        case .orange:
+            return UIColor(red: 255 / 255, green: 149 / 255, blue: 0 / 255, alpha: 1)
+        case .yellow:
+            return UIColor(red: 255 / 255, green: 204 / 255, blue: 0 / 255, alpha: 1)
+        case .green:
+            return UIColor(red: 76 / 255, green: 217 / 255, blue: 100 / 255, alpha: 1)
+        case .tealBlue:
+            return UIColor(red: 90 / 255, green: 200 / 255, blue: 250 / 255, alpha: 1)
+        case .blue:
+            return UIColor(red: 0 / 255, green: 122 / 255, blue: 255 / 255, alpha: 1)
+        case .purple:
+            return UIColor(red: 88 / 255, green: 86 / 255, blue: 214 / 255, alpha: 1)
+        case .pink:
+            return UIColor(red: 255 / 255, green: 45 / 255, blue: 85 / 255, alpha: 1)
+        }
     }
-  }
 }
 
 extension UIColor {
-  static func rgb(red: CGFloat, green: CGFloat, blue: CGFloat) -> UIColor {
-    return UIColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: 1)
-  }
+    static func rgb(red: CGFloat, green: CGFloat, blue: CGFloat) -> UIColor {
+        return UIColor(red: red / 255, green: green / 255, blue: blue / 255, alpha: 1)
+    }
 
-  static var systemBlue: UIColor {
-    return UIButton(type: .system).tintColor
-  }
+    static var systemBlue: UIColor {
+        return UIButton(type: .system).tintColor
+    }
 }

+ 13 - 13
deltachat-ios/Helper/Constants.swift

@@ -1,21 +1,21 @@
 import UIKit
 
 struct Constants {
-  // see: https://core.telegram.org/blackberry/chat
-  static let chatColors: [UIColor] = ["#ee4928", "#41a903", "#e09602", "#0f94ed", "#8f3bf7", "#fc4380", "#00a1c4", "#eb7002"].map { s in UIColor(hexString: s) }
-  struct Color {
-    static let bubble = UIColor(netHex: 0xEFFFDE)
-  }
+    // see: https://core.telegram.org/blackberry/chat
+    static let chatColors: [UIColor] = ["#ee4928", "#41a903", "#e09602", "#0f94ed", "#8f3bf7", "#fc4380", "#00a1c4", "#eb7002"].map { s in UIColor(hexString: s) }
+    struct Color {
+        static let bubble = UIColor(netHex: 0xEFFFDE)
+    }
 
-  struct Keys {
-    static let deltachatUserProvidedCredentialsKey = "__DELTACHAT_USER_PROVIDED_CREDENTIALS_KEY__"
-    static let deltachatImapEmailKey = "__DELTACHAT_IMAP_EMAIL_KEY__"
-    static let deltachatImapPasswordKey = "__DELTACHAT_IMAP_PASSWORD_KEY__"
-  }
+    struct Keys {
+        static let deltachatUserProvidedCredentialsKey = "__DELTACHAT_USER_PROVIDED_CREDENTIALS_KEY__"
+        static let deltachatImapEmailKey = "__DELTACHAT_IMAP_EMAIL_KEY__"
+        static let deltachatImapPasswordKey = "__DELTACHAT_IMAP_PASSWORD_KEY__"
+    }
 
-  static let defaultShadow = UIImage(color: UIColor(hexString: "ff2b82"), size: CGSize(width: 1, height: 1))
-  static let onlineShadow = UIImage(color: UIColor(hexString: "3ed67e"), size: CGSize(width: 1, height: 1))
+    static let defaultShadow = UIImage(color: UIColor(hexString: "ff2b82"), size: CGSize(width: 1, height: 1))
+    static let onlineShadow = UIImage(color: UIColor(hexString: "3ed67e"), size: CGSize(width: 1, height: 1))
 
-  static let notificationIdentifier = "deltachat-ios-local-notifications"
+    static let notificationIdentifier = "deltachat-ios-local-notifications"
 }
 

+ 194 - 194
deltachat-ios/Helper/Extensions.swift

@@ -1,253 +1,253 @@
 import UIKit
 
 extension String {
-  func containsCharacters() -> Bool {
-    return !trimmingCharacters(in: [" "]).isEmpty
-  }
-
-  // O(n) - returns indexes of subsequences -> can be used to highlight subsequence within string
-  func contains(subSequence: String) -> [Int] {
-    if subSequence.count > count {
-      return []
+    func containsCharacters() -> Bool {
+        return !trimmingCharacters(in: [" "]).isEmpty
     }
 
-    let str = lowercased()
-    let sub = subSequence.lowercased()
+    // O(n) - returns indexes of subsequences -> can be used to highlight subsequence within string
+    func contains(subSequence: String) -> [Int] {
+        if subSequence.count > count {
+            return []
+        }
+
+        let str = lowercased()
+        let sub = subSequence.lowercased()
 
-    var j = 0
+        var j = 0
 
-    var foundIndexes: [Int] = []
+        var foundIndexes: [Int] = []
 
-    for (index, char) in str.enumerated() {
-      if j == sub.count {
-        break
-      }
+        for (index, char) in str.enumerated() {
+            if j == sub.count {
+                break
+            }
 
-      if char == sub.subScript(j) {
-        foundIndexes.append(index)
-        j += 1
-      }
+            if char == sub.subScript(j) {
+                foundIndexes.append(index)
+                j += 1
+            }
+        }
+        return foundIndexes.count == sub.count ? foundIndexes : []
     }
-    return foundIndexes.count == sub.count ? foundIndexes : []
-  }
 
-  func subScript(_ i: Int) -> Character {
-    return self[index(startIndex, offsetBy: i)]
-  }
+    func subScript(_ i: Int) -> Character {
+        return self[index(startIndex, offsetBy: i)]
+    }
 
-  func boldAt(indexes: [Int], fontSize: CGFloat) -> NSAttributedString {
-    let attributedText = NSMutableAttributedString(string: self)
+    func boldAt(indexes: [Int], fontSize: CGFloat) -> NSAttributedString {
+        let attributedText = NSMutableAttributedString(string: self)
 
-    for index in indexes {
-      if index < 0 || count <= index {
-        break
-      }
-      attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: NSMakeRange(index, 1))
+        for index in indexes {
+            if index < 0 || count <= index {
+                break
+            }
+            attributedText.addAttribute(.font, value: UIFont.boldSystemFont(ofSize: fontSize), range: NSMakeRange(index, 1))
+        }
+        return attributedText
     }
-    return attributedText
-  }
 }
 
 extension URL {
-  public var queryParameters: [String: String]? {
-    guard
-      let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
-      let queryItems = components.queryItems else { return nil }
-    return queryItems.reduce(into: [String: String]()) { result, item in
-      result[item.name] = item.value
-    }
-  }
+    public var queryParameters: [String: String]? {
+        guard
+            let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
+            let queryItems = components.queryItems else { return nil }
+        return queryItems.reduce(into: [String: String]()) { result, item in
+            result[item.name] = item.value
+        }
+    }
 }
 
 extension Dictionary {
-  func percentEscaped() -> String {
-    return map { key, value in
-      let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
-      let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
-      return escapedKey + "=" + escapedValue
-    }
-    .joined(separator: "&")
-  }
+    func percentEscaped() -> String {
+        return map { key, value in
+            let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
+            let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
+            return escapedKey + "=" + escapedValue
+        }
+        .joined(separator: "&")
+    }
 }
 
 extension CharacterSet {
-  static let urlQueryValueAllowed: CharacterSet = {
-    let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
-    let subDelimitersToEncode = "!$&'()*+,;="
-
-    var allowed = CharacterSet.urlQueryAllowed
-    allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
-    return allowed
-  }()
+    static let urlQueryValueAllowed: CharacterSet = {
+        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
+        let subDelimitersToEncode = "!$&'()*+,;="
+
+        var allowed = CharacterSet.urlQueryAllowed
+        allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
+        return allowed
+    }()
 }
 
 extension URLSession {
-  func synchronousDataTask(request: URLRequest) -> (Data?, URLResponse?, Error?) {
-    var data: Data?
-    var response: URLResponse?
-    var error: Error?
+    func synchronousDataTask(request: URLRequest) -> (Data?, URLResponse?, Error?) {
+        var data: Data?
+        var response: URLResponse?
+        var error: Error?
 
-    let semaphore = DispatchSemaphore(value: 0)
+        let semaphore = DispatchSemaphore(value: 0)
 
-    let task = dataTask(with: request) {
-      data = $0
-      response = $1
-      error = $2
+        let task = dataTask(with: request) {
+            data = $0
+            response = $1
+            error = $2
 
-      semaphore.signal()
-    }
-    task.resume()
+            semaphore.signal()
+        }
+        task.resume()
 
-    _ = semaphore.wait(timeout: .distantFuture)
+        _ = semaphore.wait(timeout: .distantFuture)
 
-    return (data, response, error)
-  }
+        return (data, response, error)
+    }
 }
 
 extension MRContact {
-  func contains(searchText text: String) -> [ContactHighlights] {
-    var nameIndexes = [Int]()
-    var emailIndexes = [Int]()
-
-    let contactString = name + email
-    let subsequenceIndexes = contactString.contains(subSequence: text)
-
-    if !subsequenceIndexes.isEmpty {
-      for index in subsequenceIndexes {
-        if index < name.count {
-          nameIndexes.append(index)
+    func contains(searchText text: String) -> [ContactHighlights] {
+        var nameIndexes = [Int]()
+        var emailIndexes = [Int]()
+
+        let contactString = name + email
+        let subsequenceIndexes = contactString.contains(subSequence: text)
+
+        if !subsequenceIndexes.isEmpty {
+            for index in subsequenceIndexes {
+                if index < name.count {
+                    nameIndexes.append(index)
+                } else {
+                    let emailIndex = index - name.count
+                    emailIndexes.append(emailIndex)
+                }
+            }
+            return [ContactHighlights(contactDetail: .NAME, indexes: nameIndexes), ContactHighlights(contactDetail: .EMAIL, indexes: emailIndexes)]
         } else {
-          let emailIndex = index - name.count
-          emailIndexes.append(emailIndex)
+            return []
         }
-      }
-      return [ContactHighlights(contactDetail: .NAME, indexes: nameIndexes), ContactHighlights(contactDetail: .EMAIL, indexes: emailIndexes)]
-    } else {
-      return []
     }
-  }
 }
 
 extension UIImage {
 
-	func dcCompress(toMax target: Float = 1280) -> UIImage? {
-		return resize(toMax: target)
-	}
-
-	func imageSizeInPixel() -> CGSize {
-		let heightInPoints = size.height
-		let heightInPixels = heightInPoints * scale
-		let widthInPoints = size.width
-		let widthInPixels = widthInPoints * scale
-		return CGSize(width: widthInPixels, height: heightInPixels)
-	}
-
-	// source: https://stackoverflow.com/questions/29137488/how-do-i-resize-the-uiimage-to-reduce-upload-image-size // slightly changed
-	func resize(toMax: Float) -> UIImage? {
-		var actualHeight = Float(size.height)
-		var actualWidth = Float(size.width)
-		let maxHeight: Float = toMax
-		let maxWidth: Float = toMax
-		var imgRatio: Float = actualWidth / actualHeight
-		let maxRatio: Float = maxWidth / maxHeight
-		let compressionQuality: Float = 0.5
-		//50 percent compression
-		if actualHeight > maxHeight || actualWidth > maxWidth {
-			if imgRatio < maxRatio {
-				//adjust width according to maxHeight
-				imgRatio = maxHeight / actualHeight
-				actualWidth = imgRatio * actualWidth
-				actualHeight = maxHeight
-			} else if imgRatio > maxRatio {
-				//adjust height according to maxWidth
-				imgRatio = maxWidth / actualWidth
-				actualHeight = imgRatio * actualHeight
-				actualWidth = maxWidth
-			} else {
-				actualHeight = maxHeight
-				actualWidth = maxWidth
-			}
-		}
-
-		let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
-		UIGraphicsBeginImageContext(rect.size)
-		draw(in: rect)
-		let img = UIGraphicsGetImageFromCurrentImageContext()
-		let imageData = img?.jpegData(compressionQuality: CGFloat(compressionQuality))
-		UIGraphicsEndImageContext()
-		return UIImage(data: imageData!)
-	}
+    func dcCompress(toMax target: Float = 1280) -> UIImage? {
+        return resize(toMax: target)
+    }
+
+    func imageSizeInPixel() -> CGSize {
+        let heightInPoints = size.height
+        let heightInPixels = heightInPoints * scale
+        let widthInPoints = size.width
+        let widthInPixels = widthInPoints * scale
+        return CGSize(width: widthInPixels, height: heightInPixels)
+    }
+
+    // source: https://stackoverflow.com/questions/29137488/how-do-i-resize-the-uiimage-to-reduce-upload-image-size // slightly changed
+    func resize(toMax: Float) -> UIImage? {
+        var actualHeight = Float(size.height)
+        var actualWidth = Float(size.width)
+        let maxHeight: Float = toMax
+        let maxWidth: Float = toMax
+        var imgRatio: Float = actualWidth / actualHeight
+        let maxRatio: Float = maxWidth / maxHeight
+        let compressionQuality: Float = 0.5
+        //50 percent compression
+        if actualHeight > maxHeight || actualWidth > maxWidth {
+            if imgRatio < maxRatio {
+                //adjust width according to maxHeight
+                imgRatio = maxHeight / actualHeight
+                actualWidth = imgRatio * actualWidth
+                actualHeight = maxHeight
+            } else if imgRatio > maxRatio {
+                //adjust height according to maxWidth
+                imgRatio = maxWidth / actualWidth
+                actualHeight = imgRatio * actualHeight
+                actualWidth = maxWidth
+            } else {
+                actualHeight = maxHeight
+                actualWidth = maxWidth
+            }
+        }
+
+        let rect = CGRect(x: 0.0, y: 0.0, width: CGFloat(actualWidth), height: CGFloat(actualHeight))
+        UIGraphicsBeginImageContext(rect.size)
+        draw(in: rect)
+        let img = UIGraphicsGetImageFromCurrentImageContext()
+        let imageData = img?.jpegData(compressionQuality: CGFloat(compressionQuality))
+        UIGraphicsEndImageContext()
+        return UIImage(data: imageData!)
+    }
 }
 
 extension UIView {
-	func makeBorder(color: UIColor = UIColor.red) {
-		self.layer.borderColor = color.cgColor
-		self.layer.borderWidth = 2
-	}
+    func makeBorder(color: UIColor = UIColor.red) {
+        self.layer.borderColor = color.cgColor
+        self.layer.borderWidth = 2
+    }
 }
 
 extension UIImage {
-func resizeImage(targetSize: CGSize) -> UIImage {
-	let size = self.size
+    func resizeImage(targetSize: CGSize) -> UIImage {
+        let size = self.size
 
-	let widthRatio  = targetSize.width  / size.width
-	let heightRatio = targetSize.height / size.height
+        let widthRatio  = targetSize.width  / size.width
+        let heightRatio = targetSize.height / size.height
 
-	var newSize: CGSize
-	if(widthRatio > heightRatio) {
-		newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
-	} else {
-		newSize = CGSize(width: size.width * widthRatio, height: size.height *      widthRatio)
-	}
+        var newSize: CGSize
+        if(widthRatio > heightRatio) {
+            newSize = CGSize(width: size.width * heightRatio, height: size.height * heightRatio)
+        } else {
+            newSize = CGSize(width: size.width * widthRatio, height: size.height *      widthRatio)
+        }
 
-	let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
+        let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
 
-	UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
-	draw(in: rect)
-	let newImage = UIGraphicsGetImageFromCurrentImageContext()
-	UIGraphicsEndImageContext()
+        UIGraphicsBeginImageContextWithOptions(newSize, false, 1.0)
+        draw(in: rect)
+        let newImage = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
 
-	return newImage!
-}
+        return newImage!
+    }
 }
 
 
 extension UIColor {
-	convenience init(alpha: Int, red: Int, green: Int, blue: Int) {
-		assert(red >= 0 && red <= 255, "Invalid red component")
-		assert(green >= 0 && green <= 255, "Invalid green component")
-		assert(blue >= 0 && blue <= 255, "Invalid blue component")
-
-		self.init(red: CGFloat(red) / 255, green: CGFloat(green) / 255, blue: CGFloat(blue) / 255, alpha: CGFloat(alpha) / 255)
-	}
-
-	convenience init(netHex: Int) {
-		var alpha = (netHex >> 24) & 0xFF
-		if alpha == 0 {
-			alpha = 255
-		}
-
-		self.init(alpha: alpha, red: (netHex >> 16) & 0xFF, green: (netHex >> 8) & 0xFF, blue: netHex & 0xFF)
-	}
-
-	// see: https://stackoverflow.com/a/33397427
-	convenience init(hexString: String) {
-		let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
-		var int = UInt32()
-		Scanner(string: hex).scanHexInt32(&int)
-		let a, r, g, b: UInt32
-		switch hex.count {
-		case 3: // RGB (12-bit)
-			(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
-		case 6: // RGB (24-bit)
-			(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
-		case 8: // ARGB (32-bit)
-			(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
-		default:
-			(a, r, g, b) = (255, 0, 0, 0)
-		}
-		self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
-	}
+    convenience init(alpha: Int, red: Int, green: Int, blue: Int) {
+        assert(red >= 0 && red <= 255, "Invalid red component")
+        assert(green >= 0 && green <= 255, "Invalid green component")
+        assert(blue >= 0 && blue <= 255, "Invalid blue component")
+
+        self.init(red: CGFloat(red) / 255, green: CGFloat(green) / 255, blue: CGFloat(blue) / 255, alpha: CGFloat(alpha) / 255)
+    }
+
+    convenience init(netHex: Int) {
+        var alpha = (netHex >> 24) & 0xFF
+        if alpha == 0 {
+            alpha = 255
+        }
+
+        self.init(alpha: alpha, red: (netHex >> 16) & 0xFF, green: (netHex >> 8) & 0xFF, blue: netHex & 0xFF)
+    }
+
+    // see: https://stackoverflow.com/a/33397427
+    convenience init(hexString: String) {
+        let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
+        var int = UInt32()
+        Scanner(string: hex).scanHexInt32(&int)
+        let a, r, g, b: UInt32
+        switch hex.count {
+        case 3: // RGB (12-bit)
+            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
+        case 6: // RGB (24-bit)
+            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
+        case 8: // ARGB (32-bit)
+            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
+        default:
+            (a, r, g, b) = (255, 0, 0, 0)
+        }
+        self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
+    }
 
 
 }

+ 8 - 8
deltachat-ios/Helper/Protocols.swift

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

+ 18 - 18
deltachat-ios/Helper/UIImage+Extension.swift

@@ -1,26 +1,26 @@
 import UIKit
 
 extension UIImage {
-  func imageResize(sizeChange: CGSize) -> UIImage {
-    let hasAlpha = true
-    let scale: CGFloat = 0.0 // Use scale factor of main screen
+    func imageResize(sizeChange: CGSize) -> UIImage {
+        let hasAlpha = true
+        let scale: CGFloat = 0.0 // Use scale factor of main screen
 
-    UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)
-    draw(in: CGRect(origin: CGPoint.zero, size: sizeChange))
+        UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)
+        draw(in: CGRect(origin: CGPoint.zero, size: sizeChange))
 
-    let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
-    return scaledImage!
-  }
+        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
+        return scaledImage!
+    }
 
-  public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
-    let rect = CGRect(origin: .zero, size: size)
-    UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
-    color.setFill()
-    UIRectFill(rect)
-    let image = UIGraphicsGetImageFromCurrentImageContext()
-    UIGraphicsEndImageContext()
+    public convenience init?(color: UIColor, size: CGSize = CGSize(width: 1, height: 1)) {
+        let rect = CGRect(origin: .zero, size: size)
+        UIGraphicsBeginImageContextWithOptions(rect.size, false, 0.0)
+        color.setFill()
+        UIRectFill(rect)
+        let image = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
 
-    guard let cgImage = image?.cgImage else { return nil }
-    self.init(cgImage: cgImage)
-  }
+        guard let cgImage = image?.cgImage else { return nil }
+        self.init(cgImage: cgImage)
+    }
 }

+ 156 - 156
deltachat-ios/Helper/Utils.swift

@@ -3,182 +3,182 @@ import UIKit
 import AVFoundation
 
 struct Utils {
-  static func getContactIds() -> [Int] {
-    let cContacts = dc_get_contacts(mailboxPointer, 0, nil)
-    return Utils.copyAndFreeArray(inputArray: cContacts)
-  }
-
-  static func getInitials(inputName: String) -> String {
-    var nameParts = inputName.split(separator: " ")
-		// this limits initials to max 2, otherwise just takes first letter to avoid messy badges
-		if nameParts.count > 2 {
-			nameParts = [nameParts[0]]
-		}
-    let initials: [Character] = nameParts.compactMap { part in part.capitalized.first }
-    let initialsString: String = String(initials)
-    return initialsString
-  }
-
-  static func copyAndFreeArray(inputArray: OpaquePointer?) -> [Int] {
-    var acc: [Int] = []
-    let len = dc_array_get_cnt(inputArray)
-    for i in 0 ..< len {
-      let e = dc_array_get_id(inputArray, i)
-      acc.append(Int(e))
+    static func getContactIds() -> [Int] {
+        let cContacts = dc_get_contacts(mailboxPointer, 0, nil)
+        return Utils.copyAndFreeArray(inputArray: cContacts)
     }
-    dc_array_unref(inputArray)
-
-    return acc
-  }
-
-  static func copyAndFreeArrayWithLen(inputArray: OpaquePointer?, len: Int = 0) -> [Int] {
-    var acc: [Int] = []
-    let arrayLen = dc_array_get_cnt(inputArray)
-    let start = max(0, arrayLen - len)
-    for i in start ..< arrayLen {
-      let e = dc_array_get_id(inputArray, i)
-      acc.append(Int(e))
-    }
-    dc_array_unref(inputArray)
-
-    return acc
-  }
 
-  static func copyAndFreeArrayWithOffset(inputArray: OpaquePointer?, len: Int = 0, from: Int = 0, skipEnd: Int = 0) -> [Int] {
-    let lenArray = dc_array_get_cnt(inputArray)
-    if lenArray <= skipEnd || lenArray == 0 {
-      dc_array_unref(inputArray)
-      return []
+    static func getInitials(inputName: String) -> String {
+        var nameParts = inputName.split(separator: " ")
+        // this limits initials to max 2, otherwise just takes first letter to avoid messy badges
+        if nameParts.count > 2 {
+            nameParts = [nameParts[0]]
+        }
+        let initials: [Character] = nameParts.compactMap { part in part.capitalized.first }
+        let initialsString: String = String(initials)
+        return initialsString
     }
 
-    let start = lenArray - 1 - skipEnd
-    let end = max(0, start - len)
-    let finalLen = start - end + (len > 0 ? 0 : 1)
-    var acc: [Int] = [Int](repeating: 0, count: finalLen)
+    static func copyAndFreeArray(inputArray: OpaquePointer?) -> [Int] {
+        var acc: [Int] = []
+        let len = dc_array_get_cnt(inputArray)
+        for i in 0 ..< len {
+            let e = dc_array_get_id(inputArray, i)
+            acc.append(Int(e))
+        }
+        dc_array_unref(inputArray)
 
-    for i in stride(from: start, to: end, by: -1) {
-      let index = finalLen - (start - i) - 1
-      acc[index] = Int(dc_array_get_id(inputArray, i))
+        return acc
     }
 
-    dc_array_unref(inputArray)
-    logger.info("got: \(from) \(len) \(lenArray) - \(acc)")
-
-    return acc
-  }
-
-  static func isValid(_ email: String) -> Bool {
-    let emailRegEx = "(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}"
-      + "~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\"
-      + "x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-"
-      + "z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5"
-      + "]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-"
-      + "9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21"
-      + "-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"
-
-    let emailTest = NSPredicate(format: "SELF MATCHES[c] %@", emailRegEx)
-    return emailTest.evaluate(with: email)
-  }
-
-  static func formatAddressForQuery(address: [String: String]) -> String {
-    // Open address in Apple Maps app.
-    var addressParts = [String]()
-    let addAddressPart: ((String?) -> Void) = { part in
-      guard let part = part else {
-        return
-      }
-      guard !part.isEmpty else {
-        return
-      }
-      addressParts.append(part)
-    }
-    addAddressPart(address["Street"])
-    addAddressPart(address["Neighborhood"])
-    addAddressPart(address["City"])
-    addAddressPart(address["Region"])
-    addAddressPart(address["Postcode"])
-    addAddressPart(address["Country"])
-    return addressParts.joined(separator: ", ")
-  }
-
-  static func saveImage(image: UIImage) -> String? {
-    guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
-      return nil
+    static func copyAndFreeArrayWithLen(inputArray: OpaquePointer?, len: Int = 0) -> [Int] {
+        var acc: [Int] = []
+        let arrayLen = dc_array_get_cnt(inputArray)
+        let start = max(0, arrayLen - len)
+        for i in start ..< arrayLen {
+            let e = dc_array_get_id(inputArray, i)
+            acc.append(Int(e))
+        }
+        dc_array_unref(inputArray)
+
+        return acc
     }
 
-    let size = image.size.applying(CGAffineTransform(scaleX: 0.2, y: 0.2))
-    let hasAlpha = false
-    let scale: CGFloat = 0.0
+    static func copyAndFreeArrayWithOffset(inputArray: OpaquePointer?, len: Int = 0, from: Int = 0, skipEnd: Int = 0) -> [Int] {
+        let lenArray = dc_array_get_cnt(inputArray)
+        if lenArray <= skipEnd || lenArray == 0 {
+            dc_array_unref(inputArray)
+            return []
+        }
 
-    UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
-    image.draw(in: CGRect(origin: CGPoint.zero, size: size))
+        let start = lenArray - 1 - skipEnd
+        let end = max(0, start - len)
+        let finalLen = start - end + (len > 0 ? 0 : 1)
+        var acc: [Int] = [Int](repeating: 0, count: finalLen)
 
-    let scaledImageI = UIGraphicsGetImageFromCurrentImageContext()
-    UIGraphicsEndImageContext()
+        for i in stride(from: start, to: end, by: -1) {
+            let index = finalLen - (start - i) - 1
+            acc[index] = Int(dc_array_get_id(inputArray, i))
+        }
 
-    guard let scaledImage = scaledImageI else {
-      return nil
+        dc_array_unref(inputArray)
+        logger.info("got: \(from) \(len) \(lenArray) - \(acc)")
+
+        return acc
     }
 
-    guard let data = scaledImage.jpegData(compressionQuality: 0.9) else {
-      return nil
+    static func isValid(_ email: String) -> Bool {
+        let emailRegEx = "(?:[a-z0-9!#$%\\&'*+/=?\\^_`{|}~-]+(?:\\.[a-z0-9!#$%\\&'*+/=?\\^_`{|}"
+            + "~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\"
+            + "x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-"
+            + "z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5"
+            + "]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-"
+            + "9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21"
+            + "-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])"
+
+        let emailTest = NSPredicate(format: "SELF MATCHES[c] %@", emailRegEx)
+        return emailTest.evaluate(with: email)
     }
 
-    do {
-      let timestamp = Int(Date().timeIntervalSince1970)
-      let path = directory.appendingPathComponent("\(timestamp).jpg")
-      try data.write(to: path!)
-      return path?.relativePath
-    } catch {
-      logger.info(error.localizedDescription)
-      return nil
+    static func formatAddressForQuery(address: [String: String]) -> String {
+        // Open address in Apple Maps app.
+        var addressParts = [String]()
+        let addAddressPart: ((String?) -> Void) = { part in
+            guard let part = part else {
+                return
+            }
+            guard !part.isEmpty else {
+                return
+            }
+            addressParts.append(part)
+        }
+        addAddressPart(address["Street"])
+        addAddressPart(address["Neighborhood"])
+        addAddressPart(address["City"])
+        addAddressPart(address["Region"])
+        addAddressPart(address["Postcode"])
+        addAddressPart(address["Country"])
+        return addressParts.joined(separator: ", ")
+    }
+
+    static func saveImage(image: UIImage) -> String? {
+        guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
+            return nil
+        }
+
+        let size = image.size.applying(CGAffineTransform(scaleX: 0.2, y: 0.2))
+        let hasAlpha = false
+        let scale: CGFloat = 0.0
+
+        UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
+        image.draw(in: CGRect(origin: CGPoint.zero, size: size))
+
+        let scaledImageI = UIGraphicsGetImageFromCurrentImageContext()
+        UIGraphicsEndImageContext()
+
+        guard let scaledImage = scaledImageI else {
+            return nil
+        }
+
+        guard let data = scaledImage.jpegData(compressionQuality: 0.9) else {
+            return nil
+        }
+
+        do {
+            let timestamp = Int(Date().timeIntervalSince1970)
+            let path = directory.appendingPathComponent("\(timestamp).jpg")
+            try data.write(to: path!)
+            return path?.relativePath
+        } catch {
+            logger.info(error.localizedDescription)
+            return nil
+        }
+    }
+
+    static func generateThumbnailFromVideo(url: URL) -> UIImage? {
+        do {
+            let asset = AVURLAsset(url: url)
+            let imageGenerator = AVAssetImageGenerator(asset: asset)
+            imageGenerator.appliesPreferredTrackTransform = true
+            // Select the right one based on which version you are using
+            // Swift 4.2
+            //let cgImage = try imageGenerator.copyCGImage(at: .zero, actualTime: nil)
+            // Swift 4.0
+            let cgImage = try imageGenerator.copyCGImage(at: CMTime.zero, actualTime: nil)
+            return UIImage(cgImage: cgImage)
+        } catch {
+            print(error.localizedDescription)
+
+            return nil
+        }
     }
-  }
-
-	static func generateThumbnailFromVideo(url: URL) -> UIImage? {
-		do {
-			let asset = AVURLAsset(url: url)
-			let imageGenerator = AVAssetImageGenerator(asset: asset)
-			imageGenerator.appliesPreferredTrackTransform = true
-			// Select the right one based on which version you are using
-			// Swift 4.2
-			//let cgImage = try imageGenerator.copyCGImage(at: .zero, actualTime: nil)
-			// Swift 4.0
-			let cgImage = try imageGenerator.copyCGImage(at: CMTime.zero, actualTime: nil)
-			return UIImage(cgImage: cgImage)
-		} catch {
-			print(error.localizedDescription)
-
-			return nil
-		}
-	}
 }
 
 class DateUtils {
 
-	static func getBriefRelativeTimeSpanString(timeStamp: Int) -> String {
-		let unixTime = Int(Date().timeIntervalSince1970)
-		let seconds = unixTime - timeStamp
-
-		if seconds < 60 {
-			return "Now"	// under one minute
-		} else if seconds < 3600 {
-			let mins = seconds / 60
-			let minTitle = mins > 1 ? "mins" : "min"
-			return "\(mins) \(minTitle)"
-		} else if seconds < 86400 {
-			let hours = seconds / 3600
-			let hoursTitle = hours > 1 ? "hours" : "hour"
-			return "\(hours) \(hoursTitle)"
-		} else {
-			let date = Date(timeIntervalSince1970: Double(timeStamp))
-			let dateFormatter = DateFormatter()
-			// dateFormatter.timeStyle = DateFormatter.Style.short //Set time style
-			dateFormatter.dateStyle = DateFormatter.Style.medium //Set date style
-			dateFormatter.timeZone = .current
-			let localDate = dateFormatter.string(from: date)
-			return localDate
-		}
-	}
+    static func getBriefRelativeTimeSpanString(timeStamp: Int) -> String {
+        let unixTime = Int(Date().timeIntervalSince1970)
+        let seconds = unixTime - timeStamp
+
+        if seconds < 60 {
+            return "Now"	// under one minute
+        } else if seconds < 3600 {
+            let mins = seconds / 60
+            let minTitle = mins > 1 ? "mins" : "min"
+            return "\(mins) \(minTitle)"
+        } else if seconds < 86400 {
+            let hours = seconds / 3600
+            let hoursTitle = hours > 1 ? "hours" : "hour"
+            return "\(hours) \(hoursTitle)"
+        } else {
+            let date = Date(timeIntervalSince1970: Double(timeStamp))
+            let dateFormatter = DateFormatter()
+            // dateFormatter.timeStyle = DateFormatter.Style.short //Set time style
+            dateFormatter.dateStyle = DateFormatter.Style.medium //Set date style
+            dateFormatter.timeZone = .current
+            let localDate = dateFormatter.string(from: date)
+            return localDate
+        }
+    }
 }
 

+ 6 - 6
deltachat-ios/Model/Location.swift

@@ -3,12 +3,12 @@ import Foundation
 import MessageKit
 
 struct Location: LocationItem {
-  var location: CLLocation
+    var location: CLLocation
 
-  var size: CGSize
+    var size: CGSize
 
-  init(location: CLLocation, size: CGSize) {
-    self.location = location
-    self.size = size
-  }
+    init(location: CLLocation, size: CGSize) {
+        self.location = location
+        self.size = size
+    }
 }

+ 13 - 13
deltachat-ios/Model/Media.swift

@@ -3,22 +3,22 @@ import Foundation
 import MessageKit
 
 struct Media: MediaItem {
-  var url: URL?
+    var url: URL?
 
-  var image: UIImage?
+    var image: UIImage?
 
-  var placeholderImage: UIImage = UIImage(named: "ic_attach_file_36pt")!
+    var placeholderImage: UIImage = UIImage(named: "ic_attach_file_36pt")!
 
-  var size: CGSize {
-    if let image = image {
-      return image.size
-    } else {
-      return placeholderImage.size
+    var size: CGSize {
+        if let image = image {
+            return image.size
+        } else {
+            return placeholderImage.size
+        }
     }
-  }
 
-  init(url: URL? = nil, image: UIImage? = nil) {
-    self.url = url
-    self.image = image
-  }
+    init(url: URL? = nil, image: UIImage? = nil) {
+        self.url = url
+        self.image = image
+    }
 }

+ 39 - 39
deltachat-ios/Model/Message.swift

@@ -3,43 +3,43 @@ import Foundation
 import MessageKit
 
 struct Message: MessageType {
-  var messageId: String
-  var sender: SenderType
-  var sentDate: Date
-  var kind: MessageKind
-
-  init(kind: MessageKind, sender: Sender, messageId: String, date: Date) {
-    self.kind = kind
-    self.sender = sender
-    self.messageId = messageId
-    sentDate = date
-  }
-
-  init(text: String, sender: Sender, messageId: String, date: Date) {
-    self.init(kind: .text(text), sender: sender, messageId: messageId, date: date)
-  }
-
-  init(attributedText: NSAttributedString, sender: Sender, messageId: String, date: Date) {
-    self.init(kind: .attributedText(attributedText), sender: sender, messageId: messageId, date: date)
-  }
-
-  init(image: UIImage, sender: Sender, messageId: String, date: Date) {
-    let media = Media(image: image)
-    self.init(kind: .photo(media), sender: sender, messageId: messageId, date: date)
-  }
-
-  init(thumbnail: UIImage, sender: Sender, messageId: String, date: Date) {
-    let url = URL(fileURLWithPath: "")
-    let media = Media(url: url, image: thumbnail)
-    self.init(kind: .video(media), sender: sender, messageId: messageId, date: date)
-  }
-
-  init(location: CLLocation, sender: Sender, messageId: String, date: Date) {
-    let locationItem = Location(location: location, size: CGSize(width: 100, height: 50))
-    self.init(kind: .location(locationItem), sender: sender, messageId: messageId, date: date)
-  }
-
-  init(emoji: String, sender: Sender, messageId: String, date: Date) {
-    self.init(kind: .emoji(emoji), sender: sender, messageId: messageId, date: date)
-  }
+    var messageId: String
+    var sender: SenderType
+    var sentDate: Date
+    var kind: MessageKind
+
+    init(kind: MessageKind, sender: Sender, messageId: String, date: Date) {
+        self.kind = kind
+        self.sender = sender
+        self.messageId = messageId
+        sentDate = date
+    }
+
+    init(text: String, sender: Sender, messageId: String, date: Date) {
+        self.init(kind: .text(text), sender: sender, messageId: messageId, date: date)
+    }
+
+    init(attributedText: NSAttributedString, sender: Sender, messageId: String, date: Date) {
+        self.init(kind: .attributedText(attributedText), sender: sender, messageId: messageId, date: date)
+    }
+
+    init(image: UIImage, sender: Sender, messageId: String, date: Date) {
+        let media = Media(image: image)
+        self.init(kind: .photo(media), sender: sender, messageId: messageId, date: date)
+    }
+
+    init(thumbnail: UIImage, sender: Sender, messageId: String, date: Date) {
+        let url = URL(fileURLWithPath: "")
+        let media = Media(url: url, image: thumbnail)
+        self.init(kind: .video(media), sender: sender, messageId: messageId, date: date)
+    }
+
+    init(location: CLLocation, sender: Sender, messageId: String, date: Date) {
+        let locationItem = Location(location: location, size: CGSize(width: 100, height: 50))
+        self.init(kind: .location(locationItem), sender: sender, messageId: messageId, date: date)
+    }
+
+    init(emoji: String, sender: Sender, messageId: String, date: Date) {
+        self.init(kind: .emoji(emoji), sender: sender, messageId: messageId, date: date)
+    }
 }

+ 37 - 37
deltachat-ios/View/ActionCell.swift

@@ -3,44 +3,44 @@ import UIKit
 // a cell with a centered label in system blue
 
 class ActionCell: UITableViewCell {
-  var actionTitle: String? {
-    didSet {
-      actionLabel.text = actionTitle
+    var actionTitle: String? {
+        didSet {
+            actionLabel.text = actionTitle
+        }
     }
-  }
 
-  var actionColor: UIColor? {
-    didSet {
-      actionLabel.textColor = actionColor ?? UIColor.systemBlue
+    var actionColor: UIColor? {
+        didSet {
+            actionLabel.textColor = actionColor ?? UIColor.systemBlue
+        }
+    }
+
+    private lazy var actionLabel: UILabel = {
+        let label = UILabel()
+        label.text = actionTitle
+        label.textColor = UIColor.systemBlue
+        return label
+    }()
+
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        setupSubviews()
+        selectionStyle = .none
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    override func awakeFromNib() {
+        super.awakeFromNib()
+        // Initialization code
+    }
+
+    private func setupSubviews() {
+        contentView.addSubview(actionLabel)
+        actionLabel.translatesAutoresizingMaskIntoConstraints = false
+        actionLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor, constant: 0).isActive = true
+        actionLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0).isActive = true
     }
-  }
-
-  private lazy var actionLabel: UILabel = {
-    let label = UILabel()
-    label.text = actionTitle
-    label.textColor = UIColor.systemBlue
-    return label
-  }()
-
-  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-    super.init(style: style, reuseIdentifier: reuseIdentifier)
-    setupSubviews()
-    selectionStyle = .none
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  override func awakeFromNib() {
-    super.awakeFromNib()
-    // Initialization code
-  }
-
-  private func setupSubviews() {
-    contentView.addSubview(actionLabel)
-    actionLabel.translatesAutoresizingMaskIntoConstraints = false
-    actionLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor, constant: 0).isActive = true
-    actionLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0).isActive = true
-  }
 }

+ 47 - 47
deltachat-ios/View/ChatTitleView.swift

@@ -2,51 +2,51 @@ import UIKit
 
 class ChatTitleView: UIView {
 
-	private var titleLabel: UILabel = {
-		let titleLabel = UILabel()
-		titleLabel.backgroundColor = UIColor.clear
-		titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
-		titleLabel.textAlignment = .center
-		titleLabel.adjustsFontSizeToFitWidth = true
-		return titleLabel
-	}()
-
-	private var subtitleLabel: UILabel = {
-		let subtitleLabel = UILabel()
-		subtitleLabel.font = UIFont.systemFont(ofSize: 12)
-		subtitleLabel.textAlignment = .center
-		return subtitleLabel
-	}()
-
-	init() {
-		super.init(frame: .zero)
-		setupSubviews()
-	}
-
-	required init?(coder aDecoder: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	private func setupSubviews() {
-		addSubview(titleLabel)
-		titleLabel.translatesAutoresizingMaskIntoConstraints = false
-		titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
-		titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
-		titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
-		titleLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true
-
-		addSubview(subtitleLabel)
-		subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
-		subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
-		subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 0).isActive = true
-		subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
-		subtitleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
-	}
-
-	func updateTitleView(title: String, subtitle: String?, baseColor: UIColor = .darkText) {
-		subtitleLabel.textColor = baseColor.withAlphaComponent(0.95)
-		titleLabel.textColor = baseColor
-		titleLabel.text = title
-		subtitleLabel.text = subtitle
-	}
+    private var titleLabel: UILabel = {
+        let titleLabel = UILabel()
+        titleLabel.backgroundColor = UIColor.clear
+        titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
+        titleLabel.textAlignment = .center
+        titleLabel.adjustsFontSizeToFitWidth = true
+        return titleLabel
+    }()
+
+    private var subtitleLabel: UILabel = {
+        let subtitleLabel = UILabel()
+        subtitleLabel.font = UIFont.systemFont(ofSize: 12)
+        subtitleLabel.textAlignment = .center
+        return subtitleLabel
+    }()
+
+    init() {
+        super.init(frame: .zero)
+        setupSubviews()
+    }
+
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func setupSubviews() {
+        addSubview(titleLabel)
+        titleLabel.translatesAutoresizingMaskIntoConstraints = false
+        titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
+        titleLabel.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
+        titleLabel.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true
+        titleLabel.topAnchor.constraint(equalTo: topAnchor).isActive = true
+
+        addSubview(subtitleLabel)
+        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
+        subtitleLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0).isActive = true
+        subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 0).isActive = true
+        subtitleLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0).isActive = true
+        subtitleLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0).isActive = true
+    }
+
+    func updateTitleView(title: String, subtitle: String?, baseColor: UIColor = .darkText) {
+        subtitleLabel.textColor = baseColor.withAlphaComponent(0.95)
+        titleLabel.textColor = baseColor
+        titleLabel.text = title
+        subtitleLabel.text = subtitle
+    }
 }

+ 219 - 219
deltachat-ios/View/ContactCell.swift

@@ -3,227 +3,227 @@ import UIKit
 // TODO: integrate InitialsBadge in here
 
 enum MessageDeliveryState: Int {
-	case UNDEFINED =  0
-	case INNOTICED = 13
-	case INSEEN = 16
-	case OUTPAIRING = 18
-	case OUTPENDING = 20
-	case OUTERROR = 24
-	case OUTDELIVERED = 26
-	case OUTMDNRCVD = 28
+    case UNDEFINED =  0
+    case INNOTICED = 13
+    case INSEEN = 16
+    case OUTPAIRING = 18
+    case OUTPENDING = 20
+    case OUTERROR = 24
+    case OUTDELIVERED = 26
+    case OUTMDNRCVD = 28
 }
 
 class ContactCell: UITableViewCell {
 
-	private let initialsLabelSize: CGFloat = 54
-	private let imgSize: CGFloat = 25
-
-	let avatar: UIView = {
-		let avatar = UIView()
-		return avatar
-	}()
-
-	lazy var imgView: UIImageView = {
-		let imgView = UIImageView()
-		let img = UIImage(named: "approval")!.withRenderingMode(.alwaysTemplate)
-		imgView.isHidden = true
-		imgView.image = img
-		imgView.bounds = CGRect(
-			x: 0,
-			y: 0,
-			width: imgSize, height: imgSize
-		)
-		return imgView
-	}()
-
-	lazy var initialsLabel: UILabel = {
-		let initialsLabel = UILabel()
-		initialsLabel.textAlignment = NSTextAlignment.center
-		initialsLabel.textColor = UIColor.white
-		initialsLabel.font = UIFont.systemFont(ofSize: 22)
-		initialsLabel.backgroundColor = UIColor.green
-		let initialsLabelCornerRadius = (initialsLabelSize - 6) / 2
-		initialsLabel.layer.cornerRadius = initialsLabelCornerRadius
-		initialsLabel.clipsToBounds = true
-		return initialsLabel
-	}()
-
-	let nameLabel: UILabel = {
-		let label = UILabel()
-		label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
-		label.lineBreakMode = .byTruncatingTail
-		label.textColor = UIColor(hexString: "2f3944")
-		// label.makeBorder()
-		return label
-
-	}()
-
-	let emailLabel: UILabel = {
-		let label = UILabel()
-		label.font = UIFont.systemFont(ofSize: 14)
-		label.textColor = UIColor(hexString: "848ba7")
-		label.lineBreakMode = .byTruncatingTail
-		return label
-	}()
-
-	private let timeLabel: UILabel = {
-		let label = UILabel()
-		label.font = UIFont.systemFont(ofSize: 14)
-		label.textColor = UIColor(hexString: "848ba7")
-		label.textAlignment = .right
-		// label.makeBorder()
-		return label
-	}()
-
-	private let deliveryStatusIndicator: UIImageView = {
-		let view = UIImageView()
-		view.tintColor = UIColor.green
-		view.isHidden = true
-		return view
-	}()
-
-	var darkMode: Bool = false {
-		didSet {
-			if darkMode {
-				contentView.backgroundColor = UIColor.darkGray
-				nameLabel.textColor = UIColor.white
-				emailLabel.textColor = UIColor.white
-			}
-		}
-	}
-
-	override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-		super.init(style: style, reuseIdentifier: reuseIdentifier)
-		selectionStyle = .none
-		setupSubviews()
-	}
-
-	private func setupSubviews() {
-		let margin: CGFloat = 10
-
-		initialsLabel.translatesAutoresizingMaskIntoConstraints = false
-		avatar.translatesAutoresizingMaskIntoConstraints = false
-		initialsLabel.widthAnchor.constraint(equalToConstant: initialsLabelSize - 6).isActive = true
-		initialsLabel.heightAnchor.constraint(equalToConstant: initialsLabelSize - 6).isActive = true
-		// avatar.backgroundColor = .red
-
-		avatar.widthAnchor.constraint(equalToConstant: initialsLabelSize).isActive = true
-		avatar.heightAnchor.constraint(equalToConstant: initialsLabelSize).isActive = true
-
-		avatar.addSubview(initialsLabel)
-		contentView.addSubview(avatar)
-
-		initialsLabel.topAnchor.constraint(equalTo: avatar.topAnchor, constant: 3).isActive = true
-		initialsLabel.leadingAnchor.constraint(equalTo: avatar.leadingAnchor, constant: 3).isActive = true
-		initialsLabel.trailingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: -3).isActive = true
-
-		avatar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: margin).isActive = true
-		avatar.center.y = contentView.center.y
-		avatar.center.x += initialsLabelSize / 2
-		avatar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: margin).isActive = true
-		avatar.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -margin).isActive = true
-		initialsLabel.center = avatar.center
-
-		deliveryStatusIndicator.translatesAutoresizingMaskIntoConstraints = false
-		deliveryStatusIndicator.heightAnchor.constraint(equalToConstant: 25).isActive = true
-		deliveryStatusIndicator.widthAnchor.constraint(equalToConstant: 25).isActive = true
-
-		let myStackView = UIStackView()
-		myStackView.translatesAutoresizingMaskIntoConstraints = false
-		myStackView.clipsToBounds = true
-
-		let toplineStackView = UIStackView()
-		toplineStackView.axis = .horizontal
-
-		let bottomLineStackView = UIStackView()
-		bottomLineStackView.axis = .horizontal
-
-		toplineStackView.addArrangedSubview(nameLabel)
-		toplineStackView.addArrangedSubview(timeLabel)
-
-		bottomLineStackView.addArrangedSubview(emailLabel)
-		bottomLineStackView.addArrangedSubview(deliveryStatusIndicator)
-
-		contentView.addSubview(myStackView)
-		myStackView.leadingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: margin).isActive = true
-		myStackView.centerYAnchor.constraint(equalTo: avatar.centerYAnchor).isActive = true
-		myStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -margin).isActive = true
-		myStackView.axis = .vertical
-		myStackView.addArrangedSubview(toplineStackView)
-		myStackView.addArrangedSubview(bottomLineStackView)
-
-		imgView.tintColor = DCColors.primary
-
-		avatar.addSubview(imgView)
-
-		imgView.center.x = avatar.center.x + (avatar.frame.width / 2) + imgSize - 5
-		imgView.center.y = avatar.center.y + (avatar.frame.height / 2) + imgSize - 5
-	}
-
-	func setVerified(isVerified: Bool) {
-		imgView.isHidden = !isVerified
-	}
-
-	func setImage(_ img: UIImage) {
-		let attachment = NSTextAttachment()
-		attachment.image = img
-		initialsLabel.attributedText = NSAttributedString(attachment: attachment)
-	}
-
-	func setBackupImage(name: String, color: UIColor) {
-		let text = Utils.getInitials(inputName: name)
-
-		initialsLabel.textAlignment = .center
-		initialsLabel.text = text
-
-		setColor(color)
-	}
-
-	func setDeliveryStatusIndicator(_ status: Int) {
-		guard let status = MessageDeliveryState(rawValue: status) else {
-			return
-		}
-
-		var indicatorImage:UIImage?
-		switch status {
-		case .OUTPENDING, .OUTPAIRING:
-			indicatorImage = #imageLiteral(resourceName: "ic_delivery_status_sending").withRenderingMode(.alwaysTemplate)
-			deliveryStatusIndicator.tintColor = UIColor.black.withAlphaComponent(0.5)
-		case .OUTDELIVERED:
-			indicatorImage = #imageLiteral(resourceName: "ic_done_36pt").withRenderingMode(.alwaysTemplate)
-			deliveryStatusIndicator.tintColor = UIColor.green
-		case .OUTERROR:
-			indicatorImage = #imageLiteral(resourceName: "ic_error_36pt").withRenderingMode(.alwaysTemplate)
-			deliveryStatusIndicator.tintColor = UIColor.red
-		case .INSEEN:
-			indicatorImage = #imageLiteral(resourceName: "ic_done_all_36pt").withRenderingMode(.alwaysTemplate)
-			deliveryStatusIndicator.tintColor = UIColor.green
-		default: break
-		}
-		if indicatorImage != nil {
-			deliveryStatusIndicator.isHidden = false
-		} else {
-			deliveryStatusIndicator.isHidden = true
-		}
-
-		deliveryStatusIndicator.image = indicatorImage
-	}
-
-	func setTimeLabel(_ timestamp: Int?) {
-		if let timestamp = timestamp {
-			timeLabel.isHidden = false
-			timeLabel.text = DateUtils.getBriefRelativeTimeSpanString(timeStamp: timestamp)
-		} else {
-			timeLabel.isHidden = true
-			timeLabel.text = nil
-		}
-	}
-
-	func setColor(_ color: UIColor) {
-		initialsLabel.backgroundColor = color
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
+    private let initialsLabelSize: CGFloat = 54
+    private let imgSize: CGFloat = 25
+
+    let avatar: UIView = {
+        let avatar = UIView()
+        return avatar
+    }()
+
+    lazy var imgView: UIImageView = {
+        let imgView = UIImageView()
+        let img = UIImage(named: "approval")!.withRenderingMode(.alwaysTemplate)
+        imgView.isHidden = true
+        imgView.image = img
+        imgView.bounds = CGRect(
+            x: 0,
+            y: 0,
+            width: imgSize, height: imgSize
+        )
+        return imgView
+    }()
+
+    lazy var initialsLabel: UILabel = {
+        let initialsLabel = UILabel()
+        initialsLabel.textAlignment = NSTextAlignment.center
+        initialsLabel.textColor = UIColor.white
+        initialsLabel.font = UIFont.systemFont(ofSize: 22)
+        initialsLabel.backgroundColor = UIColor.green
+        let initialsLabelCornerRadius = (initialsLabelSize - 6) / 2
+        initialsLabel.layer.cornerRadius = initialsLabelCornerRadius
+        initialsLabel.clipsToBounds = true
+        return initialsLabel
+    }()
+
+    let nameLabel: UILabel = {
+        let label = UILabel()
+        label.font = UIFont.systemFont(ofSize: 16, weight: .medium)
+        label.lineBreakMode = .byTruncatingTail
+        label.textColor = UIColor(hexString: "2f3944")
+        // label.makeBorder()
+        return label
+
+    }()
+
+    let emailLabel: UILabel = {
+        let label = UILabel()
+        label.font = UIFont.systemFont(ofSize: 14)
+        label.textColor = UIColor(hexString: "848ba7")
+        label.lineBreakMode = .byTruncatingTail
+        return label
+    }()
+
+    private let timeLabel: UILabel = {
+        let label = UILabel()
+        label.font = UIFont.systemFont(ofSize: 14)
+        label.textColor = UIColor(hexString: "848ba7")
+        label.textAlignment = .right
+        // label.makeBorder()
+        return label
+    }()
+
+    private let deliveryStatusIndicator: UIImageView = {
+        let view = UIImageView()
+        view.tintColor = UIColor.green
+        view.isHidden = true
+        return view
+    }()
+
+    var darkMode: Bool = false {
+        didSet {
+            if darkMode {
+                contentView.backgroundColor = UIColor.darkGray
+                nameLabel.textColor = UIColor.white
+                emailLabel.textColor = UIColor.white
+            }
+        }
+    }
+
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        selectionStyle = .none
+        setupSubviews()
+    }
+
+    private func setupSubviews() {
+        let margin: CGFloat = 10
+
+        initialsLabel.translatesAutoresizingMaskIntoConstraints = false
+        avatar.translatesAutoresizingMaskIntoConstraints = false
+        initialsLabel.widthAnchor.constraint(equalToConstant: initialsLabelSize - 6).isActive = true
+        initialsLabel.heightAnchor.constraint(equalToConstant: initialsLabelSize - 6).isActive = true
+        // avatar.backgroundColor = .red
+
+        avatar.widthAnchor.constraint(equalToConstant: initialsLabelSize).isActive = true
+        avatar.heightAnchor.constraint(equalToConstant: initialsLabelSize).isActive = true
+
+        avatar.addSubview(initialsLabel)
+        contentView.addSubview(avatar)
+
+        initialsLabel.topAnchor.constraint(equalTo: avatar.topAnchor, constant: 3).isActive = true
+        initialsLabel.leadingAnchor.constraint(equalTo: avatar.leadingAnchor, constant: 3).isActive = true
+        initialsLabel.trailingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: -3).isActive = true
+
+        avatar.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: margin).isActive = true
+        avatar.center.y = contentView.center.y
+        avatar.center.x += initialsLabelSize / 2
+        avatar.topAnchor.constraint(equalTo: contentView.topAnchor, constant: margin).isActive = true
+        avatar.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: -margin).isActive = true
+        initialsLabel.center = avatar.center
+
+        deliveryStatusIndicator.translatesAutoresizingMaskIntoConstraints = false
+        deliveryStatusIndicator.heightAnchor.constraint(equalToConstant: 25).isActive = true
+        deliveryStatusIndicator.widthAnchor.constraint(equalToConstant: 25).isActive = true
+
+        let myStackView = UIStackView()
+        myStackView.translatesAutoresizingMaskIntoConstraints = false
+        myStackView.clipsToBounds = true
+
+        let toplineStackView = UIStackView()
+        toplineStackView.axis = .horizontal
+
+        let bottomLineStackView = UIStackView()
+        bottomLineStackView.axis = .horizontal
+
+        toplineStackView.addArrangedSubview(nameLabel)
+        toplineStackView.addArrangedSubview(timeLabel)
+
+        bottomLineStackView.addArrangedSubview(emailLabel)
+        bottomLineStackView.addArrangedSubview(deliveryStatusIndicator)
+
+        contentView.addSubview(myStackView)
+        myStackView.leadingAnchor.constraint(equalTo: avatar.trailingAnchor, constant: margin).isActive = true
+        myStackView.centerYAnchor.constraint(equalTo: avatar.centerYAnchor).isActive = true
+        myStackView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -margin).isActive = true
+        myStackView.axis = .vertical
+        myStackView.addArrangedSubview(toplineStackView)
+        myStackView.addArrangedSubview(bottomLineStackView)
+
+        imgView.tintColor = DCColors.primary
+
+        avatar.addSubview(imgView)
+
+        imgView.center.x = avatar.center.x + (avatar.frame.width / 2) + imgSize - 5
+        imgView.center.y = avatar.center.y + (avatar.frame.height / 2) + imgSize - 5
+    }
+
+    func setVerified(isVerified: Bool) {
+        imgView.isHidden = !isVerified
+    }
+
+    func setImage(_ img: UIImage) {
+        let attachment = NSTextAttachment()
+        attachment.image = img
+        initialsLabel.attributedText = NSAttributedString(attachment: attachment)
+    }
+
+    func setBackupImage(name: String, color: UIColor) {
+        let text = Utils.getInitials(inputName: name)
+
+        initialsLabel.textAlignment = .center
+        initialsLabel.text = text
+
+        setColor(color)
+    }
+
+    func setDeliveryStatusIndicator(_ status: Int) {
+        guard let status = MessageDeliveryState(rawValue: status) else {
+            return
+        }
+
+        var indicatorImage:UIImage?
+        switch status {
+        case .OUTPENDING, .OUTPAIRING:
+            indicatorImage = #imageLiteral(resourceName: "ic_delivery_status_sending").withRenderingMode(.alwaysTemplate)
+            deliveryStatusIndicator.tintColor = UIColor.black.withAlphaComponent(0.5)
+        case .OUTDELIVERED:
+            indicatorImage = #imageLiteral(resourceName: "ic_done_36pt").withRenderingMode(.alwaysTemplate)
+            deliveryStatusIndicator.tintColor = UIColor.green
+        case .OUTERROR:
+            indicatorImage = #imageLiteral(resourceName: "ic_error_36pt").withRenderingMode(.alwaysTemplate)
+            deliveryStatusIndicator.tintColor = UIColor.red
+        case .INSEEN:
+            indicatorImage = #imageLiteral(resourceName: "ic_done_all_36pt").withRenderingMode(.alwaysTemplate)
+            deliveryStatusIndicator.tintColor = UIColor.green
+        default: break
+        }
+        if indicatorImage != nil {
+            deliveryStatusIndicator.isHidden = false
+        } else {
+            deliveryStatusIndicator.isHidden = true
+        }
+
+        deliveryStatusIndicator.image = indicatorImage
+    }
+
+    func setTimeLabel(_ timestamp: Int?) {
+        if let timestamp = timestamp {
+            timeLabel.isHidden = false
+            timeLabel.text = DateUtils.getBriefRelativeTimeSpanString(timeStamp: timestamp)
+        } else {
+            timeLabel.isHidden = true
+            timeLabel.text = nil
+        }
+    }
+
+    func setColor(_ color: UIColor) {
+        initialsLabel.backgroundColor = color
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 }

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

@@ -1,20 +1,20 @@
 import UIKit
 
 class ContactDetailHeader: ContactCell {
-	init() {
-		super.init(style: .default, reuseIdentifier: nil)
-		let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
-		backgroundColor = bg
-		darkMode = false
-		selectionStyle = .none
-	}
+    init() {
+        super.init(style: .default, reuseIdentifier: nil)
+        let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
+        backgroundColor = bg
+        darkMode = false
+        selectionStyle = .none
+    }
 
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-	func updateDetails(title: String?, subtitle: String?) {
-		nameLabel.text = title
-		emailLabel.text = subtitle
-	}
+    func updateDetails(title: String?, subtitle: String?) {
+        nameLabel.text = title
+        emailLabel.text = subtitle
+    }
 }

+ 32 - 32
deltachat-ios/View/CustomMessageCell.swift

@@ -2,39 +2,39 @@ import MessageKit
 import UIKit
 
 open class CustomMessageCell: UICollectionViewCell {
-  let label = UILabel()
-
-  public override init(frame: CGRect) {
-    super.init(frame: frame)
-    setupSubviews()
-  }
-
-  public required init?(coder aDecoder: NSCoder) {
-    super.init(coder: aDecoder)
-    setupSubviews()
-  }
-
-  open func setupSubviews() {
-    contentView.addSubview(label)
-    label.textAlignment = .center
-    label.font = UIFont.italicSystemFont(ofSize: 13)
-  }
-
-  open override func layoutSubviews() {
-    super.layoutSubviews()
-    label.frame = contentView.bounds
-  }
-
-  open func configure(with message: MessageType, at _: IndexPath, and _: MessagesCollectionView) {
-    // Do stuff
-    switch message.kind {
-    case let .custom(data):
-      guard let systemMessage = data as? String else { return }
-      label.text = systemMessage
-    default:
-      break
+    let label = UILabel()
+
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupSubviews()
+    }
+
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        setupSubviews()
+    }
+
+    open func setupSubviews() {
+        contentView.addSubview(label)
+        label.textAlignment = .center
+        label.font = UIFont.italicSystemFont(ofSize: 13)
+    }
+
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+        label.frame = contentView.bounds
+    }
+
+    open func configure(with message: MessageType, at _: IndexPath, and _: MessagesCollectionView) {
+        // Do stuff
+        switch message.kind {
+        case let .custom(data):
+            guard let systemMessage = data as? String else { return }
+            label.text = systemMessage
+        default:
+            break
+        }
     }
-  }
 }
 
 

+ 59 - 59
deltachat-ios/View/GroupNameCell.swift

@@ -1,63 +1,63 @@
 import UIKit
 
 class GroupLabelCell: UITableViewCell {
-  var groupBadgeSize: CGFloat = 54
-
-  var onTextChanged: ((String) -> Void)? // use this callback to update editButton in navigationController
-
-  lazy var groupBadge: InitialsBadge = {
-    let badge = InitialsBadge(size: groupBadgeSize)
-    badge.layer.cornerRadius = groupBadgeSize / 2
-    badge.clipsToBounds = true
-		badge.setColor(UIColor.lightGray)
-    return badge
-  }()
-
-  lazy var inputField: UITextField = {
-    let textField = UITextField()
-    textField.placeholder = "Group Name"
-    textField.borderStyle = .none
-    textField.becomeFirstResponder()
-    textField.autocorrectionType = .no
-    textField.addTarget(self, action: #selector(nameFieldChanged), for: .editingChanged)
-    return textField
-  }()
-
-  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-    super.init(style: style, reuseIdentifier: reuseIdentifier)
-    setupSubviews()
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  private func setupSubviews() {
-    contentView.addSubview(groupBadge)
-    groupBadge.translatesAutoresizingMaskIntoConstraints = false
-
-    groupBadge.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor, constant: 0).isActive = true
-    groupBadge.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor, constant: 5).isActive = true
-    groupBadge.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -5).isActive = true
-    groupBadge.widthAnchor.constraint(equalToConstant: groupBadgeSize).isActive = true
-    groupBadge.heightAnchor.constraint(equalToConstant: groupBadgeSize).isActive = true
-
-    contentView.addSubview(inputField)
-    inputField.translatesAutoresizingMaskIntoConstraints = false
-
-    inputField.leadingAnchor.constraint(equalTo: groupBadge.trailingAnchor, constant: 15).isActive = true
-    inputField.heightAnchor.constraint(equalToConstant: 45).isActive = true
-    inputField.centerYAnchor.constraint(equalTo: groupBadge.centerYAnchor, constant: 0).isActive = true
-    inputField.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor, constant: 0).isActive = true
-  }
-
-  @objc func nameFieldChanged() {
-    let groupName = inputField.text ?? ""
-    groupBadge.setName(groupName)
-    onTextChanged?(groupName)
-  }
-
-  func getGroupName() -> String {
-    return inputField.text ?? ""
-  }
+    var groupBadgeSize: CGFloat = 54
+
+    var onTextChanged: ((String) -> Void)? // use this callback to update editButton in navigationController
+
+    lazy var groupBadge: InitialsBadge = {
+        let badge = InitialsBadge(size: groupBadgeSize)
+        badge.layer.cornerRadius = groupBadgeSize / 2
+        badge.clipsToBounds = true
+        badge.setColor(UIColor.lightGray)
+        return badge
+    }()
+
+    lazy var inputField: UITextField = {
+        let textField = UITextField()
+        textField.placeholder = "Group Name"
+        textField.borderStyle = .none
+        textField.becomeFirstResponder()
+        textField.autocorrectionType = .no
+        textField.addTarget(self, action: #selector(nameFieldChanged), for: .editingChanged)
+        return textField
+    }()
+
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        setupSubviews()
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    private func setupSubviews() {
+        contentView.addSubview(groupBadge)
+        groupBadge.translatesAutoresizingMaskIntoConstraints = false
+
+        groupBadge.leadingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.leadingAnchor, constant: 0).isActive = true
+        groupBadge.topAnchor.constraint(equalTo: contentView.layoutMarginsGuide.topAnchor, constant: 5).isActive = true
+        groupBadge.bottomAnchor.constraint(equalTo: contentView.layoutMarginsGuide.bottomAnchor, constant: -5).isActive = true
+        groupBadge.widthAnchor.constraint(equalToConstant: groupBadgeSize).isActive = true
+        groupBadge.heightAnchor.constraint(equalToConstant: groupBadgeSize).isActive = true
+
+        contentView.addSubview(inputField)
+        inputField.translatesAutoresizingMaskIntoConstraints = false
+
+        inputField.leadingAnchor.constraint(equalTo: groupBadge.trailingAnchor, constant: 15).isActive = true
+        inputField.heightAnchor.constraint(equalToConstant: 45).isActive = true
+        inputField.centerYAnchor.constraint(equalTo: groupBadge.centerYAnchor, constant: 0).isActive = true
+        inputField.trailingAnchor.constraint(equalTo: contentView.layoutMarginsGuide.trailingAnchor, constant: 0).isActive = true
+    }
+
+    @objc func nameFieldChanged() {
+        let groupName = inputField.text ?? ""
+        groupBadge.setName(groupName)
+        onTextChanged?(groupName)
+    }
+
+    func getGroupName() -> String {
+        return inputField.text ?? ""
+    }
 }

+ 44 - 44
deltachat-ios/View/InitialsBadge.swift

@@ -2,48 +2,48 @@ import UIKit
 
 class InitialsBadge: UIView {
 
-	private var label: UILabel = {
-		let label = UILabel()
-		label.adjustsFontSizeToFitWidth = true
-		label.textAlignment = NSTextAlignment.center
-		label.textColor = UIColor.white
-		return label
-	}()
-
-	convenience init(name: String, color: UIColor, size: CGFloat) {
-		self.init(size: size)
-		setName(name)
-		setColor(color)
-	}
-
-	init(size: CGFloat) {
-		super.init(frame: CGRect(x: 0, y: 0, width: size, height: size))
-		let initialsLabelCornerRadius = size / 2
-		layer.cornerRadius = initialsLabelCornerRadius
-		translatesAutoresizingMaskIntoConstraints = false
-		heightAnchor.constraint(equalToConstant: size).isActive = true
-		widthAnchor.constraint(equalToConstant: size).isActive = true
-		clipsToBounds = true
-		setupSubviews()
-	}
-
-	private func setupSubviews() {
-		addSubview(label)
-		label.translatesAutoresizingMaskIntoConstraints = false
-		label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2).isActive = true
-		label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2).isActive = true
-		label.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0).isActive = true
-	}
-
-	required init?(coder _: NSCoder) {
-		fatalError("init(coder:) has not been implemented")
-	}
-
-	func setName(_ name: String) {
-		label.text = Utils.getInitials(inputName: name)
-	}
-
-	func setColor(_ color: UIColor) {
-		backgroundColor = color
-	}
+    private var label: UILabel = {
+        let label = UILabel()
+        label.adjustsFontSizeToFitWidth = true
+        label.textAlignment = NSTextAlignment.center
+        label.textColor = UIColor.white
+        return label
+    }()
+
+    convenience init(name: String, color: UIColor, size: CGFloat) {
+        self.init(size: size)
+        setName(name)
+        setColor(color)
+    }
+
+    init(size: CGFloat) {
+        super.init(frame: CGRect(x: 0, y: 0, width: size, height: size))
+        let initialsLabelCornerRadius = size / 2
+        layer.cornerRadius = initialsLabelCornerRadius
+        translatesAutoresizingMaskIntoConstraints = false
+        heightAnchor.constraint(equalToConstant: size).isActive = true
+        widthAnchor.constraint(equalToConstant: size).isActive = true
+        clipsToBounds = true
+        setupSubviews()
+    }
+
+    private func setupSubviews() {
+        addSubview(label)
+        label.translatesAutoresizingMaskIntoConstraints = false
+        label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2).isActive = true
+        label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -2).isActive = true
+        label.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0).isActive = true
+    }
+
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+
+    func setName(_ name: String) {
+        label.text = Utils.getInitials(inputName: name)
+    }
+
+    func setColor(_ color: UIColor) {
+        backgroundColor = color
+    }
 }

+ 3 - 3
deltachat-ios/View/PhotoPickerAlertAction.swift

@@ -2,8 +2,8 @@ import UIKit
 
 class PhotoPickerAlertAction: UIAlertAction {
 
-	/*
-	This is intended to show a horizontal grid with a photo selection and a camera option
-	*/
+    /*
+     This is intended to show a horizontal grid with a photo selection and a camera option
+     */
 
 }

+ 36 - 36
deltachat-ios/View/ProgressHud.swift

@@ -1,49 +1,49 @@
 import JGProgressHUD
 
 class ProgressHud {
-  var hud: JGProgressHUD
+    var hud: JGProgressHUD
 
-  func error(_ message: String?, _ cb: (() -> Void)? = nil) {
-    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
-      UIView.animate(
-        withDuration: 0.1, animations: {
-          self.hud.textLabel.text = message ?? "Error"
-          self.hud.detailTextLabel.text = nil
-          self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+    func error(_ message: String?, _ cb: (() -> Void)? = nil) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+            UIView.animate(
+                withDuration: 0.1, animations: {
+                    self.hud.textLabel.text = message ?? "Error"
+                    self.hud.detailTextLabel.text = nil
+                    self.hud.indicatorView = JGProgressHUDErrorIndicatorView()
+                }
+            )
+            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(5000)) {
+                self.hud.dismiss()
+                cb?()
+            }
         }
-      )
-      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(5000)) {
-        self.hud.dismiss()
-        cb?()
-      }
     }
-  }
 
-  func done(_ message: String? = nil) {
-    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
-      UIView.animate(
-        withDuration: 0.1, animations: {
-          self.hud.textLabel.text = message ?? "Success"
-          self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
-        }
-      )
+    func done(_ message: String? = nil) {
+        DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+            UIView.animate(
+                withDuration: 0.1, animations: {
+                    self.hud.textLabel.text = message ?? "Success"
+                    self.hud.indicatorView = JGProgressHUDSuccessIndicatorView()
+                }
+            )
 
-      self.hud.dismiss(afterDelay: 1.0)
+            self.hud.dismiss(afterDelay: 1.0)
+        }
     }
-  }
 
-  func progress(_ progress: Int) {
-    hud.progress = Float(progress) / 1000.0
-    hud.detailTextLabel.text = "\(progress / 10)% Complete"
-  }
+    func progress(_ progress: Int) {
+        hud.progress = Float(progress) / 1000.0
+        hud.detailTextLabel.text = "\(progress / 10)% Complete"
+    }
 
-  init(_ text: String, in view: UIView) {
-    hud = JGProgressHUD(style: .dark)
-    hud.vibrancyEnabled = true
-    hud.indicatorView = JGProgressHUDPieIndicatorView()
+    init(_ text: String, in view: UIView) {
+        hud = JGProgressHUD(style: .dark)
+        hud.vibrancyEnabled = true
+        hud.indicatorView = JGProgressHUDPieIndicatorView()
 
-    hud.detailTextLabel.text = "0% Complete"
-    hud.textLabel.text = text
-    hud.show(in: view)
-  }
+        hud.detailTextLabel.text = "0% Complete"
+        hud.textLabel.text = text
+        hud.show(in: view)
+    }
 }

+ 35 - 35
deltachat-ios/View/QrCodeView.swift

@@ -1,47 +1,47 @@
 import UIKit
 
 class QRCodeView: UIView {
-  lazy var filter = CIFilter(name: "CIQRCodeGenerator")
-  lazy var imageView = UIImageView()
-
-  override init(frame: CGRect) {
-    super.init(frame: frame)
-    addSubview(imageView)
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  override func layoutSubviews() {
-    super.layoutSubviews()
-    imageView.frame = bounds
-  }
-
-  func generateCode(_ string: String, foregroundColor: UIColor = .black, backgroundColor: UIColor = .white) {
-    guard let filter = filter,
-      let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
-      return
+    lazy var filter = CIFilter(name: "CIQRCodeGenerator")
+    lazy var imageView = UIImageView()
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+        addSubview(imageView)
     }
 
-    filter.setValue(data, forKey: "inputMessage")
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 
-    guard let ciImage = filter.outputImage else {
-      return
+    override func layoutSubviews() {
+        super.layoutSubviews()
+        imageView.frame = bounds
     }
 
-    let transformed = ciImage.transformed(by: CGAffineTransform(scaleX: 10, y: 10))
-    let invertFilter = CIFilter(name: "CIColorInvert")
-    invertFilter?.setValue(transformed, forKey: kCIInputImageKey)
+    func generateCode(_ string: String, foregroundColor: UIColor = .black, backgroundColor: UIColor = .white) {
+        guard let filter = filter,
+            let data = string.data(using: .isoLatin1, allowLossyConversion: false) else {
+            return
+        }
+
+        filter.setValue(data, forKey: "inputMessage")
+
+        guard let ciImage = filter.outputImage else {
+            return
+        }
+
+        let transformed = ciImage.transformed(by: CGAffineTransform(scaleX: 10, y: 10))
+        let invertFilter = CIFilter(name: "CIColorInvert")
+        invertFilter?.setValue(transformed, forKey: kCIInputImageKey)
 
-    let alphaFilter = CIFilter(name: "CIMaskToAlpha")
-    alphaFilter?.setValue(invertFilter?.outputImage, forKey: kCIInputImageKey)
+        let alphaFilter = CIFilter(name: "CIMaskToAlpha")
+        alphaFilter?.setValue(invertFilter?.outputImage, forKey: kCIInputImageKey)
 
-    if let outputImage = alphaFilter?.outputImage {
-      imageView.tintColor = foregroundColor
-      imageView.backgroundColor = backgroundColor
-      imageView.image = UIImage(ciImage: outputImage, scale: 2.0, orientation: .up)
-        .withRenderingMode(.alwaysTemplate)
+        if let outputImage = alphaFilter?.outputImage {
+            imageView.tintColor = foregroundColor
+            imageView.backgroundColor = backgroundColor
+            imageView.image = UIImage(ciImage: outputImage, scale: 2.0, orientation: .up)
+                .withRenderingMode(.alwaysTemplate)
+        }
     }
-  }
 }

+ 107 - 107
deltachat-ios/View/TextFieldCell.swift

@@ -1,115 +1,115 @@
 import UIKit
 
 class TextFieldCell: UITableViewCell {
-  private let placeholder: String
-
-	var onTextFieldChange:((_:UITextField)->Void)?	// set this from outside to get notified about textfield changes
-
-  lazy var textField: UITextField = {
-    let textField = UITextField()
-    textField.textAlignment = .right
-    // textField.enablesReturnKeyAutomatically = true
-    textField.placeholder = self.placeholder
-		textField.addTarget(self, action: #selector(textFieldChanged), for: .editingChanged)
-    return textField
-  }()
-
-  init(description: String, placeholder: String, delegate: UITextFieldDelegate? = nil) {
-    self.placeholder = placeholder
-    super.init(style: .value1, reuseIdentifier: nil)
-    textLabel?.text = "\(description):"
-
-    // see: https://stackoverflow.com/a/35903650
-    // this makes the textField respect the trailing margin of
-    // the table view cell
-    selectionStyle = .none
-    setupViews()
-    textField.delegate = delegate
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  private func setupViews() {
-    contentView.addSubview(textField)
-    textField.translatesAutoresizingMaskIntoConstraints = false
-    let margins = contentView.layoutMarginsGuide
-    let trailing = margins.trailingAnchor
-    textField.trailingAnchor.constraint(equalTo: trailing).isActive = true
-    textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
-    if let label = self.textLabel {
-      textField.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 20).isActive = true // this will prevent the textfield from growing over the textLabel while typing
-    } else {
-      textField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
+    private let placeholder: String
+
+    var onTextFieldChange:((_:UITextField)->Void)?	// set this from outside to get notified about textfield changes
+
+    lazy var textField: UITextField = {
+        let textField = UITextField()
+        textField.textAlignment = .right
+        // textField.enablesReturnKeyAutomatically = true
+        textField.placeholder = self.placeholder
+        textField.addTarget(self, action: #selector(textFieldChanged), for: .editingChanged)
+        return textField
+    }()
+
+    init(description: String, placeholder: String, delegate: UITextFieldDelegate? = nil) {
+        self.placeholder = placeholder
+        super.init(style: .value1, reuseIdentifier: nil)
+        textLabel?.text = "\(description):"
+
+        // see: https://stackoverflow.com/a/35903650
+        // this makes the textField respect the trailing margin of
+        // the table view cell
+        selectionStyle = .none
+        setupViews()
+        textField.delegate = delegate
     }
-  }
 
-  override func setSelected(_ selected: Bool, animated _: Bool) {
-    if selected {
-      textField.becomeFirstResponder()
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
     }
-  }
-
-	@objc func textFieldChanged() {
-		onTextFieldChange?(self.textField)
-	}
-
-  func getText() -> String? {
-    if let text = textField.text {
-      if text.isEmpty {
-        return nil
-      } else {
-        return textField.text
-      }
-    } else {
-      return nil
+
+    private func setupViews() {
+        contentView.addSubview(textField)
+        textField.translatesAutoresizingMaskIntoConstraints = false
+        let margins = contentView.layoutMarginsGuide
+        let trailing = margins.trailingAnchor
+        textField.trailingAnchor.constraint(equalTo: trailing).isActive = true
+        textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
+        if let label = self.textLabel {
+            textField.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: 20).isActive = true // this will prevent the textfield from growing over the textLabel while typing
+        } else {
+            textField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20).isActive = true
+        }
+    }
+
+    override func setSelected(_ selected: Bool, animated _: Bool) {
+        if selected {
+            textField.becomeFirstResponder()
+        }
+    }
+
+    @objc func textFieldChanged() {
+        onTextFieldChange?(self.textField)
+    }
+
+    func getText() -> String? {
+        if let text = textField.text {
+            if text.isEmpty {
+                return nil
+            } else {
+                return textField.text
+            }
+        } else {
+            return nil
+        }
+    }
+
+    func setText(text: String?) {
+        textField.text = text
+    }
+
+    static func makeEmailCell(delegate: UITextFieldDelegate? = nil) -> TextFieldCell {
+        let cell = TextFieldCell(description: "Email", placeholder: "you@example.com")
+        cell.textField.keyboardType = .emailAddress
+        // switch off quicktype
+        cell.textField.autocorrectionType = .no
+        cell.textField.autocapitalizationType = .none
+        cell.textField.delegate = delegate
+        return cell
+    }
+
+    static func makePasswordCell(delegate _: UITextFieldDelegate? = nil) -> TextFieldCell {
+        let cell = TextFieldCell(description: "Password", placeholder: "your IMAP password")
+        cell.textField.textContentType = UITextContentType.password
+        cell.textField.isSecureTextEntry = true
+        return cell
+    }
+
+    static func makeNameCell(delegate: UITextFieldDelegate? = nil) -> TextFieldCell {
+        let cell = TextFieldCell(description: "Name", placeholder: "new contacts nickname")
+        cell.textField.autocapitalizationType = .words
+        cell.textField.autocorrectionType = .no
+        // .namePhonePad doesn't support autocapitalization
+        // see: https://stackoverflow.com/a/36365399
+        // therefore we use .default to capitalize the first character of the name
+        cell.textField.keyboardType = .default
+        cell.textField.delegate = delegate
+
+        return cell
+    }
+
+    static func makeConfigCell(label: String, placeholder: String, delegate: UITextFieldDelegate? = nil) -> TextFieldCell {
+        let cell = TextFieldCell(description: label, placeholder: placeholder)
+        cell.textField.autocapitalizationType = .words
+        cell.textField.autocorrectionType = .no
+        // .namePhonePad doesn't support autocapitalization
+        // see: https://stackoverflow.com/a/36365399
+        // therefore we use .default to capitalize the first character of the name
+        cell.textField.keyboardType = .default
+        cell.textField.delegate = delegate
+        return cell
     }
-  }
-
-  func setText(text: String?) {
-    textField.text = text
-  }
-
-  static func makeEmailCell(delegate: UITextFieldDelegate? = nil) -> TextFieldCell {
-    let cell = TextFieldCell(description: "Email", placeholder: "you@example.com")
-    cell.textField.keyboardType = .emailAddress
-    // switch off quicktype
-    cell.textField.autocorrectionType = .no
-    cell.textField.autocapitalizationType = .none
-    cell.textField.delegate = delegate
-    return cell
-  }
-
-  static func makePasswordCell(delegate _: UITextFieldDelegate? = nil) -> TextFieldCell {
-    let cell = TextFieldCell(description: "Password", placeholder: "your IMAP password")
-    cell.textField.textContentType = UITextContentType.password
-    cell.textField.isSecureTextEntry = true
-    return cell
-  }
-
-  static func makeNameCell(delegate: UITextFieldDelegate? = nil) -> TextFieldCell {
-    let cell = TextFieldCell(description: "Name", placeholder: "new contacts nickname")
-    cell.textField.autocapitalizationType = .words
-    cell.textField.autocorrectionType = .no
-    // .namePhonePad doesn't support autocapitalization
-    // see: https://stackoverflow.com/a/36365399
-    // therefore we use .default to capitalize the first character of the name
-    cell.textField.keyboardType = .default
-    cell.textField.delegate = delegate
-
-    return cell
-  }
-
-  static func makeConfigCell(label: String, placeholder: String, delegate: UITextFieldDelegate? = nil) -> TextFieldCell {
-    let cell = TextFieldCell(description: label, placeholder: placeholder)
-    cell.textField.autocapitalizationType = .words
-    cell.textField.autocorrectionType = .no
-    // .namePhonePad doesn't support autocapitalization
-    // see: https://stackoverflow.com/a/36365399
-    // therefore we use .default to capitalize the first character of the name
-    cell.textField.keyboardType = .default
-    cell.textField.delegate = delegate
-    return cell
-  }
 }

+ 23 - 23
deltachat-ios/View/TextFieldTableViewCell.swift

@@ -1,37 +1,37 @@
 import UIKit
 
 internal class TextFieldTableViewCell: UITableViewCell {
-  static let identifier = "TextFieldTableViewCellIdentifier"
+    static let identifier = "TextFieldTableViewCellIdentifier"
 
-  var mainLabel = UILabel()
-  var textField = UITextField()
+    var mainLabel = UILabel()
+    var textField = UITextField()
 
-  // MARK: - View lifecycle
+    // MARK: - View lifecycle
 
-  override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-    super.init(style: style, reuseIdentifier: reuseIdentifier)
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
 
-    mainLabel.translatesAutoresizingMaskIntoConstraints = false
-    textField.translatesAutoresizingMaskIntoConstraints = false
+        mainLabel.translatesAutoresizingMaskIntoConstraints = false
+        textField.translatesAutoresizingMaskIntoConstraints = false
 
-    contentView.addSubview(mainLabel)
-    contentView.addSubview(textField)
+        contentView.addSubview(mainLabel)
+        contentView.addSubview(textField)
 
-    NSLayoutConstraint.activate([
-      mainLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
-      mainLabel.widthAnchor.constraint(equalToConstant: 200),
-      mainLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+        NSLayoutConstraint.activate([
+            mainLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
+            mainLabel.widthAnchor.constraint(equalToConstant: 200),
+            mainLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
 
-      textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+            textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
 
-      textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
-      textField.widthAnchor.constraint(equalToConstant: 50),
-    ])
+            textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
+            textField.widthAnchor.constraint(equalToConstant: 50),
+        ])
 
-    textField.textAlignment = .right
-  }
+        textField.textAlignment = .right
+    }
 
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
+    required init?(coder _: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
 }

+ 36 - 36
deltachat-iosTests/deltachat_iosTests.swift

@@ -2,53 +2,53 @@
 import XCTest
 
 class DeltachatTests: XCTestCase {
-  var testContact: MRContact?
+    var testContact: MRContact?
 
-  override func setUp() {
-    let contactIds = Utils.getContactIds()
+    override func setUp() {
+        let contactIds = Utils.getContactIds()
 
-    let contacts = contactIds.map { MRContact(id: $0) }
+        let contacts = contactIds.map { MRContact(id: $0) }
 
-    testContact = contacts.filter { $0.name == "John Appleseed" }.first
-    // Put setup code here. This method is called before the invocation of each test method in the class.
-  }
-
-  override func tearDown() {
-    // Put teardown code here. This method is called after the invocation of each test method in the class.
-  }
+        testContact = contacts.filter { $0.name == "John Appleseed" }.first
+        // Put setup code here. This method is called before the invocation of each test method in the class.
+    }
 
-  func testContactSearchForSubsequences() {
-    // this test will only success if run on a simulator (assuming one of the sample contacts is named John Appleseed)
-    guard let appleseedContact = testContact else {
-      return
+    override func tearDown() {
+        // Put teardown code here. This method is called after the invocation of each test method in the class.
     }
 
-    XCTAssert(appleseedContact.name == "John Appleseed", "Test contacts name is John Appleseed")
-    XCTAssert(appleseedContact.email == "John-Appleseed@mac.com", "Test contacts email is john.appleseed@mac.com")
+    func testContactSearchForSubsequences() {
+        // this test will only success if run on a simulator (assuming one of the sample contacts is named John Appleseed)
+        guard let appleseedContact = testContact else {
+            return
+        }
 
-    let indexDetailA = appleseedContact.contains(searchText: "jmc")
+        XCTAssert(appleseedContact.name == "John Appleseed", "Test contacts name is John Appleseed")
+        XCTAssert(appleseedContact.email == "John-Appleseed@mac.com", "Test contacts email is john.appleseed@mac.com")
 
-    XCTAssert(indexDetailA.count == 2)
-    XCTAssert(indexDetailA[0].contactDetail == .NAME)
-    XCTAssert(indexDetailA[0].indexes == [0])
-    XCTAssert(indexDetailA[1].indexes == [15, 17])
+        let indexDetailA = appleseedContact.contains(searchText: "jmc")
 
-    let indexDetailB = appleseedContact.contains(searchText: "joj")
-    XCTAssert(indexDetailB[0].indexes == [0, 1])
-    XCTAssert(indexDetailB[1].indexes == [0])
+        XCTAssert(indexDetailA.count == 2)
+        XCTAssert(indexDetailA[0].contactDetail == .NAME)
+        XCTAssert(indexDetailA[0].indexes == [0])
+        XCTAssert(indexDetailA[1].indexes == [15, 17])
 
-    let indexDetailC = appleseedContact.contains(searchText: "jojh")
-    XCTAssert(indexDetailC[0].indexes == [0, 1])
-    XCTAssert(indexDetailC[1].indexes == [0, 2])
+        let indexDetailB = appleseedContact.contains(searchText: "joj")
+        XCTAssert(indexDetailB[0].indexes == [0, 1])
+        XCTAssert(indexDetailB[1].indexes == [0])
 
-    let indexDetailD = appleseedContact.contains(searchText: "joz")
-    XCTAssert(indexDetailD.isEmpty)
-  }
+        let indexDetailC = appleseedContact.contains(searchText: "jojh")
+        XCTAssert(indexDetailC[0].indexes == [0, 1])
+        XCTAssert(indexDetailC[1].indexes == [0, 2])
+
+        let indexDetailD = appleseedContact.contains(searchText: "joz")
+        XCTAssert(indexDetailD.isEmpty)
+    }
 
-  func testPerformanceExample() {
-    // This is an example of a performance test case.
-    measure {
-      // Put the code you want to measure the time of here.
+    func testPerformanceExample() {
+        // This is an example of a performance test case.
+        measure {
+            // Put the code you want to measure the time of here.
+        }
     }
-  }
 }