Prechádzať zdrojové kódy

chore: better linting and formatting

- add swiftlint
- fix swiftlint warnings
- add swiftformat config
- run swiftformat on build
- run swiftlint on build
dignifiedquire 6 rokov pred
rodič
commit
0b6f2c1f42
69 zmenil súbory, kde vykonal 6984 pridanie a 4985 odobranie
  1. 3 0
      .swiftformat
  2. 47 0
      .swiftlint.yml
  3. 6 0
      Podfile
  4. 11 3
      Podfile.lock
  5. 11 3
      Pods/Manifest.lock
  6. 902 898
      Pods/Pods.xcodeproj/project.pbxproj
  7. 48 32
      Pods/QuickTableViewController/README.md
  8. 131 0
      Pods/QuickTableViewController/Source/Model/Deprecated.swift
  9. 61 0
      Pods/QuickTableViewController/Source/Model/DetailText.swift
  10. 0 14
      Pods/QuickTableViewController/Source/Model/Icon.swift
  11. 1 1
      Pods/QuickTableViewController/Source/Model/Section.swift
  12. 18 35
      Pods/QuickTableViewController/Source/Model/Subtitle.swift
  13. 2 2
      Pods/QuickTableViewController/Source/Protocol/Configurable.swift
  14. 4 4
      Pods/QuickTableViewController/Source/Protocol/Row.swift
  15. 6 1
      Pods/QuickTableViewController/Source/Protocol/RowCompatible.swift
  16. 2 2
      Pods/QuickTableViewController/Source/Protocol/RowStyle.swift
  17. 13 2
      Pods/QuickTableViewController/Source/QuickTableViewController.swift
  18. 73 18
      Pods/QuickTableViewController/Source/Rows/NavigationRow.swift
  19. 15 12
      Pods/QuickTableViewController/Source/Rows/OptionRow.swift
  20. 15 12
      Pods/QuickTableViewController/Source/Rows/SwitchRow.swift
  21. 13 11
      Pods/QuickTableViewController/Source/Rows/TapActionRow.swift
  22. 1 1
      Pods/QuickTableViewController/Source/Views/SwitchCell.swift
  23. 1 1
      Pods/QuickTableViewController/Source/Views/TapActionCell.swift
  24. BIN
      Pods/SwiftFormat/CommandLineTool/swiftformat
  25. 21 0
      Pods/SwiftFormat/LICENSE.md
  26. 1457 0
      Pods/SwiftFormat/README.md
  27. 21 0
      Pods/SwiftLint/LICENSE
  28. BIN
      Pods/SwiftLint/swiftlint
  29. 1 1
      Pods/Target Support Files/ALCameraViewController/ALCameraViewController.xcconfig
  30. 1 1
      Pods/Target Support Files/MessageInputBar/MessageInputBar.xcconfig
  31. 1 1
      Pods/Target Support Files/MessageKit/MessageKit.xcconfig
  32. 50 0
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.markdown
  33. 62 0
      Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.plist
  34. 1 1
      Pods/Target Support Files/QuickTableViewController/Info.plist
  35. 1 1
      Pods/Target Support Files/QuickTableViewController/QuickTableViewController.xcconfig
  36. 1 1
      Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift.xcconfig
  37. 1 1
      Pods/Target Support Files/SwiftyBeaver/SwiftyBeaver.xcconfig
  38. 40 0
      deltachat-ios.xcodeproj/project.pbxproj
  39. 11 11
      deltachat-ios/AppCoordinator.swift
  40. 237 237
      deltachat-ios/AppDelegate.swift
  41. 42 42
      deltachat-ios/AppTabBarController.swift
  42. 51 51
      deltachat-ios/BaseController.swift
  43. 689 683
      deltachat-ios/ChatViewController.swift
  44. 16 16
      deltachat-ios/Constants.swift
  45. 118 118
      deltachat-ios/ContactCell.swift
  46. 94 94
      deltachat-ios/ContactProfileViewController.swift
  47. 27 27
      deltachat-ios/CustomCell.swift
  48. 18 18
      deltachat-ios/Extensions/UIImage+Extension.swift
  49. 33 33
      deltachat-ios/Extensions/UIViewController+Extension.swift
  50. 66 66
      deltachat-ios/GroupNameController.swift
  51. 58 58
      deltachat-ios/Message.swift
  52. 91 91
      deltachat-ios/MessageInfoViewController.swift
  53. 39 39
      deltachat-ios/NavigationController.swift
  54. 179 179
      deltachat-ios/NewChatViewController.swift
  55. 111 111
      deltachat-ios/NewContactController.swift
  56. 63 63
      deltachat-ios/NewGroupMemberChoiceController.swift
  57. 54 54
      deltachat-ios/NewGroupViewController.swift
  58. 40 40
      deltachat-ios/ProgressHud.swift
  59. 64 64
      deltachat-ios/QrCodeReaderController.swift
  60. 35 35
      deltachat-ios/QrCodeView.swift
  61. 23 23
      deltachat-ios/TableViewCell.swift
  62. 59 59
      deltachat-ios/TextFieldCell.swift
  63. 155 155
      deltachat-ios/TopViews/ChatListController.swift
  64. 69 69
      deltachat-ios/TopViews/ContactListController.swift
  65. 120 120
      deltachat-ios/TopViews/ProfileViewController.swift
  66. 364 362
      deltachat-ios/TopViews/SettingsController.swift
  67. 142 136
      deltachat-ios/Utils.swift
  68. 694 692
      deltachat-ios/Wrapper.swift
  69. 180 180
      deltachat-ios/events.swift

+ 3 - 0
.swiftformat

@@ -0,0 +1,3 @@
+--indent 2
+
+--exclude Pods,Generated

+ 47 - 0
.swiftlint.yml

@@ -0,0 +1,47 @@
+opt_in_rules:
+- empty_count
+- empty_string
+
+excluded:
+- Carthage
+- Pods
+- SwiftLint/Common/3rdPartyLib
+
+force_cast:
+  warning
+
+identifier_name:
+  min_length:
+    warning: 1
+    error: 0
+  max_length: 30
+
+line_length:
+  warning: 150
+  error: 200
+  ignores_function_declarations: true
+  ignores_comments: true
+  ignores_urls: true
+
+function_body_length:
+  warning: 300
+  error: 500
+
+function_parameter_count:
+  warning: 6
+  error: 8
+
+type_body_length:
+  warning: 300
+  error: 500
+
+file_length:
+  warning: 1000
+  error: 1500
+  ignore_comment_only_lines: true
+
+cyclomatic_complexity:
+  warning: 15
+  error: 25
+
+reporter: "xcode"

+ 6 - 0
Podfile

@@ -1,7 +1,13 @@
 target 'deltachat-ios' do
+  platform :ios, '10.0'
   use_frameworks!
   swift_version = '4.2'
   
+  # ignore all warnings from all dependencies
+  inhibit_all_warnings!
+  
+  pod 'SwiftLint'
+  pod 'SwiftFormat/CLI'
   pod 'ALCameraViewController', :git => 'https://github.com/dignifiedquire/ALCameraViewController'
   pod 'openssl-ios-bitcode', '1.0.210'
   pod 'ReachabilitySwift'

+ 11 - 3
Podfile.lock

@@ -6,8 +6,10 @@ PODS:
   - MessageKit (2.0.0):
     - MessageInputBar/Core
   - openssl-ios-bitcode (1.0.210)
-  - QuickTableViewController (1.0.0)
+  - QuickTableViewController (1.1.0)
   - ReachabilitySwift (4.3.0)
+  - SwiftFormat/CLI (0.37.2)
+  - SwiftLint (0.29.3)
   - SwiftyBeaver (1.6.1)
 
 DEPENDENCIES:
@@ -18,6 +20,8 @@ DEPENDENCIES:
   - openssl-ios-bitcode (= 1.0.210)
   - QuickTableViewController
   - ReachabilitySwift
+  - SwiftFormat/CLI
+  - SwiftLint
   - SwiftyBeaver
 
 SPEC REPOS:
@@ -29,6 +33,8 @@ SPEC REPOS:
     - openssl-ios-bitcode
     - QuickTableViewController
     - ReachabilitySwift
+    - SwiftFormat
+    - SwiftLint
     - SwiftyBeaver
 
 EXTERNAL SOURCES:
@@ -47,10 +53,12 @@ SPEC CHECKSUMS:
   MessageInputBar: e81c7535347f1f7b923de7080409a535a004b6e4
   MessageKit: 29c1c87e5a396d2ca7a3f712e5171dc9aba42a1e
   openssl-ios-bitcode: c833701a4488bd43de0051db41cfa75f6fef8109
-  QuickTableViewController: a49fb6dc5623b9dbe301a03ac5c563099e234fbc
+  QuickTableViewController: 129e71fc37a69991aff02bc946723d51a13d6aa5
   ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3
+  SwiftFormat: abbfb26dde4c6d683923cde153c88e10da9d7881
+  SwiftLint: bfa7ca7b4d170cfaf0d236ca3ffd969e88a2f002
   SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a
 
-PODFILE CHECKSUM: f1e841a85f483feff977aea1a006be4b735b4bf8
+PODFILE CHECKSUM: 07c0360b910556b36e03f48076744fe0a090b161
 
 COCOAPODS: 1.5.3

+ 11 - 3
Pods/Manifest.lock

@@ -6,8 +6,10 @@ PODS:
   - MessageKit (2.0.0):
     - MessageInputBar/Core
   - openssl-ios-bitcode (1.0.210)
-  - QuickTableViewController (1.0.0)
+  - QuickTableViewController (1.1.0)
   - ReachabilitySwift (4.3.0)
+  - SwiftFormat/CLI (0.37.2)
+  - SwiftLint (0.29.3)
   - SwiftyBeaver (1.6.1)
 
 DEPENDENCIES:
@@ -18,6 +20,8 @@ DEPENDENCIES:
   - openssl-ios-bitcode (= 1.0.210)
   - QuickTableViewController
   - ReachabilitySwift
+  - SwiftFormat/CLI
+  - SwiftLint
   - SwiftyBeaver
 
 SPEC REPOS:
@@ -29,6 +33,8 @@ SPEC REPOS:
     - openssl-ios-bitcode
     - QuickTableViewController
     - ReachabilitySwift
+    - SwiftFormat
+    - SwiftLint
     - SwiftyBeaver
 
 EXTERNAL SOURCES:
@@ -47,10 +53,12 @@ SPEC CHECKSUMS:
   MessageInputBar: e81c7535347f1f7b923de7080409a535a004b6e4
   MessageKit: 29c1c87e5a396d2ca7a3f712e5171dc9aba42a1e
   openssl-ios-bitcode: c833701a4488bd43de0051db41cfa75f6fef8109
-  QuickTableViewController: a49fb6dc5623b9dbe301a03ac5c563099e234fbc
+  QuickTableViewController: 129e71fc37a69991aff02bc946723d51a13d6aa5
   ReachabilitySwift: 408477d1b6ed9779dba301953171e017c31241f3
+  SwiftFormat: abbfb26dde4c6d683923cde153c88e10da9d7881
+  SwiftLint: bfa7ca7b4d170cfaf0d236ca3ffd969e88a2f002
   SwiftyBeaver: ccfcdf85a04d429f1633f668650b0ce8020bda3a
 
-PODFILE CHECKSUM: f1e841a85f483feff977aea1a006be4b735b4bf8
+PODFILE CHECKSUM: 07c0360b910556b36e03f48076744fe0a090b161
 
 COCOAPODS: 1.5.3

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 902 - 898
Pods/Pods.xcodeproj/project.pbxproj


+ 48 - 32
Pods/QuickTableViewController/README.md

@@ -13,7 +13,7 @@ A simple way to create a table view for settings, including:
 * Table view cells with center aligned text for tap actions
 * A section that provides mutually exclusive options
 * Actions performed when the row reacts to the user interaction
-* Customizable table view cell image, cell style and accessory type
+* Easy to specify table view cell image, cell style and accessory type
 
 <img src="https://bcylin.github.io/QuickTableViewController/img/screenshots.png" width="80%"></img>
 
@@ -24,32 +24,32 @@ Set up `tableContents` in `viewDidLoad`:
 ```swift
 import QuickTableViewController
 
-class ViewController: QuickTableViewController {
+final class ViewController: QuickTableViewController {
 
   override func viewDidLoad() {
     super.viewDidLoad()
 
     tableContents = [
       Section(title: "Switch", rows: [
-        SwitchRow(title: "Setting 1", switchValue: true, action: { _ in }),
-        SwitchRow(title: "Setting 2", switchValue: false, action: { _ in }),
+        SwitchRow(text: "Setting 1", switchValue: true, action: { _ in }),
+        SwitchRow(text: "Setting 2", switchValue: false, action: { _ in })
       ]),
 
       Section(title: "Tap Action", rows: [
-        TapActionRow(title: "Tap action", action: { [weak self] in self?.showAlert($0) })
+        TapActionRow(text: "Tap action", action: { [weak self] in self?.showAlert($0) })
       ]),
 
       Section(title: "Navigation", rows: [
-        NavigationRow(title: "CellStyle.default", subtitle: .none, icon: .named("gear")),
-        NavigationRow(title: "CellStyle", subtitle: .belowTitle(".subtitle"), icon: .named("globe")),
-        NavigationRow(title: "CellStyle", subtitle: .rightAligned(".value1"), icon: .named("time"), action: { _ in }),
-        NavigationRow(title: "CellStyle", subtitle: .leftAligned(".value2"))
-      ]),
+        NavigationRow(text: "CellStyle.default", detailText: .none, icon: .named("gear")),
+        NavigationRow(text: "CellStyle", detailText: .subtitle(".subtitle"), icon: .named("globe")),
+        NavigationRow(text: "CellStyle", detailText: .value1(".value1"), icon: .named("time"), action: { _ in }),
+        NavigationRow(text: "CellStyle", detailText: .value2(".value2"))
+      ], footer: "UITableViewCellStyle.Value2 hides the image view."),
 
       RadioSection(title: "Radio Buttons", options: [
-        OptionRow(title: "Option 1", isSelected: true, action: didToggleOption()),
-        OptionRow(title: "Option 2", isSelected: false, action: didToggleOption()),
-        OptionRow(title: "Option 3", isSelected: false, action: didToggleOption())
+        OptionRow(text: "Option 1", isSelected: true, action: didToggleSelection()),
+        OptionRow(text: "Option 2", isSelected: false, action: didToggleSelection()),
+        OptionRow(text: "Option 3", isSelected: false, action: didToggleSelection())
       ], footer: "See RadioSection for more details.")
     ]
   }
@@ -60,7 +60,7 @@ class ViewController: QuickTableViewController {
     // ...
   }
 
-  private func didToggleOption() -> (Row) -> Void {
+  private func didToggleSelection() -> (Row) -> Void {
     return { [weak self] row in
       // ...
     }
@@ -71,19 +71,34 @@ class ViewController: QuickTableViewController {
 
 ### NavigationRow
 
-#### Subtitle Styles
+#### Detail Text Styles
 
 ```swift
-NavigationRow(title: "UITableViewCellStyle.default", subtitle: .none)
-NavigationRow(title: "UITableViewCellStyle", subtitle: .belowTitle(".subtitle")
-NavigationRow(title: "UITableViewCellStyle", subtitle: .rightAligned(".value1")
-NavigationRow(title: "UITableViewCellStyle", subtitle: .leftAligned(".value2"))
+NavigationRow(text: "UITableViewCellStyle.default", detailText: .none)
+NavigationRow(text: "UITableViewCellStyle", detailText: .subtitle(".subtitle")
+NavigationRow(text: "UITableViewCellStyle", detailText: .value1(".value1")
+NavigationRow(text: "UITableViewCellStyle", detailText: .value2(".value2"))
 ```
 
-#### Disclosure Indicator
+[`Subtitle`](https://github.com/bcylin/QuickTableViewController/blob/develop/Source/Model/Subtitle.swift) and the [initializers with title/subtitle](https://github.com/bcylin/QuickTableViewController/blob/develop/Source/Model/Deprecated.swift) are deprecated and will be removed in **v2.0.0**.
+
+#### Accessory Type
+
+* The `NavigationRow` shows with different accessory types based on the `action` and `accessoryButtonAction` closures:
+
+```swift
+var accessoryType: UITableViewCell.AccessoryType {
+  switch (action, accessoryButtonAction) {
+  case (nil, nil):      return .none
+  case (.some, nil):    return .disclosureIndicator
+  case (nil, .some):    return .detailButton
+  case (.some, .some):  return .detailDisclosureButton
+  }
+}
+```
 
-* A `NavigationRow` with an `action` will be displayed in a table view cell with `.disclosureIndicator`.
 * The `action` will be invoked when the table view cell is selected.
+* The `accessoryButtonAction` will be invoked when the accessory button is selected.
 
 #### Images
 
@@ -102,32 +117,31 @@ enum Icon {
 
 * A `SwitchRow` is representing a table view cell with a `UISwitch` as its `accessoryView`.
 * The `action` will be invoked when the switch value changes.
-* The subtitle is disabled in `SwitchRow `.
 
 ### TapActionRow
 
 * A `TapActionRow` is representing a button-like table view cell.
 * The `action` will be invoked when the table view cell is selected.
-* The icon and subtitle are disabled in `TapActionRow`.
+* The icon, detail text, and accessory type are disabled in `TapActionRow`.
 
 ### OptionRow
 
 * An `OptionRow` is representing a table view cell with `.checkmark`.
-* The subtitle is disabled in `OptionRow`.
 * The `action` will be invoked when the selected state is toggled.
 
 ```swift
-let didToggleOption: (Row) -> Void = { [weak self] in
+let didToggleSelection: (Row) -> Void = { [weak self] in
   if let option = $0 as? OptionRowCompatible, option.isSelected {
-    // to exclude the option that's toggled off
+    // to exclude the event where the option is toggled off
   }
 }
 ```
 
 ### RadioSection
 
-* `OptionRow` can be used with or without `RadioSection`, which allows only one selected option.
+* `RadioSection` allows only one selected option at a time.
 * Setting `alwaysSelectsOneOption` to true will keep one of the options selected.
+* `OptionRow` can also be used with `Section` for multiple selections.
 
 ## Customization
 
@@ -146,16 +160,16 @@ A customized table view cell type can be specified to rows during initialization
 
 ```swift
 // Default is UITableViewCell.
-NavigationRow<CustomCell>(title: "Navigation", subtitle: .none)
+NavigationRow<CustomCell>(text: "Navigation", detailText: .none)
 
 // Default is SwitchCell.
-SwitchRow<CustomSwitchCell>(title: "Switch", switchValue: true, action: { _ in })
+SwitchRow<CustomSwitchCell>(text: "Switch", switchValue: true, action: { _ in })
 
 // Default is TapActionCell.
-TapActionRow<CustomTapActionCell>(title: "Tap", action: { _ in })
+TapActionRow<CustomTapActionCell>(text: "Tap", action: { _ in })
 
 // Default is UITableViewCell.
-OptionRow<CustomOptionCell>(title: "Option", isSelected: true, action: { _ in })
+OptionRow<CustomOptionCell>(text: "Option", isSelected: true, action: { _ in })
 ```
 
 Since the rows carry different cell types, they can be matched using either the concrete types or the related protocol:
@@ -193,7 +207,7 @@ protocol RowStyle {
 }
 ```
 
-The `customize` closure overwrites the `Configurable` setup.
+The `customize` closure [overwrites](https://github.com/bcylin/QuickTableViewController/blob/develop/Source/QuickTableViewController.swift#L104-L109) the `Configurable` setup.
 
 ### UIAppearance
 
@@ -203,6 +217,7 @@ As discussed in issue [#12](https://github.com/bcylin/QuickTableViewController/i
 
 * `UISwitch` is replaced by a checkmark in `SwitchCell`.
 * `TapActionCell` does not use center aligned text.
+* `NavigationRow.accessoryButtonAction` is not available.
 * Cell image view's left margin is 0.
 
 ## Limitation
@@ -236,6 +251,7 @@ QuickTableViewController | iOS  | tvOS | Xcode | Swift
 `~> 0.8.0`               | 8.0+ | -    | 9.1   | 4.0
 `~> 0.9.0`               | 8.0+ | -    | 9.3   | 4.1
 `~> 1.0.0`               | 8.0+ | 9.0+ | 9.4   | 4.1
+`~> 1.1.0`               | 8.0+ | 9.0+ | 10.1  | 4.2
 
 ## Installation
 

+ 131 - 0
Pods/QuickTableViewController/Source/Model/Deprecated.swift

@@ -0,0 +1,131 @@
+//
+//  Deprecated.swift
+//  QuickTableViewController
+//
+//  Created by bcylin on 01/01/2019.
+//  Copyright © 2019 bcylin.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in all
+//  copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+//  SOFTWARE.
+//
+
+import Foundation
+
+public extension Row {
+
+  @available(*, deprecated, message: "Use `text` instead.")
+  public var title: String {
+    return text
+  }
+
+  @available(*, deprecated, message: "Use `detailText` instead.")
+  public var subtitle: Subtitle? {
+    return detailText?.subtitle
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+public extension NavigationRow {
+
+  @available(*, deprecated, message: "Use `init(text:detailText:icon:customization:action:)` instead.")
+  public convenience init(
+    title: String,
+    subtitle: Subtitle,
+    icon: Icon? = nil,
+    customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
+    action: ((Row) -> Void)? = nil
+  ) {
+    self.init(
+      text: title,
+      detailText: subtitle.detailText,
+      icon: icon,
+      customization: customization,
+      action: action
+    )
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+public extension OptionRow {
+
+  @available(*, deprecated, message: "Use `init(text:detailText:isSelected:icon:customization:action:)` instead.")
+  public convenience init(
+    title: String,
+    isSelected: Bool,
+    icon: Icon? = nil,
+    customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
+    action: ((Row) -> Void)?
+  ) {
+    self.init(
+      text: title,
+      detailText: nil,
+      isSelected: isSelected,
+      icon: icon,
+      customization: customization,
+      action: action
+    )
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+public extension SwitchRow {
+
+  @available(*, deprecated, message: "Use `init(text:detailText:switchValue:icon:customization:action:)` instead.")
+  public convenience init(
+    title: String,
+    switchValue: Bool,
+    icon: Icon? = nil,
+    customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
+    action: ((Row) -> Void)?
+  ) {
+    self.init(
+      text: title,
+      detailText: nil,
+      switchValue: switchValue,
+      icon: icon,
+      customization: customization,
+      action: action
+    )
+  }
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+public extension TapActionRow {
+
+  @available(*, deprecated, message: "Use `init(text:customization:action:)` instead.")
+  public convenience init(
+    title: String,
+    customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
+    action: ((Row) -> Void)?
+  ) {
+    self.init(
+      text: title,
+      customization: customization,
+      action: action
+    )
+  }
+
+}

+ 61 - 0
Pods/QuickTableViewController/Source/Model/DetailText.swift

@@ -0,0 +1,61 @@
+//
+//  DetailText.swift
+//  QuickTableViewController
+//
+//  Created by bcylin on 31/12/2018.
+//  Copyright © 2018 bcylin.
+//
+//  Permission is hereby granted, free of charge, to any person obtaining a copy
+//  of this software and associated documentation files (the "Software"), to deal
+//  in the Software without restriction, including without limitation the rights
+//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+//  copies of the Software, and to permit persons to whom the Software is
+//  furnished to do so, subject to the following conditions:
+//
+//  The above copyright notice and this permission notice shall be included in all
+//  copies or substantial portions of the Software.
+//
+//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+//  SOFTWARE.
+//
+
+import UIKit
+
+/// An enum that represents a detail text with `UITableViewCell.CellStyle`.
+public enum DetailText: Equatable {
+
+  /// Does not show a detail text in `UITableViewCell.CellStyle.default`.
+  case none
+  /// Shows the detail text in `UITableViewCell.CellStyle.subtitle`.
+  case subtitle(String)
+  /// Shows the detail text in `UITableViewCell.CellStyle.value1`.
+  case value1(String)
+  /// Shows the detail text in `UITableViewCell.CellStyle.value2`.
+  case value2(String)
+
+  /// Returns the corresponding table view cell style.
+  public var style: UITableViewCell.CellStyle {
+    switch self {
+    case .none:     return .default
+    case .subtitle: return .subtitle
+    case .value1:   return .value1
+    case .value2:   return .value2
+    }
+  }
+
+  /// Returns the associated text of the case.
+  public var text: String? {
+    switch self {
+    case .none:
+      return nil
+    case let .subtitle(text), let .value1(text), let .value2(text):
+      return text
+    }
+  }
+
+}

+ 0 - 14
Pods/QuickTableViewController/Source/Model/Icon.swift

@@ -61,18 +61,4 @@ public enum Icon: Equatable {
     }
   }
 
-  // MARK: Equatable
-
-  /// Returns true iff `lhs` and `rhs` have equal images and highlighted images.
-  public static func == (lhs: Icon, rhs: Icon) -> Bool {
-    switch (lhs, rhs) {
-    case let (.named(left), .named(right)):
-      return left == right
-    default:
-      return
-        lhs.image == rhs.image &&
-        lhs.highlightedImage == rhs.highlightedImage
-    }
-  }
-
 }

+ 1 - 1
Pods/QuickTableViewController/Source/Model/Section.swift

@@ -41,7 +41,7 @@ open class Section {
   // MARK: - Properties
 
   /// The text of the section title.
-  open let title: String?
+  public let title: String?
 
   /// The array of rows in the section.
   open var rows: [Row & RowStyle]

+ 18 - 35
Pods/QuickTableViewController/Source/Model/Subtitle.swift

@@ -26,7 +26,7 @@
 
 import UIKit
 
-/// An enum that represents a subtitle text with `UITableViewCellStyle`.
+@available(*, deprecated, message: "Use `DetailText` instead.")
 public enum Subtitle: Equatable {
 
   /// Does not show a subtitle as `UITableViewCellStyle.default`.
@@ -39,54 +39,37 @@ public enum Subtitle: Equatable {
   case leftAligned(String)
 
   /// Returns the corresponding table view cell style.
-  public var style: UITableViewCellStyle {
-    switch self {
-    case .none:          return .default
-    case .belowTitle:    return .subtitle
-    case .rightAligned:  return .value1
-    case .leftAligned:   return .value2
-    }
+  public var style: UITableViewCell.CellStyle {
+    return detailText.style
   }
 
   /// Returns the associated text of the case.
   public var text: String? {
-    switch self {
-    case let .belowTitle(text):   return text
-    case let .rightAligned(text): return text
-    case let .leftAligned(text):  return text
-    default:                      return nil
-    }
+    return detailText.text
   }
 
-  // MARK: Equatable
-
-  /// Returns true iff `lhs` and `rhs` have equal texts in the same `Subtitle`.
-  public static func == (lhs: Subtitle, rhs: Subtitle) -> Bool {
-    switch (lhs, rhs) {
-    case (.none, .none):
-      return true
-    case let (.belowTitle(a), .belowTitle(b)):
-      return a == b
-    case let (.rightAligned(a), .rightAligned(b)):
-      return a == b
-    case let (.leftAligned(a), .leftAligned(b)):
-      return a == b
-    default:
-      return false
+  @available(*, deprecated, message: "The conversion between Subtitle and DetailText.")
+  internal var detailText: DetailText {
+    switch self {
+    case .none:                   return .none
+    case let .belowTitle(text):   return .subtitle(text)
+    case let .rightAligned(text): return .value1(text)
+    case let .leftAligned(text):  return .value2(text)
     }
   }
 
 }
 
 
-internal extension UITableViewCellStyle {
+internal extension DetailText {
 
-  var stringValue: String {
+  @available(*, deprecated, message: "The conversion between DetailText and Subtitle.")
+  internal var subtitle: Subtitle {
     switch self {
-    case .default:  return ".default"
-    case .subtitle: return ".subtitle"
-    case .value1:   return ".value1"
-    case .value2:   return ".value2"
+    case .none:               return .none
+    case let .subtitle(text): return .belowTitle(text)
+    case let .value1(text):   return .rightAligned(text)
+    case let .value2(text):   return .leftAligned(text)
     }
   }
 

+ 2 - 2
Pods/QuickTableViewController/Source/Protocol/Configurable.swift

@@ -36,8 +36,8 @@ public protocol Configurable {
 extension UITableViewCell {
 
   internal func defaultSetUp(with row: Row & RowStyle) {
-    textLabel?.text = row.title
-    detailTextLabel?.text = row.subtitle?.text
+    textLabel?.text = row.text
+    detailTextLabel?.text = row.detailText?.text
 
     // Reset the accessory view in case the cell is reused.
     accessoryView = nil

+ 4 - 4
Pods/QuickTableViewController/Source/Protocol/Row.swift

@@ -29,11 +29,11 @@ import Foundation
 /// Any type that conforms to this protocol is capable of representing a row in a table view.
 public protocol Row: class {
 
-  /// The title text of the row.
-  var title: String { get }
+  /// The text of the row.
+  var text: String { get }
 
-  /// The subtitle text of the row.
-  var subtitle: Subtitle? { get }
+  /// The detail text of the row.
+  var detailText: DetailText? { get }
 
   /// A closure related to the action of the row.
   var action: ((Row) -> Void)? { get }

+ 6 - 1
Pods/QuickTableViewController/Source/Protocol/RowCompatible.swift

@@ -27,7 +27,12 @@
 import Foundation
 
 /// This protocol defines the compatible interface of a `NavigationRow` regardless of its associated cell type.
-public protocol NavigationRowCompatible: Row, RowStyle {}
+public protocol NavigationRowCompatible: Row, RowStyle {
+  #if os(iOS)
+  /// A closure that will be invoked when the accessory button is selected.
+  var accessoryButtonAction: ((Row) -> Void)? { get }
+  #endif
+}
 
 
 /// This protocol defines the compatible interface of a `TapActionRow` regardless of its associated cell type.

+ 2 - 2
Pods/QuickTableViewController/Source/Protocol/RowStyle.swift

@@ -36,13 +36,13 @@ public protocol RowStyle {
   var cellReuseIdentifier: String { get }
 
   /// The style of the table view cell to display the row.
-  var cellStyle: UITableViewCellStyle { get }
+  var cellStyle: UITableViewCell.CellStyle { get }
 
   /// The icon of the row.
   var icon: Icon? { get }
 
   /// The type of standard accessory view the cell should use.
-  var accessoryType: UITableViewCellAccessoryType { get }
+  var accessoryType: UITableViewCell.AccessoryType { get }
 
   /// The flag that indicates whether the table view cell should trigger the action when selected.
   var isSelectable: Bool { get }

+ 13 - 2
Pods/QuickTableViewController/Source/QuickTableViewController.swift

@@ -51,7 +51,7 @@ open class QuickTableViewController: UIViewController, UITableViewDataSource, UI
 
    - returns: An initialized `QuickTableViewController` object.
    */
-  public convenience init(style: UITableViewStyle) {
+  public convenience init(style: UITableView.Style) {
     self.init(nibName: nil, bundle: nil)
     tableView = UITableView(frame: .zero, style: style)
   }
@@ -68,7 +68,7 @@ open class QuickTableViewController: UIViewController, UITableViewDataSource, UI
     view.addSubview(tableView)
     tableView.frame = view.bounds
     tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
-    tableView.rowHeight = UITableViewAutomaticDimension
+    tableView.rowHeight = UITableView.automaticDimension
     tableView.estimatedRowHeight = 44
     tableView.dataSource = self
     tableView.delegate = self
@@ -165,6 +165,17 @@ open class QuickTableViewController: UIViewController, UITableViewDataSource, UI
     }
   }
 
+  #if os(iOS)
+  public func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
+    switch tableContents[indexPath.section].rows[indexPath.row] {
+    case let row as NavigationRowCompatible:
+      row.accessoryButtonAction?(row)
+    default:
+      break
+    }
+  }
+  #endif
+
 }
 
 

+ 73 - 18
Pods/QuickTableViewController/Source/Rows/NavigationRow.swift

@@ -31,33 +31,64 @@ open class NavigationRow<T: UITableViewCell>: NavigationRowCompatible, Equatable
 
   // MARK: - Initializer
 
-  /// Initializes a `NavigationRow` with a title and a subtitle.
+  #if os(iOS)
+
+  /// Designated initializer on iOS. Returns a `NavigationRow` with a text and a detail text.
+  /// The icon, customization, action and accessory button action closures are optional.
+  public init(
+    text: String,
+    detailText: DetailText,
+    icon: Icon? = nil,
+    customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
+    action: ((Row) -> Void)? = nil,
+    accessoryButtonAction: ((Row) -> Void)? = nil
+  ) {
+    self.text = text
+    self.detailText = detailText
+    self.icon = icon
+    self.customize = customization
+    self.action = action
+    self.accessoryButtonAction = accessoryButtonAction
+  }
+
+  #elseif os(tvOS)
+
+  /// Designated initializer on tvOS. Returns a `NavigationRow` with a text and a detail text.
   /// The icon, customization and action closures are optional.
   public init(
-    title: String,
-    subtitle: Subtitle,
+    text: String,
+    detailText: DetailText,
     icon: Icon? = nil,
     customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
     action: ((Row) -> Void)? = nil
   ) {
-    self.title = title
-    self.subtitle = subtitle
+    self.text = text
+    self.detailText = detailText
     self.icon = icon
     self.customize = customization
     self.action = action
   }
 
+  #endif
+
   // MARK: - Row
 
-  /// The title text of the row.
-  public let title: String
+  /// The text of the row.
+  public let text: String
 
-  /// The subtitle text of the row.
-  public let subtitle: Subtitle?
+  /// The detail text of the row.
+  public let detailText: DetailText?
 
   /// A closure that will be invoked when the row is selected.
   public let action: ((Row) -> Void)?
 
+  #if os(iOS)
+
+  /// A closure that will be invoked when the accessory button is selected.
+  public let accessoryButtonAction: ((Row) -> Void)?
+
+  #endif
+
   // MARK: - RowStyle
 
   /// The type of the table view cell to display the row.
@@ -65,20 +96,30 @@ open class NavigationRow<T: UITableViewCell>: NavigationRowCompatible, Equatable
 
   /// Returns the reuse identifier of the table view cell to display the row.
   public var cellReuseIdentifier: String {
-    return T.reuseIdentifier + (subtitle?.style.stringValue ?? "")
+    return T.reuseIdentifier + (detailText?.style.stringValue ?? "")
   }
 
   /// Returns the table view cell style for the specified subtitle.
-  public var cellStyle: UITableViewCellStyle {
-    return subtitle?.style ?? .default
+  public var cellStyle: UITableViewCell.CellStyle {
+    return detailText?.style ?? .default
   }
 
   /// The icon of the row.
   public let icon: Icon?
 
-  /// Returns `.disclosureIndicator` when action is not nil, otherwise returns `.none`.
-  public var accessoryType: UITableViewCellAccessoryType {
-    return (action == nil) ? .none : .disclosureIndicator
+  /// Returns the accessory type with the disclosure indicator when `action` is not nil,
+  /// and with the detail button when `accessoryButtonAction` is not nil.
+  public var accessoryType: UITableViewCell.AccessoryType {
+    #if os(iOS)
+      switch (action, accessoryButtonAction) {
+      case (nil, nil):      return .none
+      case (.some, nil):    return .disclosureIndicator
+      case (nil, .some):    return .detailButton
+      case (.some, .some):  return .detailDisclosureButton
+      }
+    #elseif os(tvOS)
+      return (action == nil) ? .none : .disclosureIndicator
+    #endif
   }
 
   /// The `NavigationRow` is selectable when action is not nil.
@@ -91,12 +132,26 @@ open class NavigationRow<T: UITableViewCell>: NavigationRowCompatible, Equatable
 
   // MARK: Equatable
 
-  /// Returns true iff `lhs` and `rhs` have equal titles, subtitles and icons.
+  /// Returns true iff `lhs` and `rhs` have equal titles, detail texts and icons.
   public static func == (lhs: NavigationRow, rhs: NavigationRow) -> Bool {
     return
-      lhs.title == rhs.title &&
-      lhs.subtitle == rhs.subtitle &&
+      lhs.text == rhs.text &&
+      lhs.detailText == rhs.detailText &&
       lhs.icon == rhs.icon
   }
 
 }
+
+
+private extension UITableViewCell.CellStyle {
+
+  var stringValue: String {
+    switch self {
+    case .default:  return ".default"
+    case .subtitle: return ".subtitle"
+    case .value1:   return ".value1"
+    case .value2:   return ".value2"
+    }
+  }
+
+}

+ 15 - 12
Pods/QuickTableViewController/Source/Rows/OptionRow.swift

@@ -31,16 +31,18 @@ open class OptionRow<T: UITableViewCell>: OptionRowCompatible, Equatable {
 
   // MARK: - Initializer
 
-  /// Initializes an `OptionRow` with a title, a selection state and an action closure.
-  /// The icon and the customization closure are optional.
+  /// Initializes an `OptionRow` with a text, a selection state and an action closure.
+  /// The detail text, icon, and the customization closure are optional.
   public init(
-    title: String,
+    text: String,
+    detailText: DetailText? = nil,
     isSelected: Bool,
     icon: Icon? = nil,
     customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
     action: ((Row) -> Void)?
   ) {
-    self.title = title
+    self.text = text
+    self.detailText = detailText
     self.isSelected = isSelected
     self.icon = icon
     self.customize = customization
@@ -63,11 +65,11 @@ open class OptionRow<T: UITableViewCell>: OptionRowCompatible, Equatable {
 
   // MARK: - Row
 
-  /// The title text of the row.
-  public let title: String
+  /// The text of the row.
+  public let text: String
 
-  /// Subtitle is disabled in `OptionRow`.
-  public let subtitle: Subtitle? = nil
+  /// The detail text of the row.
+  public let detailText: DetailText?
 
   /// A closure that will be invoked when the `isSelected` is changed.
   public let action: ((Row) -> Void)?
@@ -81,13 +83,13 @@ open class OptionRow<T: UITableViewCell>: OptionRowCompatible, Equatable {
   public let cellReuseIdentifier: String = T.reuseIdentifier
 
   /// The cell style is `.default`.
-  public let cellStyle: UITableViewCellStyle = .default
+  public let cellStyle: UITableViewCell.CellStyle = .default
 
   /// The icon of the row.
   public let icon: Icon?
 
   /// Returns `.checkmark` when the row is selected, otherwise returns `.none`.
-  public var accessoryType: UITableViewCellAccessoryType {
+  public var accessoryType: UITableViewCell.AccessoryType {
     return isSelected ? .checkmark : .none
   }
 
@@ -99,10 +101,11 @@ open class OptionRow<T: UITableViewCell>: OptionRowCompatible, Equatable {
 
   // MARK: - Equatable
 
-  /// Returns true iff `lhs` and `rhs` have equal titles, selection states, and icons.
+  /// Returns true iff `lhs` and `rhs` have equal titles, detail texts, selection states, and icons.
   public static func == (lhs: OptionRow, rhs: OptionRow) -> Bool {
     return
-      lhs.title == rhs.title &&
+      lhs.text == rhs.text &&
+      lhs.detailText == rhs.detailText &&
       lhs.isSelected == rhs.isSelected &&
       lhs.icon == rhs.icon
   }

+ 15 - 12
Pods/QuickTableViewController/Source/Rows/SwitchRow.swift

@@ -32,15 +32,17 @@ open class SwitchRow<T: SwitchCell>: SwitchRowCompatible, Equatable {
   // MARK: - Initializer
 
   /// Initializes a `SwitchRow` with a title, a switch state and an action closure.
-  /// The icon and the customization closure are optional.
+  /// The detail text, icon and the customization closure are optional.
   public init(
-    title: String,
+    text: String,
+    detailText: DetailText? = nil,
     switchValue: Bool,
     icon: Icon? = nil,
     customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
     action: ((Row) -> Void)?
   ) {
-    self.title = title
+    self.text = text
+    self.detailText = detailText
     self.switchValue = switchValue
     self.icon = icon
     self.customize = customization
@@ -63,11 +65,11 @@ open class SwitchRow<T: SwitchCell>: SwitchRowCompatible, Equatable {
 
   // MARK: - Row
 
-  /// The title text of the row.
-  public let title: String
+  /// The text of the row.
+  public let text: String
 
-  /// The subtitle is disabled in `SwitchRow`.
-  public let subtitle: Subtitle? = nil
+  /// The detail text of the row.
+  public let detailText: DetailText?
 
   /// A closure that will be invoked when the `switchValue` is changed.
   public let action: ((Row) -> Void)?
@@ -81,7 +83,7 @@ open class SwitchRow<T: SwitchCell>: SwitchRowCompatible, Equatable {
   public let cellReuseIdentifier: String = T.reuseIdentifier
 
   /// The cell style is `.default`.
-  public let cellStyle: UITableViewCellStyle = .default
+  public let cellStyle: UITableViewCell.CellStyle = .default
 
   /// The icon of the row.
   public let icon: Icon?
@@ -89,7 +91,7 @@ open class SwitchRow<T: SwitchCell>: SwitchRowCompatible, Equatable {
   #if os(iOS)
 
   /// The default accessory type is `.none`.
-  public let accessoryType: UITableViewCellAccessoryType = .none
+  public let accessoryType: UITableViewCell.AccessoryType = .none
 
   /// The `SwitchRow` should not be selectable.
   public let isSelectable: Bool = false
@@ -97,7 +99,7 @@ open class SwitchRow<T: SwitchCell>: SwitchRowCompatible, Equatable {
   #elseif os(tvOS)
 
   /// Returns `.checkmark` when the `switchValue` is on, otherwise returns `.none`.
-  public var accessoryType: UITableViewCellAccessoryType {
+  public var accessoryType: UITableViewCell.AccessoryType {
     return switchValue ? .checkmark : .none
   }
 
@@ -111,10 +113,11 @@ open class SwitchRow<T: SwitchCell>: SwitchRowCompatible, Equatable {
 
   // MARK: - Equatable
 
-  /// Returns true iff `lhs` and `rhs` have equal titles, switch values, and icons.
+  /// Returns true iff `lhs` and `rhs` have equal titles, detail texts, switch values, and icons.
   public static func == (lhs: SwitchRow, rhs: SwitchRow) -> Bool {
     return
-      lhs.title == rhs.title &&
+      lhs.text == rhs.text &&
+      lhs.detailText == rhs.detailText &&
       lhs.switchValue == rhs.switchValue &&
       lhs.icon == rhs.icon
   }

+ 13 - 11
Pods/QuickTableViewController/Source/Rows/TapActionRow.swift

@@ -31,25 +31,25 @@ open class TapActionRow<T: TapActionCell>: TapActionRowCompatible, Equatable {
 
   // MARK: - Initializer
 
-  /// Initializes a `TapActionRow` with a title, an action closure,
+  /// Initializes a `TapActionRow` with a text, an action closure,
   /// and an optional customization closure.
   public init(
-    title: String,
+    text: String,
     customization: ((UITableViewCell, Row & RowStyle) -> Void)? = nil,
     action: ((Row) -> Void)?
   ) {
-    self.title = title
+    self.text = text
     self.customize = customization
     self.action = action
   }
 
   // MARK: - Row
 
-  /// The title text of the row.
-  public let title: String
+  /// The text of the row.
+  public let text: String
 
-  /// The subtitle is disabled in `TapActionRow`.
-  public let subtitle: Subtitle? = nil
+  /// The detail text is disabled in `TapActionRow`.
+  public let detailText: DetailText? = nil
 
   /// A closure that will be invoked when the row is selected.
   public let action: ((Row) -> Void)?
@@ -63,13 +63,13 @@ open class TapActionRow<T: TapActionCell>: TapActionRowCompatible, Equatable {
   public let cellReuseIdentifier: String = T.reuseIdentifier
 
   /// The cell style is `.default`.
-  public let cellStyle: UITableViewCellStyle = .default
+  public let cellStyle: UITableViewCell.CellStyle = .default
 
   /// The default icon is nil.
   public let icon: Icon? = nil
 
   /// The default accessory type is `.none`.
-  public let accessoryType: UITableViewCellAccessoryType = .none
+  public let accessoryType: UITableViewCell.AccessoryType = .none
 
   /// The `TapActionRow` is selectable when action is not nil.
   public var isSelectable: Bool {
@@ -81,9 +81,11 @@ open class TapActionRow<T: TapActionCell>: TapActionRowCompatible, Equatable {
 
   // MARK: - Equatable
 
-  /// Returns true iff `lhs` and `rhs` have equal titles and subtitles.
+  /// Returns true iff `lhs` and `rhs` have equal titles and detail texts.
   public static func == (lhs: TapActionRow, rhs: TapActionRow) -> Bool {
-    return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
+    return
+      lhs.text == rhs.text &&
+      lhs.detailText == rhs.detailText
   }
 
 }

+ 1 - 1
Pods/QuickTableViewController/Source/Views/SwitchCell.swift

@@ -63,7 +63,7 @@ open class SwitchCell: UITableViewCell, Configurable {
 
    - returns: An initialized `SwitchCell` object.
    */
-  public override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
+  public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
     super.init(style: style, reuseIdentifier: reuseIdentifier)
     setUpAppearance()
   }

+ 1 - 1
Pods/QuickTableViewController/Source/Views/TapActionCell.swift

@@ -42,7 +42,7 @@ open class TapActionCell: UITableViewCell {
 
    - returns: An initialized `TapActionCell` object.
    */
-  public override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
+  public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
     super.init(style: .default, reuseIdentifier: reuseIdentifier)
     setUpAppearance()
   }

BIN
Pods/SwiftFormat/CommandLineTool/swiftformat


+ 21 - 0
Pods/SwiftFormat/LICENSE.md

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2016 Nick Lockwood
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 1457 - 0
Pods/SwiftFormat/README.md

@@ -0,0 +1,1457 @@
+[![Travis](https://img.shields.io/travis/nicklockwood/SwiftFormat.svg)](https://travis-ci.org/nicklockwood/SwiftFormat)
+[![Coveralls](https://coveralls.io/repos/github/nicklockwood/SwiftFormat/badge.svg)](https://coveralls.io/github/nicklockwood/SwiftFormat)
+[![Swift 3.4](https://img.shields.io/badge/swift-3.4-orange.svg?style=flat)](https://developer.apple.com/swift)
+[![Swift 4.2](https://img.shields.io/badge/swift-4.2-red.svg?style=flat)](https://developer.apple.com/swift)
+[![License](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://opensource.org/licenses/MIT)
+[![Twitter](https://img.shields.io/badge/twitter-@nicklockwood-blue.svg)](http://twitter.com/nicklockwood)
+
+Table of Contents
+-----------------
+
+- [What?](#what-is-this)
+- [Why?](#why-would-i-want-to-do-that)
+- [How?](#how-do-i-install-it)
+    - [Command-line tool](#command-line-tool)
+    - [Xcode source editor extension](#xcode-source-editor-extension)
+    - [Xcode build phase](#xcode-build-phase)
+    - [VSCode plugin](#vscode-plugin)
+    - [Git pre-commit hook](#git-pre-commit-hook)
+    - [On CI using Danger](#on-ci-using-danger)
+- [Usage](#so-what-does-swiftformat-actually-do)
+    - [Options](#options)
+    - [Rules](#rules)
+    - [Config](#config)
+    - [Globs](#globs)
+    - [Linting](#linting)
+- [FAQ](#faq)
+- [Cache](#cache)
+- [File headers](#file-headers)
+- [Known issues](#known-issues)
+- [Credits](#credits)
+
+
+What is this?
+----------------
+
+SwiftFormat is a code library and command-line tool for reformatting swift code.
+
+It applies a set of rules to the formatting and space around the code, leaving the behavior intact.
+
+
+Why would I want to do that?
+-----------------------------
+
+Many programmers have a preferred style for formatting their code, and others seem entirely blind to the existing formatting conventions of a project (to the enragement of their colleagues).
+
+When collaborating on a project, it can be helpful to agree on a common coding style, but enforcing that manually is tedious and error-prone, and can lead to bad feeling if some participants take it more seriously than others.
+
+Having a tool to automatically enforce a common style eliminates those issues, and lets you focus on the *operation* of the code, not its presentation.
+
+
+How do I install it?
+---------------------
+
+That depends. There are four ways you can use SwiftFormat:
+
+1. As a command-line tool that you run manually, or as part of some other toolchain
+2. As a Source Editor Extension that you can invoke via the Editor > SwiftFormat menu within Xcode
+3. As a build phase in your Xcode project, so that it runs every time you press Cmd-R or Cmd-B, or
+4. As a Git pre-commit hook, so that it runs on any files you've changed before you check them in
+
+
+Command-line tool
+-------------------
+
+**Installation:**
+
+You can install the `swiftformat` command-line tool using [Homebrew](http://brew.sh/). Assuming you already have Homebrew installed, just type:
+
+```bash
+> brew update
+> brew install swiftformat
+```
+
+Alternatively, you can install the tool using [Mint](https://github.com/yonaskolb/Mint) as follows:
+
+```bash
+> mint install nicklockwood/SwiftFormat
+```
+    
+And then run it using:
+
+```bash
+> mint run swiftformat
+```
+
+If you are installing SwiftFormat into your project directory, you can use [CocoaPods](https://cocoapods.org/) to automatically install the swiftformat binary along with your other pods - see the Xcode build phase instructions below for details.
+
+If you would prefer not to use a package manager, you can build the command-line app manually:
+
+1. open `SwiftFormat.xcodeproj` and build the `SwiftFormat (Application)` scheme.
+
+2. Drag the `swiftformat` binary into `/usr/local/bin/` (this is a hidden folder, but you can use the Finder's `Go > Go to Folder...` menu to open it).
+
+3. Open `~/.bash_profile` in your favorite text editor (this is a hidden file, but you can type `open ~/.bash_profile` in the terminal to open it).
+
+4. Add the following line to the file: `alias swiftformat="/usr/local/bin/swiftformat --indent 4"` (you can omit the `--indent 4`, or replace it with something else. Run `swiftformat --help` to see the available options).
+
+5. Save the `.bash_profile` file and run the command `source ~/.bash_profile` for the changes to take effect.
+
+**Usage:**
+
+If you followed the installation instructions above, you can now just type
+
+```bash
+> swiftformat .
+```
+    
+(that's a space and then a period after the command) in the terminal to format any Swift files in the current directory.
+
+**WARNING:** `swiftformat .` will overwrite any Swift files it finds in the current directory, and any subfolders therein. If you run it from your home directory, it will probably reformat every Swift file on your hard drive.
+
+To use it safely, do the following:
+
+1. Choose a file or directory that you want to apply the changes to.
+
+2. Make sure that you have committed all your changes to that code safely in git (or whatever source control system you use).
+
+3. (Optional) In Terminal, type `swiftformat --inferoptions "/path/to/your/code/"`. This will suggest a set of formatting options to use that match your existing project style (but you are free to ignore these and use the defaults, or your own settings if you prefer).
+
+    The path can point to either a single Swift file or a directory of files. It can be either be absolute, or relative to the current directory. The `""` quotes around the path are optional, but if the path contains spaces then you either need to use quotes, or escape each space with `\`. You may include multiple paths separated by spaces.
+
+4. In Terminal, type `swiftformat "/path/to/your/code/"`. The same rules apply as above with respect to paths, and multiple space-delimited paths are allowed.
+
+    If you used `--inferoptions` to generate a suggested set of options in step 3, you should copy and paste them into the command, either before or after the path(s) to your source files.
+    
+    If you have created a [configuration file](#config), you can specify its path using `--config "/path/to/your/config-file/".
+
+5. Press enter to begin formatting. Once the formatting is complete, use your source control system to check the changes, and verify that no undesirable changes have been introduced. If they have, revert the changes, tweak the options and try again.
+
+6. (Optional) commit the changes.
+
+Following these instructions *should* ensure that you avoid catastrophic data loss, but in the unlikely event that it wipes your hard drive, **please note that I accept no responsibility**.
+
+If you prefer, you can also use unix pipes to include swiftformat as part of a command chain. For example, this is an alternative way to format a file:
+
+```bash
+> cat /path/to/file.swift | swiftformat --output /path/to/file.swift
+```
+    
+Omitting the `--output /path/to/file.swift` will print the formatted file to `stdout`.
+
+
+Xcode source editor extension
+-----------------------------
+
+**Installation:**
+
+You'll find the latest version of the SwiftFormat for Xcode application inside the EditorExtension folder included in the SwiftFormat repository. Drag it into your `Applications` folder, then double-click to launch it, and follow the on-screen instructions.
+
+**NOTE:** The Extension requires Xcode 8 and macOS 10.12 Sierra or higher.
+
+**Usage:**
+
+In Xcode, you'll find a SwiftFormat option under the Editor menu. You can use this to format either the current selection or the whole file.
+
+You can configure the formatting [rules](#rules) and [options](#options) used by the Xcode source editor extension using the host application. There is currently no way to override these per-project, however you can import and export different configurations using the File menu. You will need to do this again each time you switch project.
+
+The format of the configuration file is described in the [Config section](#config) below.
+
+**Note:** SwiftFormat for Xcode cannot automatically detect changes to an imported configuration file. If you update the `.swiftformat` file for your project, you will need to manually re-import that file into SwiftFormat for Xcode in order for the Xcode source editor extension to use the new configuration.
+
+
+Xcode build phase
+-------------------
+
+To set up SwiftFormat as an Xcode build phase, do the following:
+
+1. Add the `swiftformat` binary to your project directory (this is better than referencing a locally installed copy because it means that project will still compile on machines that don't have the `swiftformat` command-line tool installed). You can install the binary manually, or via [CocoaPods](https://cocoapods.org/), by adding the following line to your Podfile then running `pod install`:
+
+    ```ruby
+    pod 'SwiftFormat/CLI'
+    ```
+
+    **NOTE:** This will only install the pre-built command-line app, not the source code for the SwiftFormat framework.
+
+2. In the Build Phases section of your project target, add a new Run Script phase before the Compile Sources step. The script should be
+
+    ```bash
+    "${SRCROOT}/path/to/swiftformat" "${SRCROOT}/path/to/your/swift/code/"
+    ```
+        
+	Both paths should be relative to the directory containing your Xcode project. If you are installing SwiftFormat as a Cocoapod, the swiftformat path will be
+
+    ```bash
+    "${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat"
+    ```
+	    
+    **NOTE:** Adding this script will slightly increase your build time, and will make changes to your source files as you work on them, which can have annoying side-effects such as clearing the undo buffer. You may wish to add the script to your test target rather than your main target, so that it is invoked only when you run the unit tests, and not every time you build the app.
+
+Alternatively, you can skip installation of the SwiftFormat pod and configure Xcode to use the locally installed swiftformat command-line tool instead by putting the following in your Run Script build phase:
+
+```bash
+if which swiftformat >/dev/null; then
+  swiftformat .
+else
+  echo "warning: SwiftFormat not installed, download from https://github.com/nicklockwood/SwiftFormat"
+fi
+```
+
+This is not recommended for shared projects however, as different team members using different versions of SwiftFormat may result in noise in the commits as code gets reformatted inconsistently.
+
+
+VSCode plugin
+--------------
+
+If you prefer to use Microsoft's [VSCode](https://code.visualstudio.com) editor for writing Swift, [Valentin Knabel](https://github.com/vknabel) has created a [VSCode plugin](https://marketplace.visualstudio.com/items?itemName=vknabel.vscode-swiftformat) for SwiftFormat.
+
+
+Git pre-commit hook
+---------------------
+
+1. Follow the instructions for installing the swiftformat command-line tool.
+
+2. Edit or create a `.git/hooks/pre-commit` file in your project folder. The .git folder is hidden but should already exist if you are using Git with your project, so open in with the terminal, or the Finder's `Go > Go to Folder...` menu.
+
+3. Add the following line in the pre-commit file (unlike the Xcode build phase approach, this uses your locally installed version of SwiftFormat, not a separate copy in your project repository)
+
+    ```bash
+    #!/bin/bash
+    git diff --diff-filter=d --staged --name-only | grep -e '\(.*\).swift$' | while read line; do
+      swiftformat "${line}";
+      git add "$line";
+    done
+    ```
+
+4. enable the hook by typing `chmod +x .git/hooks/pre-commit` in the terminal
+ 
+The pre-commit hook will now run whenever you run `git commit`. Running `git commit --no-verify` will skip the pre-commit hook.
+
+**NOTE:** If you are using Git via a GUI client such as [Tower](https://www.git-tower.com), [additional steps](https://www.git-tower.com/help/mac/faq-and-tips/faq/hook-scripts) may be needed.
+
+**NOTE (2):** Unlike the Xcode build phase approach, git pre-commit hook won't be checked in to source control, and there's no way to guarantee that all users of the project are using the same version of SwiftFormat. For a collaborative project, you might want to consider a *post*-commit hook instead, which would run on your continuous integration server.
+
+On CI using Danger
+-------------------
+
+To setup SwiftFormat to be used by your continuous integration system using [Danger](http://danger.systems/ruby/), do the following:
+
+1. Follow the [`instructions`](http://danger.systems/guides/getting_started.html) to setup Danger.
+2. Add the [`danger-swiftformat`](https://github.com/garriguv/danger-ruby-swiftformat) plugin to your `Gemfile`.
+3. Add the following to your `Dangerfile`:
+
+    ```ruby
+    swiftformat.binary_path = "/path/to/swiftformat" # optional
+    swiftformat.additional_args = "--indent tab --self insert" # optional
+    swiftformat.check_format(fail_on_error: true)
+    ```
+    
+    **NOTE:** It is recommended to add the `swiftformat` binary to your project directory to ensure the same version is used each time (see the [Xcode build phase](#xcode-build-phase) instructions above).
+
+So what does SwiftFormat actually do?
+--------------------------------------
+
+SwiftFormat first converts the source file into tokens, then iteratively applies a set of rules to the tokens to adjust the formatting. The tokens are then converted back into text.
+
+SwiftFormat's configuration is split between **rules** and **options**. Rules are functions in the SwiftFormat library that apply changes to the code. Options are settings that control the behavior of the rules. 
+
+
+Options
+-------
+
+The options available in SwiftFormat can be displayed using the `--help` command-line argument. The default value for each option is indicated in the help text.
+
+Rules are configured by adding `--[rulename] [value]` to your command-line arguments, or by creating a [config file](#config) as explained below.
+
+A given option may affect multiple rules. See the [Rules](#rules) section below for details about which options affect which rule.
+
+
+Rules
+-----
+
+The rules used by SwiftFormat can be displayed using the `--rules` command-line argument. Rules can be either enabled or disabled. Most are enabled by default. Disabled rules are marked with "(disabled)" when using the `--rules` argument.
+
+You can disable rules individually using `--disable` followed by a list of one or more comma-delimited rule names, or enable additional rules using `--enable` followed by the names. 
+
+To see exactly which rules were applied to a given file, you can use the `--verbose` command-line option to force SwiftFormat to print a more detailed log as it applies the formatting. **NOTE:** running in verbose mode is slower than the default mode.
+
+You can also enable/disable rules for specific files or code ranges by using `swiftformat:` directives in comments inside your Swift files. To temporarily disable one or more rules inside a source file, use:
+
+```swift
+// swiftformat:disable <rule1> [<rule2> [rule<3> ...]]
+```
+
+To enable the rule(s) again, use:
+
+```swift
+// swiftformat:enable <rule1> [<rule2> [rule<3> ...]]
+```
+
+To disable all rules use:
+
+```swift
+// swiftformat:disable all
+```
+
+And to enable them all again, use:
+
+```swift
+// swiftformat:enable all
+```
+
+To temporarily prevent one or more rules being applied to just the next line, use:
+
+```swift
+// swiftformat:disable:next <rule1> [<rule2> [rule<3> ...]]
+let foo = bar // rule(s) will be disabled for this line
+let bar = baz // rule(s) will be re-enabled for this line
+```
+
+There is no need to manually re-enable a rule after using the `next` directive.
+
+**Note:** The `swiftformat:enable` directives only serves to counter a previous `swiftformat:disable` directive in the same file. It is not possible to use `swiftformat:enable` to enable a rule that was not already enabled when formatting started.
+
+Here are all the rules that SwiftFormat currently applies, and the effects that they have:
+
+***blankLinesAtEndOfScope*** - removes trailing blank lines from inside braces, brackets, parens or chevrons:
+
+```diff
+  func foo() {
+    // foo
+- 
+  }
+
+  func foo() {
+    // foo
+  }
+```
+
+```diff
+  array = [
+    foo,
+    bar,
+    baz,
+- 
+  ]
+
+  array = [
+    foo,
+    bar,
+    baz,
+  ]
+```
+
+***blankLinesAtStartOfScope*** - removes leading blank lines from inside braces, brackets, parens or chevrons:
+
+```diff
+  func foo() {
+-
+    // foo 
+  }
+
+  func foo() {
+    // foo
+  }
+```
+
+```diff
+  array = [
+-
+    foo,
+    bar,
+    baz, 
+  ]
+
+  array = [
+    foo,
+    bar,
+    baz,
+  ]
+```
+
+***blankLinesBetweenScopes*** - adds a blank line before each class, struct, enum, extension, protocol or function:
+
+```diff
+  func foo() {
+    // foo
+  }
+  func bar() {
+    // bar
+  }
+  var baz: Bool
+  var quux: Int
+
+  func foo() {
+    // foo
+  }
++ 
+  func bar() {
+    // bar
+  }
++ 
+  var baz: Bool
+  var quux: Int
+```
+
+***blankLinesAroundMark*** - adds a blank line before and after each `MARK:` comment:
+
+```diff
+  func foo() {
+    // foo
+  }
+  // MARK: bar
+  func bar() {
+    // bar
+  }
+  
+  func foo() {
+    // foo
+  }
++
+  // MARK: bar
++
+  func bar() {
+    // bar
+  }
+```
+                         
+***braces*** - implements K&R (default, `--allman false`) or Allman-style indentation (`--allman true`):
+
+```diff
+- if x
+- {
+    // foo
+  }
+- else
+- {
+    // bar
+  }
+
++ if x {
+    // foo
+  }
++ else {
+    // bar
+  }
+```
+                         
+***consecutiveBlankLines*** - reduces multiple sequential blank lines to a single blank line:
+
+```diff
+  func foo() {
+    let x = "bar"
+- 
+ 
+    print(x)
+  }
+
+  func foo() {
+    let x = "bar"
+ 
+    print(x)
+  }
+```
+
+***consecutiveSpaces*** - reduces a sequence of spaces to a single space:
+
+```diff
+- let     foo = 5
++ let foo = 5
+```
+
+***duplicateImports*** - removes duplicate import statements:
+
+```diff
+  import Foo
+  import Bar
+- import Foo
+```
+
+```diff
+  import B
+  #if os(iOS)
+    import A
+-   import B
+  #endif
+```
+
+***elseOnSameLine*** - controls whether an `else`, `catch` or `while` keyword after a `}` appears on the same line, depending on the `--elseposition` option (`same-line` (default) or `next-line`):
+
+```diff
+  if x {
+    // foo
+- }
+- else {
+    // bar
+  }
+
+  if x {
+    // foo
++ } else {
+    // bar
+  }
+```
+
+```diff
+  do {
+    // try foo
+- }
+- catch {
+    // bar
+  }
+
+  do {
+    // try foo
++ } catch {
+    // bar
+  }
+```
+
+```diff
+  repeat {
+    // foo
+- }
+- while {
+    // bar
+  }
+
+  repeat {
+    // foo
++ } while {
+    // bar
+  }
+```
+
+***emptyBraces*** - removes all white space between otherwise empty braces:
+
+```diff
+- func foo() {
+- 
+- }
+
+
++ func foo() {}
+```
+   
+***fileHeader*** - allows the replacement or removal of Xcode's automated comment header blocks. By default, no action is taken, but passing one of the following arguments to the command-line will activate its function.
+
+- `--header strip`: removes all automated comment header blocks
+- `--header "Copyright Text {year}"`: replaces all automated comment header blocks with the text specified
+
+    See the File Headers section below for more information.
+
+***hoistPatternLet*** - moves `let` or `var` bindings inside patterns to the start of the expression, or vice-versa, depending on the `--patternlet` option (`hoist` or `inline`).
+
+```diff
+- (let foo, let bar) = baz()
++ let (foo, bar) = baz()
+```
+
+```diff
+- if case .foo(let bar, let baz) = quux {
+    // inner foo
+  }
+
++ if case let .foo(bar, baz) = quux {
+    // inner foo
+  }
+```
+
+***indent*** - adjusts leading whitespace based on scope and line wrapping. Uses either tabs (`--indent tab`) or spaces (`--indent 4`). By default, `case` statements will be indented level with their containing `switch`, but this can be controlled with the `--indentcase` options. Also affects comments and `#if ...` statements, depending on the configuration of the `--comments` option (`indent` or `ignore`) and the `--ifdef` option (`indent` (default), `noindent` or `ignore`):
+
+```diff
+  if x {
+-     // foo
+  } else {
+-     // bar
+-       }
+
+  if x {
++   // foo
+  } else {
++   // bar
++ }
+```
+
+```diff
+  let array = [
+    foo,
+-     bar,
+-       baz
+-   ]
+
+  let array = [
+    foo,
++   bar,
++   baz
++ ]
+```
+
+```diff
+  switch foo {
+-   case bar: break
+-   case baz: break
+  }
+
+  switch foo {
++ case bar: break
++ case baz: break
+  }
+```
+
+***isEmpty*** - replaces `count == 0` checks with `isEmpty`, which is preferred for performance reasons (disabled by default because SwiftFormat is not able to check that the isEmpty property is actually available in all cases):
+
+```diff
+- if foo.count == 0 {
++ if foo.isEmpty {
+
+- if foo.count > 0 {
++ if !foo.isEmpty {
+
+- if foo?.count == 0 {
++ if foo?.isEmpty == true {
+```
+
+***linebreakAtEndOfFile*** - ensures that the last line of the file is empty:
+       
+***linebreaks*** - normalizes all linebreaks to use the same character, depending on the `--linebreaks` option (`cr`, `crlf` or `lf`).
+
+***numberFormatting*** - handles case and grouping of number literals, depending on the following options:
+- `--hexliteralcase` and `--exponentcase` (`uppercase` (default) or `lowercase`)
+- `--hexgrouping`, `--binarygrouping`, `--octalgrouping` (`4,8` (default grouping,threshold), `none` or `ignore`) 
+- `--decimalgrouping` (`3,6` (default grouping,threshold), `none` or `ignore`)
+- `--fractiongrouping` and `--exponentgrouping` (`disabled` (default) or `enabled`)
+
+```diff
+- let color = 0xFF77A5
++ let color = 0xff77a5
+```
+
+```diff
+- let big = 123456.123
++ let big = 123_456.123
+```
+
+***ranges*** - controls the spacing around range operators. By default, a space is added, but this can be configured using the `--ranges` option (`spaced` (default) or `nospace`).
+
+***redundantBackticks*** - removes unnecessary escaping of identifiers using backticks, e.g. in cases where the escaped word is not a keyword, or is not ambiguous in that context:
+
+```diff
+- let `infix` = bar
++ let infix = bar
+```
+
+```diff
+- func foo(with `default`: Int) {}
++ func foo(with default: Int) {}
+```
+
+***redundantGet*** - removes unnecessary `get { }` clauses from inside read-only computed properties:
+
+```diff
+  var foo: Int {
+-   get {
+-     return 5
+-   }
+  }
+
+  var foo: Int {
++   return 5
+  }
+```
+
+***redundantLet*** - removes redundant `let` or `var` from ignored variables in bindings (which is a warning in Xcode):
+
+```diff
+- let _ = resultIgnorableFunction()
++ _ = resultIgnorableFunction()
+```
+
+```diff
+- if case (let foo, let _) = bar {}
++ if case (let foo, _) = bar {}
+```
+
+```diff
+- if case .foo(var /* unused */ _) = bar {}
++ if case .foo( /* unused */ _) = bar {}
+```
+
+***redundantLetError*** - removes redundant `let error` from `catch` statements, where it is declared implicitly:
+
+```diff
+- do { ... } catch let error { log(error) }
++ do { ... } catch { log(error) }
+```
+
+***redundantNilInit*** - removes unnecessary nil initialization of Optional vars (which are nil by default anyway):
+
+```diff
+- var foo: Int? = nil
++ var foo: Int?
+```
+
+```diff
+// doesn't apply to `let` properties
+let foo: Int? = nil
+```
+
+```diff
+// doesn't affect non-nil initialization
+var foo: Int? = 0
+```
+
+***redundantParens*** - removes unnecessary parens from expressions and branch conditions:
+
+```diff
+- if (foo == true) {} 
++ if foo == true {}
+```
+
+```diff
+- while (i < bar.count) {}
++ while i < bar.count {}
+```
+
+```diff
+- queue.async() { ... }
++ queue.async { ... }
+```
+
+```diff
+- let foo: Int = ({ ... })()
++ let foo: Int = { ... }()
+```
+    
+***redundantPattern*** - removes redundant pattern matching arguments for ignored variables:
+
+```diff
+- if case .foo(_, _) = bar {}
++ if case .foo = bar {}
+```
+
+```diff
+- let (_, _) = bar
++ let _ = bar
+```
+    
+***redundantRawValues*** - removes raw string values from enum cases when they match the case name:
+
+```diff
+  enum Foo: String {
+-   case bar = "bar"
+    case baz = "quux"
+  }
+
+  enum Foo: String {
++   case bar
+    case baz = "quux"
+  }
+```
+
+***redundantReturn*** - removes unnecessary `return` keyword from single-line closures:
+
+```diff
+- array.filter { return $0.foo == bar }
++ array.filter { $0.foo == bar }
+```
+    
+***redundantSelf*** - removes or inserts `self` prefix from class and instance member references, depending on the `--self` option:
+
+```diff
+  func foobar(foo: Int, bar: Int) {
+    self.foo = foo
+    self.bar = bar
+-   self.baz = 42
+  }
+
+  func foobar(foo: Int, bar: Int) {
+    self.foo = foo
+    self.bar = bar
++   baz = 42
+  }  
+```
+
+There is also an option to always use explicit `self` but *only* inside `init`, by using `--self init-only`:
+
+```diff
+  init(foo: Int, bar: Int) {
+    self.foo = foo
+    self.bar = bar
+-   baz = 42
+  }
+
+  init(foo: Int, bar: Int) {
+    self.foo = foo
+    self.bar = bar
++   self.baz = 42
+  }  
+```
+    
+***redundantVoidReturnType*** - removes unnecessary `Void` return type from function declarations:
+
+```diff
+- func foo() -> Void {
+    // returns nothing
+  }
+
++ func foo() {
+    // returns nothing
+  }
+```
+
+***redundantInit*** - removes unnecessary `init` when instantiating Types:
+
+```diff
+- String.init("text")
++ String("text")
+```
+    
+***semicolons*** - removes semicolons at the end of lines.  Also replaces inline semicolons with a linebreak, depending on the `--semicolons` option (`inline` (default) or `never`).
+
+```diff
+- let foo = 5;
++ let foo = 5
+```
+
+```diff
+- let foo = 5; let bar = 6
++ let foo = 5
++ let bar = 6
+```
+
+```diff
+// semicolon is not removed if it would affect the behavior of the code
+return;
+goto(fail)
+```
+
+***sortedImports*** - rearranges import statements so that they are sorted. Use the `--importgrouping` option to configure how to group `@testable import`s (alphabetically (default), testable-top or testable-bottom).
+
+```diff
+- import Foo
+- import Bar
++ import Bar
++ import Foo
+```
+
+```diff
+- import B
+- import A
+- #if os(iOS)
+-   import Foo-iOS
+-   import Bar-iOS
+- #endif
++ import A
++ import B
++ #if os(iOS)
++   import Bar-iOS
++   import Foo-iOS
++ #endif
+```
+
+***spaceAroundBraces*** - contextually adds or removes space around `{ ... }`:
+
+```diff
+- foo.filter{ return true }.map{ $0 }
++ foo.filter { return true }.map { $0 }
+```
+
+```diff
+- foo( {} )
++ foo({})
+```
+
+***spaceAroundBrackets*** - contextually adjusts the space around `[ ... ]`:
+
+```diff
+- foo as[String]
++ foo as [String]
+```
+
+```diff
+- foo = bar [5]
++ foo = bar[5]
+```
+
+***spaceAroundComments*** - adds space around `/* ... */` comments and before `//` comments, depending on the `--comments` option (`indent` (default) or `ignore`).
+
+```diff
+- let a = 5// assignment
++ let a = 5 // assignment
+```
+
+```diff
+- func foo() {/* no-op */}
++ func foo() { /* no-op */ }
+```
+
+***spaceAroundGenerics*** - removes the space around `< ... >`:
+
+```diff
+- Foo <Bar> ()
++ Foo<Bar>()
+```
+
+***spaceAroundOperators*** - contextually adjusts the space around infix operators. Also adds or removes the space between an operator function declaration and its arguments, depending on the `--operatorfunc` option (`spaced` (default) or `nospace`).
+
+```diff
+- foo . bar()
++ foo.bar()
+```
+
+```diff
+- a+b+c
++ a + b + c
+```
+
+```diff
+- func ==(lhs: Int, rhs: Int) -> Bool
++ func == (lhs: Int, rhs: Int) -> Bool
+```
+
+***spaceAroundParens*** - contextually adjusts the space around `( ... )`:
+
+```diff
+- init (foo)
++ init(foo)
+```
+
+```diff
+- switch(x){
++ switch (x) {
+```
+
+***spaceInsideBraces*** - adds space inside `{ ... }`:
+
+```diff
+- foo.filter {return true}
++ foo.filter { return true }
+```
+
+***spaceInsideBrackets*** - removes the space inside `[ ... ]`:
+
+```diff
+- [ 1, 2, 3 ]
++ [1, 2, 3]
+```
+
+***spaceInsideComments*** - adds a space inside `/* ... */` comments and at the start of `//` comments, depending on the `--comments` option (`indent` (default) or `ignore`).
+
+```diff
+- let a = 5 //assignment
++ let a = 5 // assignment
+```
+
+```diff
+- func foo() { /*no-op*/ }
++ func foo() { /* no-op */ }
+```
+
+***spaceInsideGenerics*** - removes the space inside `< ... >`:
+
+```diff
+- Foo< Bar, Baz >
++ Foo<Bar, Baz>
+```
+
+***spaceInsideParens*** - removes the space inside `( ... )`:
+
+```diff
+- ( a, b )
++ (a, b)
+```
+
+***specifiers*** - normalizes the order for access specifiers, and other property/function/class/etc. specifiers:
+
+```diff
+- lazy public weak private(set) var foo: UIView?
++ private(set) public lazy weak var foo: UIView?
+```
+
+```diff
+- public override final func foo()
++ final override public func foo()
+```
+
+```diff
+- convenience private init() 
++ private convenience init()
+```
+
+***strongOutlets*** - removes the `weak` specifier from `@IBOutlet` properties, as per [Apple's recommendation](https://developer.apple.com/videos/play/wwdc2015/407/):
+
+```diff
+- @IBOutlet weak var label: UILabel!
++ @IBOutlet var label: UILabel!
+```
+
+***trailingClosures*** - converts the last closure argument in a function call to trailing closure syntax where possible (disabled by default because it can introduce ambiguity that prevents code from compiling):
+
+```diff
+- DispatchQueue.main.async(execute: {
+    // do stuff
+- })
+
++ DispatchQueue.main.async {
+    // do stuff
++ }
+```
+
+**NOTE:** Occasionally, using trailing closure syntax makes a function call ambiguous, and the compiler can't understand it. Since SwiftFormat isn't able to detect this in all cases, the `trailingClosures` rule is disabled by default, and must be manually enabled via the `--enable trailingClosures` option.
+
+***trailingCommas*** - adds or removes trailing commas from the last item in an array or dictionary literal, depending on the `--commas` option (`always` (default) or `inline`).
+
+```diff
+  let array = [
+    foo,
+    bar,
+-   baz
+  ]
+
+  let array = [
+    foo,
+    bar,
++   baz,
+  ]
+```
+
+***trailingSpace*** - removes the whitespace at the end of a line, depending on the `--trimwhitespace` option (`always` (default) or `nonblank-lines`).
+ 
+***todos*** - ensures that `TODO:`, `MARK:` and `FIXME:` comments include the trailing colon (else they're ignored by Xcode):
+
+```diff
+- /* TODO fix this properly */
++ /* TODO: fix this properly */
+```
+
+```diff
+- // MARK - UIScrollViewDelegate
++ // MARK: - UIScrollViewDelegate
+```
+
+***unusedArguments*** - marks unused arguments in functions and closures with `_` to make it clear they aren't used. Use the `--stripunusedargs` option to configure which argument types are affected (`always` (default), `closure-only` or `unnamed-only`).
+
+```diff
+- func foo(bar: Int, baz: String) {
+    print("Hello \(baz)")
+  }
+
++ func foo(bar _: Int, baz: String) {
+    print("Hello \(baz)")
+  }
+```
+
+```diff
+- func foo(_ bar: Int) {
+    // no-op
+  }
+
++ func foo(_: Int) {
+    // no-op
+  }
+```
+
+```diff
+- request { response, data in
+    self.data += data
+  }
+
++ request { _, data in
+    self.data += data
+  }
+```
+    
+***void*** - standardizes the use of `Void` vs an empty tuple `()` to represent empty argument lists and return values, depending on the `--empty` option (`void` (default) or `tuple`).
+
+```diff
+- let foo: () -> (
++ let foo: () -> Void
+```
+
+```diff
+- let bar: Void -> Void
++ let bar: () -> Void
+```
+
+```diff
+- let baz: (Void) -> Void
++ let baz: () -> Void
+```
+
+```diff
+- func quux() -> (Void)
++ func quux() -> Void
+```
+
+***wrapArguments*** - wraps function arguments and collection literals depending on the `--wraparguments` and `--wrapcollections` options (`beforefirst`, `afterfirst` or `preserve`) and the `--closingparen` option (`balanced` (default) or `same-line`). E.g. for `--wraparguments beforefirst` and `--closingparen balanced`:
+
+```diff
+- func foo(bar: Int,
+-          baz: String) {
+    // foo function
+  }
+
++ func foo(
++   bar: Int,
++   baz: String
++ ) {
+    // foo function
+  }
+```
+
+Or for `--wrapcollections beforefirst`:
+
+```diff
+- let foo = [bar,
+             baz,
+-            quuz]
+
++ let foo = [
++   bar,
+    baz,
++   quuz
+]
+```
+
+***andOperator*** - replaces the `&&` operator with `,` inside `if`, `guard` and `while` conditions:
+
+```diff
+- if true && true {
++ if true, true {
+```
+
+```diff
+- guard true && true else {
++ guard true, true else {
+```
+
+```diff
+- if functionReturnsBool() && true {
++ if functionReturnsBool(), true {
+```
+
+```diff
+- if functionReturnsBool() && variable {
++ if functionReturnsBool(), variable {
+```
+
+
+Config
+------
+
+Although it is possible to configure SwiftFormat directly by using the command-line [options](#options) and [rules](#rules) detailed above, it is sometimes more convenient to create a configuration file, which can be added to your project and shared with other developers.
+
+A SwiftFormat configuration file consists of one or more command-line options, split onto separate lines, e.g:
+
+```
+--allman true
+--indent tabs
+--disable elseOnSameLine,semicolons
+```
+
+While formatting, SwiftFormat will automatically check inside each subdirectory for the presence of a ".swiftformat" file and will apply any options that it finds there to the files in that directory.
+
+This allows you to override certain rules or formatting options just for a particular directory of files. You can also specify excluded files relative to that directory using `--exclude`, which may be more convenient than specifying them at the top-level:
+
+```
+--exclude Pods,Generated
+```
+
+The `--exclude` option takes a comma-delimited list of file or directory paths to exclude from formatting. Excluded paths are relative to the config file containing the `--exclude` command. The excluded paths can include wildcards, specified using Unix "Glob" syntax, as [documented below](#globs).
+
+Config files named ".swiftformat" will be processed automatically, however you can select an additional configuration file to use for formatting using the `--config "path/to/config/file"` command-line argument. A configuration file selected using `--config` does not need to be named ".swiftformat", and can be located outside of the project directory.
+
+The config file format is designed to be human editable. You may include blank lines for readability, and can also add comments using a hash prefix (#), e.g.
+
+```
+# format options
+--allman true
+--indent tabs # tabs FTW!
+
+# file options
+--exclude Pods
+
+# rules
+--disable elseOnSameLine,semicolons
+```
+
+If you would prefer not to edit the configuration file by hand, you can use the [SwiftFormat for Xcode](#xcode-source-editor-extension) app to edit the configuration and export a configuration file. Alternatively, you can use the swiftformat command-line-tool's `--inferoptions` command to generate a config file from your existing project, like this:
+
+```bash
+> cd /path/to/project
+> swiftformat --inferoptions . --output .swiftformat
+```
+
+Globs
+-----
+
+When excluding files from formatting using the `--exclude` option, you may wish to make use of wildcard paths (aka "Globs") to match all files that match a particular naming convention without having to manually list them all.
+
+SwiftFormat's glob syntax is based on Ruby's implementation, which varies slightly from the Unix standard. The following patterns are supported:
+
+* `*` - A single star matches zero or more characters in a filename, but *not* a `/`.
+
+* `**` - A double star will match anything, including one or more `/`.
+
+* `?` - A question mark will match any single character except `/`.
+
+* `[abc]` - Matches any single character inside the brackets.
+
+* `[a-z]` - Matches a single character in the specified range in the brackets.
+
+* `{foo,bar}` - Matches any one of the comma-delimited strings inside the braces.
+    
+Examples:
+
+* `foo.swift` - Matches the file "foo.swift" in the same directory as the config file.
+
+* `*.swift` - Matches any swift file in the same directory as the config file.
+
+* `foo/bar.swift` - Matches the file "bar.swift" in the directory "foo".
+
+* `**/foo.swift` - Matches any file named "foo.swift" in the project.
+
+* `**/*.swift` - Matches any swift file in the project.
+
+* `**/Generated` - Matches any folder called `Generated` in the project.
+
+* `**/*_generated.swift` - Matches any Swift file with the suffix "_generated" in the project.
+
+
+Linting
+-------
+
+SwiftFormat is primarily designed as a formatter rather than a linter, i.e. it is designed to fix your code rather than tell you what's wrong with it. However, sometimes it can be useful to verify that code has been formatted in a context where it is not desirable to actually change it.
+
+A typical example would be as part of a CI (Continuous Integration) process, where you may wish to have an automated script that checks committed code for style violations. While you could use a separate tool such as [SwiftLint](https://github.com/realm/SwiftLint) for this, it makes sense to be able to validate the formatting against the exact same rules as you are using to apply it.
+
+In order to run SwiftFormat as a linter, you can use the `--lint` command-line option:
+
+```bash
+> swiftformat --lint path/to/project
+```
+
+This works exactly the same way as when running in format mode, and all the same configuration options apply, however no files will be modified. SwiftFormat will simply format each file in memory and then compare the result against the input. If any formatting changes would have been applied, it will report an error.
+
+The `--lint` option is very similar to `--dryrun`, except that `--lint` will return a nonzero error code if any changes are detected, which is useful if you want it to fail a build step on your CI server.
+
+By default, `--lint` will only report the number of files that were changed, but you can use the additional `--verbose` flag to display a detailed report about which specific rules were applied to which specific files.
+
+
+FAQ
+-----
+
+There haven't been many questions yet, but here's what I'd like to think people are wondering:
+
+
+*Q. What versions of Swift are supported?*
+
+> A. The framework compiles on Swift 3.x or 4.x and can format programs written in Swift 3.x or 4.x. Swift 2.x is no longer actively supported. If you are still using Swift 2.x, and find that SwiftFormat breaks your code, the best solution is probably to revert to an earlier SwiftFormat release, or enable only a small subset of rules.
+
+
+*Q. I don't like how SwiftFormat formatted my code*
+
+> A. That's not a question (but see below).
+
+
+*Q. How can I modify the formatting rules?*
+
+> A. Many configuration options are exposed in the command-line interface or .swiftformat configuration file. You can either set these manually, or use the `--inferoptions` argument to automatically generate the configuration from your existing project.
+
+> If there is a rule that you don't like, and which cannot be configured to your liking via the command-line options, you can disable the rule by using the `--disable` argument, followed by the name of the rule. You can display a list of all rules using the `--rules` argument, and their behaviors are documented above this section in the README.
+
+> If you are using the Xcode source editor extension, rules and options can be configured using the [SwiftFormat for Xcode](#xcode-source-editor-extension) host application. Unfortunately, due to limitation of the Extensions API, there is no way to configure these on a per-project basis.
+
+> If the options you want aren't exposed, and disabling the rule doesn't solve the problem, the rules are implemented as functions in the file `Rules.swift`, so you can modify them and build a new version of the command-line tool. If you think your changes might be generally useful, make a pull request.
+
+
+*Q. Why can't I set the indent width or choose between tabs/spaces in the [SwiftFormat for Xcode](#xcode-source-editor-extension) options?*
+
+> Indent width and tabs/spaces can be configured in Xcode on a per project-basis. You'll find the option under "Text Settings" in the right-hand sidebar.
+
+
+*Q. After applying SwiftFormat, my code won't compile. Is that a bug?*
+
+> A. SwiftFormat should never break your code. Check the known issues below, and if it's not already listed there, or the suggested workaround doesn't solve your problem, please [raise an issue on Github](https://github.com/nicklockwood/SwiftFormat/issues).
+
+
+*Q. Why did you write yet another Swift formatting tool?*
+
+> A. Surprisingly, there really aren't that many other options out there, and none of them currently support all the rules I wanted. The only other comparable ones I'm aware of are Realm's [SwiftLint](https://github.com/realm/SwiftLint) and Jintin's [Swimat](https://github.com/Jintin/Swimat) - you might want to try those if SwiftFormat doesn't meet your requirements.
+
+
+*Q. Does it use SourceKit?*
+
+> A. No. SourceKit is too slow and doesn't support parsing incomplete code fragments.
+
+
+*Q. What about LibSyntax?*
+
+> A. No. SwiftFormat predates LibSyntax by a couple of years, and it would be a big job to rewrite.
+
+
+*Q. How does it work?*
+
+> A. First it loops through the source file character-by-character and breaks it into tokens, such as `number`, `identifier`, `linebreak`, etc. That's handled by the functions in `Tokenizer.swift`.
+
+> Next, it applies a series of formatting rules to the token array, such as removing whitespace at the end of a line, or ensuring each opening brace appears on the same line as the preceding non-space token. The rules are defined as methods of the `FormatRules` class in `Rules.swift`, and are detected automatically using runtime magic. Each rule is designed to be independent of the others, so they can be enabled or disabled individually.
+
+> Rules are applied recursively until no changes are detected. Finally, the modified token array is stitched back together to re-generate the source file.
+
+
+*Q. Why aren't you using regular expressions?*
+
+> A. See https://xkcd.com/1171/ for details.
+
+
+*Q. Can I use SwiftFormat to lint my code without changing it?*
+
+> A. Yes, see the [linting](#linting) section above for details.
+
+
+*Q. Can I use the `SwiftFormat.framework` inside another app?*
+
+> A. Yes, the SwiftFormat framework can be included in an app or test target, and used for many kinds of parsing and processing of Swift source code besides formatting. The SwiftFormat framework is available as a [CocoaPod](https://cocoapods.org/pods/SwiftFormat) for easy integration.
+
+
+Cache
+------
+
+SwiftFormat uses a cache file to avoid reformatting files that haven't changed. For a large project, this can significantly reduce processing time.
+
+By default, the cache is stored in `~/Library/Caches/com.charcoaldesign.swiftformat`. Use the command-line option `--cache ignore` to ignore the cached version and re-apply formatting to all files. Alternatively, you can use `--cache clear` to delete the cache (or you can just manually delete the cache file).
+
+The cache is shared between all projects. The file is fairly small, as it only stores the path and size for each file, not the contents. If you do start experiencing slowdown due to the cache growing too large, you might want to consider using a separate cache file for each project.
+
+You can specify a custom cache file location by passing a path as the `--cache` option value. For example, you might want to store the cache file inside your project directory. It is fine to check in the cache file if you want to share it between different users of your project, as the paths stored in the cache are relative to the location of the formatted files.
+
+
+File headers
+-------------
+
+SwiftFormat can be configured to strip or replace the header comments in every file with a template. The "header comment" is defined as a comment block that begins on the first nonblank line in the file, and is followed by at least one blank line. This may consist of a single comment body, or multiple comments on consecutive lines:
+
+```swift
+// This is a header comment
+```
+
+```swift
+// This is a regular comment
+func foo(bar: Int) -> Void { ... }
+```
+
+The header template is a string that you provide using the `--header` command-line option. Passing a value of `ignore` (the default) will leave the header comments unmodified. Passing `strip` or an empty string `""` will remove them. If you wish to provide a custom header template, the format is as follows:
+
+For a single-line template: `--header "Copyright (c) 2017 Foobar Industries"`
+
+For a multiline comment, mark linebreaks with `\n`: `--header "First line\nSecond line"`
+
+You can optionally include Swift comment markup in the template if you wish: `--header "/*--- Header comment ---*/"`
+
+If you do not include comment markup, each line in the template will be prepended with `//` and a single space.
+
+Finally, it is common practice to include the current year in a comment header copyright notice. To do that, use the following syntax:
+
+```bash
+--header "Copyright (c) {year} Foobar Industries"
+```
+    
+And the `{year}` token will be automatically replaced by the current year whenever SwiftFormat is applied (**Note:** the year is determined from the locale and timezone of the machine running the script).
+
+
+Known issues
+---------------
+
+* When using the `--self remove` option, the `redundantSelf` rule will remove references to `self` in autoclosure arguments, which may change the meaning of the code, or cause it not to compile. To work around this issue, use the `// swiftformat:disable:next redundantSelf` comment directive to disable the rule for any affected lines of code (or just disable the `redundantSelf` rule completely). If you are using the `--self insert` option then this is not an issue.
+
+* If you assign `SomeClass.self` to a variable and then instantiate an instance of the class using that variable, Swift requires that you use an explicit `.init()`, however the   `redundantInit` rule is not currently capable of detecting this situation and will remove the `.init`. To work around this issue, use the `// swiftformat:disable:next redundantInit` comment directive to disable the rule for any affected lines of code (or just disable the `redundantInit` rule completely).
+
+* The `--self insert` option can only recognize locally declared member variables, not ones inherited from superclasses or extensions in other files, so it cannot insert missing `self` references for those. Note that the reverse is not true: `--self remove` should remove *all* redundant `self` references.
+
+* The `trailingClosures` rule will sometimes generate ambiguous code that breaks your program. For this reason, the rule is disabled by default. It is recommended that you apply this rule manually and review the changes, rather than including it in an automated formatting process.
+
+* The `isEmpty` rule will convert `count == 0` to `isEmpty` even for types that do not have an `isEmpty` method, such as `NSArray`/`NSDictionary`/etc. Use of Foundation collections in Swift code is pretty rare, but just in case, the rule is disabled by default.
+
+* Under rare circumstances, SwiftFormat may misinterpret a generic type followed by an `=` sign as a pair of `<` and `>=` expressions. For example, the following case would be handled incorrectly:
+
+    ```swift
+    let foo: Dictionary<String, String>=["Hello": "World"]
+    ```    
+
+    To work around this, either manually add spaces around the `=` character to eliminate the ambiguity, or add a comment directive above that line in the file:
+    
+    ```swift
+    // swiftformat:disable:next spaceAroundOperators
+    let foo: Dictionary<String, String>=["Hello": "World"]
+    ```
+
+* If a file begins with a comment, the `stripHeaders` rule will remove it if is followed by a blank line. To avoid this, make sure that the first comment is directly followed by a line of code.
+
+* SwiftFormat currently reformats multiline comment blocks without regard for the original indenting. That means
+
+    ```swift
+    /* some documentation
+    
+          func codeExample() {
+              print("Hello World")
+          }
+ 
+     */
+    ```
+         
+    Will become
+    
+    ```swift
+    /* some documentation
+    
+     func codeExample() {
+     print("Hello World")
+     }
+     
+     */
+    ```
+         
+    To work around that, you can disable automatic indenting of comments using the `comments` command-line flag.
+    
+    Alternatively, if you prefer to leave the comment indenting feature enabled, you can rewrite your multiline comment as a block of single-line comments...
+    
+    ```swift
+    // some documentation
+    //
+    //    func codeExample() {
+    //        print("Hello World")
+    //    }
+    //
+    //
+    ```
+        
+    Or begin each line with a `*` (or any other non-whitespace character)
+    
+    ```swift
+    /* some documentation
+     *
+     *    func codeExample() {
+     *        print("Hello World")
+     *    }
+     *  
+     */
+    ```
+         
+* The formatted file cache is based on a hash of the file contents, so it's possible (though unlikely) that an edited file will have the exact same hash as the previously formatted version, causing SwiftFormat to incorrectly identify it as not having changed, and fail to update it.
+
+    To fix this, you can use the command-line option `--cache ignore` to force SwiftFormat to ignore the cache for this run, or just type an extra space in the file (which SwiftFormat will then remove again when it applies the correct formatting).
+
+
+Credits
+------------
+
+* [Tony Arnold](https://github.com/tonyarnold) - Xcode source editor extension
+* [Vincent Bernier](https://github.com/vinceburn) - Xcode extension settings UI
+* [Maxime Marinel](https://github.com/bourvill) - Git pre-commit hook script
+* [Romain Pouclet](https://github.com/palleas) - Homebrew formula
+* [Ali Akhtarzada](https://github.com/aliak00) - Several path-related CLI enhancements
+* [Yonas Kolb](https://github.com/yonaskolb) - Swift Package Manager integration
+* [Nick Lockwood](https://github.com/nicklockwood) - Everything else
+
+([Full list of contributors](https://github.com/nicklockwood/SwiftFormat/graphs/contributors))

+ 21 - 0
Pods/SwiftLint/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Realm Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

BIN
Pods/SwiftLint/swiftlint


+ 1 - 1
Pods/Target Support Files/ALCameraViewController/ALCameraViewController.xcconfig

@@ -1,6 +1,6 @@
 CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ALCameraViewController
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
+OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_ROOT = ${SRCROOT}

+ 1 - 1
Pods/Target Support Files/MessageInputBar/MessageInputBar.xcconfig

@@ -1,6 +1,6 @@
 CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MessageInputBar
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
+OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_ROOT = ${SRCROOT}

+ 1 - 1
Pods/Target Support Files/MessageKit/MessageKit.xcconfig

@@ -1,7 +1,7 @@
 CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MessageKit
 FRAMEWORK_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/MessageInputBar"
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
+OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_ROOT = ${SRCROOT}

+ 50 - 0
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.markdown

@@ -172,6 +172,56 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 
 
+## SwiftFormat
+
+MIT License
+
+Copyright (c) 2016 Nick Lockwood
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
+## SwiftLint
+
+The MIT License (MIT)
+
+Copyright (c) 2015 Realm Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
 ## SwiftyBeaver
 
 The MIT License (MIT)

+ 62 - 0
Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios-acknowledgements.plist

@@ -225,6 +225,68 @@ THE SOFTWARE.
 			<key>Type</key>
 			<string>PSGroupSpecifier</string>
 		</dict>
+		<dict>
+			<key>FooterText</key>
+			<string>MIT License
+
+Copyright (c) 2016 Nick Lockwood
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+</string>
+			<key>License</key>
+			<string>MIT</string>
+			<key>Title</key>
+			<string>SwiftFormat</string>
+			<key>Type</key>
+			<string>PSGroupSpecifier</string>
+		</dict>
+		<dict>
+			<key>FooterText</key>
+			<string>The MIT License (MIT)
+
+Copyright (c) 2015 Realm Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+</string>
+			<key>License</key>
+			<string>MIT</string>
+			<key>Title</key>
+			<string>SwiftLint</string>
+			<key>Type</key>
+			<string>PSGroupSpecifier</string>
+		</dict>
 		<dict>
 			<key>FooterText</key>
 			<string>The MIT License (MIT)

+ 1 - 1
Pods/Target Support Files/QuickTableViewController/Info.plist

@@ -15,7 +15,7 @@
   <key>CFBundlePackageType</key>
   <string>FMWK</string>
   <key>CFBundleShortVersionString</key>
-  <string>1.0.0</string>
+  <string>1.1.0</string>
   <key>CFBundleSignature</key>
   <string>????</string>
   <key>CFBundleVersion</key>

+ 1 - 1
Pods/Target Support Files/QuickTableViewController/QuickTableViewController.xcconfig

@@ -1,6 +1,6 @@
 CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/QuickTableViewController
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
+OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_ROOT = ${SRCROOT}

+ 1 - 1
Pods/Target Support Files/ReachabilitySwift/ReachabilitySwift.xcconfig

@@ -1,7 +1,7 @@
 CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/ReachabilitySwift
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
 OTHER_LDFLAGS = -framework "SystemConfiguration"
-OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
+OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_ROOT = ${SRCROOT}

+ 1 - 1
Pods/Target Support Files/SwiftyBeaver/SwiftyBeaver.xcconfig

@@ -1,6 +1,6 @@
 CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftyBeaver
 GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
-OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS"
+OTHER_SWIFT_FLAGS = $(inherited) "-D" "COCOAPODS" "-suppress-warnings"
 PODS_BUILD_DIR = ${BUILD_DIR}
 PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
 PODS_ROOT = ${SRCROOT}

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

@@ -51,6 +51,7 @@
 		7092474120B3869500AF8799 /* ContactProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7092474020B3869500AF8799 /* ContactProfileViewController.swift */; };
 		70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */; };
 		70B8882E2091B8550074812E /* ContactCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 70B8882D2091B8550074812E /* ContactCell.swift */; };
+		7837B64021E54DC600CDE126 /* .swiftlint.yml in Resources */ = {isa = PBXBuildFile; fileRef = 7837B63F21E54DC600CDE126 /* .swiftlint.yml */; };
 		785BE16821E247F1003BE98C /* MessageInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 785BE16721E247F1003BE98C /* MessageInfoViewController.swift */; };
 		789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */; };
 		789E879D21D6DF86003ED1C5 /* ProgressHud.swift in Sources */ = {isa = PBXBuildFile; fileRef = 789E879C21D6DF86003ED1C5 /* ProgressHud.swift */; };
@@ -180,6 +181,7 @@
 		7092474020B3869500AF8799 /* ContactProfileViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactProfileViewController.swift; sourceTree = "<group>"; };
 		70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewGroupMemberChoiceController.swift; sourceTree = "<group>"; };
 		70B8882D2091B8550074812E /* ContactCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCell.swift; sourceTree = "<group>"; };
+		7837B63F21E54DC600CDE126 /* .swiftlint.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .swiftlint.yml; sourceTree = "<group>"; };
 		785BE16721E247F1003BE98C /* MessageInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageInfoViewController.swift; sourceTree = "<group>"; };
 		789E879521D6CB58003ED1C5 /* QrCodeReaderController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeReaderController.swift; sourceTree = "<group>"; };
 		789E879C21D6DF86003ED1C5 /* ProgressHud.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressHud.swift; sourceTree = "<group>"; };
@@ -385,6 +387,7 @@
 		7A9FB1371FB061E2001FEA36 = {
 			isa = PBXGroup;
 			children = (
+				7837B63F21E54DC600CDE126 /* .swiftlint.yml */,
 				78E45E2C21D1774200D4B15E /* dc_context.c */,
 				78E45E2D21D1774200D4B15E /* dc_context.h */,
 				78E45E2E21D1774200D4B15E /* dc_move.c */,
@@ -544,6 +547,8 @@
 				7A9FB13D1FB061E2001FEA36 /* Frameworks */,
 				7A9FB13E1FB061E2001FEA36 /* Resources */,
 				30C8FE37A924BE7AFF9661C1 /* [CP] Embed Pods Frameworks */,
+				7837B63821E54CB400CDE126 /* ShellScript */,
+				7837B64621E5532B00CDE126 /* ShellScript */,
 			);
 			buildRules = (
 			);
@@ -638,6 +643,7 @@
 			isa = PBXResourcesBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				7837B64021E54DC600CDE126 /* .swiftlint.yml in Resources */,
 				7A9FB14E1FB061E2001FEA36 /* LaunchScreen.storyboard in Resources */,
 				7A9FB14B1FB061E2001FEA36 /* Assets.xcassets in Resources */,
 			);
@@ -696,6 +702,40 @@
 			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
 			showEnvVarsInLog = 0;
 		};
+		7837B63821E54CB400CDE126 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\"\n";
+		};
+		7837B64621E5532B00CDE126 /* ShellScript */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+			);
+			outputFileListPaths = (
+			);
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/SwiftFormat/CommandLineTool/swiftformat\"\n";
+		};
 /* End PBXShellScriptBuildPhase section */
 
 /* Begin PBXSourcesBuildPhase section */

+ 11 - 11
deltachat-ios/AppCoordinator.swift

@@ -9,21 +9,21 @@
 import UIKit
 
 protocol Coordinator {
-    func setupViewControllers(window: UIWindow)
+  func setupViewControllers(window: UIWindow)
 }
 
 class AppCoordinator: Coordinator {
-    let baseController = BaseController()
+  let baseController = BaseController()
 
-    func setupViewControllers(window: UIWindow) {
-        window.rootViewController = AppTabBarController()
-        window.makeKeyAndVisible()
-    }
+  func setupViewControllers(window: UIWindow) {
+    window.rootViewController = AppTabBarController()
+    window.makeKeyAndVisible()
+  }
 
-    func setupInnerViewControllers() {
-        let chatListController = ChatListController()
-        let chatNavigationController = UINavigationController(rootViewController: chatListController)
+  func setupInnerViewControllers() {
+    let chatListController = ChatListController()
+    let chatNavigationController = UINavigationController(rootViewController: chatListController)
 
-        baseController.present(chatNavigationController, animated: false, completion: nil)
-    }
+    baseController.present(chatNavigationController, animated: false, completion: nil)
+  }
 }

+ 237 - 237
deltachat-ios/AppDelegate.swift

@@ -17,293 +17,293 @@ var mailboxPointer: UnsafeMutablePointer<dc_context_t>!
 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 {
-    static let appCoordinator = AppCoordinator()
-    static var progress: Float = 0
-    static var lastErrorDuringConfig: String?
-    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 []
+  static let appCoordinator = AppCoordinator()
+  static var progress: Float = 0
+  static var lastErrorDuringConfig: String?
+  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: "=")
+      }
     }
 
-    func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
-        DBDebugToolkit.setup()
-        DBDebugToolkit.setupCrashReporting()
-
-        let console = ConsoleDestination()
-        logger.addDestination(console)
-
-        logger.info("launching")
-
-        // Override point for customization after application launch.
+    return []
+  }
 
-        window = UIWindow(frame: UIScreen.main.bounds)
-        guard let window = window else {
-            fatalError("window was nil in app delegate")
-        }
-        AppDelegate.appCoordinator.setupViewControllers(window: window)
+  func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
+    DBDebugToolkit.setup()
+    DBDebugToolkit.setupCrashReporting()
 
-        UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
+    let console = ConsoleDestination()
+    logger.addDestination(console)
 
-        start()
-        open()
+    logger.info("launching")
 
-        registerForPushNotifications()
+    // Override point for customization after application launch.
 
-        return true
+    window = UIWindow(frame: UIScreen.main.bounds)
+    guard let window = window else {
+      fatalError("window was nil in app delegate")
     }
+    AppDelegate.appCoordinator.setupViewControllers(window: window)
 
-    func application(_: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
-        logger.info("---- background-fetch ----")
+    UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
 
-        start {
-            // TODO: actually set the right value depending on if we found sth
-            completionHandler(.newData)
-        }
-    }
-
-    func applicationWillEnterForeground(_: UIApplication) {
-        logger.info("---- foreground ----")
-        start()
-    }
+    start()
+    open()
 
-    func applicationDidEnterBackground(_: UIApplication) {
-        logger.info("---- background ----")
+    registerForPushNotifications()
 
-        reachability.stopNotifier()
-        NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
+    return true
+  }
 
-        maybeStop()
-    }
+  func application(_: UIApplication, performFetchWithCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
+    logger.info("---- background-fetch ----")
 
-    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()
-            }
-        }
+    start {
+      // TODO: actually set the right value depending on if we found sth
+      completionHandler(.newData)
     }
-
-    func applicationWillTerminate(_: UIApplication) {
-        logger.info("---- terminate ----")
-        close()
-
-        reachability.stopNotifier()
-        NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
+  }
+
+  func applicationWillEnterForeground(_: UIApplication) {
+    logger.info("---- foreground ----")
+    start()
+  }
+
+  func applicationDidEnterBackground(_: UIApplication) {
+    logger.info("---- background ----")
+
+    reachability.stopNotifier()
+    NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
+
+    maybeStop()
+  }
+
+  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 dbfile() -> String {
-        let paths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
-        let documentsPath = paths[0]
-
-        return documentsPath + "/messenger.db"
-    }
+  func applicationWillTerminate(_: UIApplication) {
+    logger.info("---- terminate ----")
+    close()
 
-    func open() {
-        logger.info("open: \(dbfile())")
+    reachability.stopNotifier()
+    NotificationCenter.default.removeObserver(self, name: .reachabilityChanged, object: reachability)
+  }
 
-        _ = dc_open(mailboxPointer, dbfile(), nil)
-    }
+  func dbfile() -> String {
+    let paths = NSSearchPathForDirectoriesInDomains(.libraryDirectory, .userDomainMask, true)
+    let documentsPath = paths[0]
 
-    func stop() {
-        state = .background
+    return documentsPath + "/messenger.db"
+  }
 
-        dc_interrupt_imap_idle(mailboxPointer)
-        dc_interrupt_smtp_idle(mailboxPointer)
-        dc_interrupt_mvbox_idle(mailboxPointer)
-        dc_interrupt_sentbox_idle(mailboxPointer)
-    }
+  func open() {
+    logger.info("open: \(dbfile())")
 
-    func close() {
-        state = .stopped
+    _ = dc_open(mailboxPointer, dbfile(), nil)
+  }
 
-        dc_close(mailboxPointer)
-        mailboxPointer = nil
-    }
+  func stop() {
+    state = .background
 
-    func start(_ completion: (() -> Void)? = nil) {
-        logger.info("---- start ----")
+    dc_interrupt_imap_idle(mailboxPointer)
+    dc_interrupt_smtp_idle(mailboxPointer)
+    dc_interrupt_mvbox_idle(mailboxPointer)
+    dc_interrupt_sentbox_idle(mailboxPointer)
+  }
 
-        if state == .running {
-            return
-        }
+  func close() {
+    state = .stopped
 
-        if mailboxPointer == nil {
-            //       - second param remains nil (user data for more than one mailbox)
-            mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
-            guard mailboxPointer != nil else {
-                fatalError("Error: dc_context_new returned nil")
-            }
-        }
+    dc_close(mailboxPointer)
+    mailboxPointer = 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 start(_ completion: (() -> Void)? = nil) {
+    logger.info("---- start ----")
 
-        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 state == .running {
+      return
+    }
 
-        if MRConfig.sentboxWatch {
-            DispatchQueue.global(qos: .background).async {
-                while self.state == .running {
-                    dc_perform_sentbox_fetch(mailboxPointer)
-                    dc_perform_sentbox_idle(mailboxPointer)
-                }
-            }
-        }
+    if mailboxPointer == nil {
+      //       - second param remains nil (user data for more than one mailbox)
+      mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
+      guard mailboxPointer != nil else {
+        fatalError("Error: dc_context_new returned nil")
+      }
+    }
 
-        if MRConfig.mvboxWatch {
-            DispatchQueue.global(qos: .background).async {
-                while self.state == .running {
-                    dc_perform_mvbox_fetch(mailboxPointer)
-                    dc_perform_mvbox_idle(mailboxPointer)
-                }
-            }
-        }
+    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()
+      }
+    }
 
-        NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
-        do {
-            try reachability.startNotifier()
-        } catch {
-            logger.info("could not start reachability notifier")
-        }
+    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()
+      }
+    }
 
-        let info: [DBCustomVariable] = getCoreInfo().map { kv in
-            let value = kv.count > 1 ? kv[1] : ""
-            return DBCustomVariable(name: kv[0], value: value)
+    if MRConfig.sentboxWatch {
+      DispatchQueue.global(qos: .background).async {
+        while self.state == .running {
+          dc_perform_sentbox_fetch(mailboxPointer)
+          dc_perform_sentbox_idle(mailboxPointer)
         }
-
-        DBDebugToolkit.add(info)
+      }
     }
 
-    @objc 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: dc_notificationStateChanged,
-                        object: nil,
-                        userInfo: ["state": "online"])
-            }
-        case .none:
-            logger.info("network: not reachable")
-            let nc = NotificationCenter.default
-            DispatchQueue.main.async {
-                nc.post(name: dc_notificationStateChanged,
-                        object: nil,
-                        userInfo: ["state": "offline"])
-            }
+    if MRConfig.mvboxWatch {
+      DispatchQueue.global(qos: .background).async {
+        while self.state == .running {
+          dc_perform_mvbox_fetch(mailboxPointer)
+          dc_perform_mvbox_idle(mailboxPointer)
         }
+      }
     }
 
-    // MARK: - BackgroundTask
-
-    func registerBackgroundTask() {
-        logger.info("background task registered")
-        backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
-            self?.endBackgroundTask()
-        }
-        assert(backgroundTask != .invalid)
+    NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(note:)), name: .reachabilityChanged, object: reachability)
+    do {
+      try reachability.startNotifier()
+    } catch {
+      logger.info("could not start reachability notifier")
     }
 
-    func endBackgroundTask() {
-        logger.info("background task ended")
-        UIApplication.shared.endBackgroundTask(backgroundTask)
-        backgroundTask = .invalid
+    let info: [DBCustomVariable] = getCoreInfo().map { kv in
+      let value = kv.count > 1 ? kv[1] : ""
+      return DBCustomVariable(name: kv[0], value: value)
     }
 
-    // MARK: - PushNotifications
+    DBDebugToolkit.add(info)
+  }
+
+  @objc 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"])
+      }
+    }
+  }
 
-    func registerForPushNotifications() {
-        UNUserNotificationCenter.current().delegate = self
+  // MARK: - BackgroundTask
 
-        UNUserNotificationCenter.current()
-            .requestAuthorization(options: [.alert, .sound, .badge]) {
-                granted, _ in
-                logger.info("permission granted: \(granted)")
-                guard granted else { return }
-                self.getNotificationSettings()
-            }
+  func registerBackgroundTask() {
+    logger.info("background task registered")
+    backgroundTask = UIApplication.shared.beginBackgroundTask { [weak self] in
+      self?.endBackgroundTask()
     }
-
-    func getNotificationSettings() {
-        UNUserNotificationCenter.current().getNotificationSettings { settings in
-            logger.info("Notification settings: \(settings)")
-        }
+    assert(backgroundTask != .invalid)
+  }
+
+  func endBackgroundTask() {
+    logger.info("background task ended")
+    UIApplication.shared.endBackgroundTask(backgroundTask)
+    backgroundTask = .invalid
+  }
+
+  // MARK: - PushNotifications
+
+  func registerForPushNotifications() {
+    UNUserNotificationCenter.current().delegate = self
+
+    UNUserNotificationCenter.current()
+      .requestAuthorization(options: [.alert, .sound, .badge]) {
+        granted, _ in
+        logger.info("permission granted: \(granted)")
+        guard granted else { return }
+        self.getNotificationSettings()
+      }
+  }
+
+  func getNotificationSettings() {
+    UNUserNotificationCenter.current().getNotificationSettings { settings in
+      logger.info("Notification settings: \(settings)")
     }
-
-    func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
-        logger.info("forground notification")
-        completionHandler([.alert, .sound])
+  }
+
+  func userNotificationCenter(_: UNUserNotificationCenter, willPresent _: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
+    logger.info("forground notification")
+    completionHandler([.alert, .sound])
+  }
+
+  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
+        )
+      }
     }
 
-    func userNotificationCenter(_: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
-        if response.notification.request.identifier == Constants.notificationIdentifier {
-            logger.info("handling notifications")
-            let userInfo = response.notification.request.content.userInfo
-            let nc = NotificationCenter.default
-            DispatchQueue.main.async {
-                nc.post(
-                    name: dc_notificationViewChat,
-                    object: nil,
-                    userInfo: userInfo
-                )
-            }
-        }
-
-        completionHandler()
-    }
+    completionHandler()
+  }
 }

+ 42 - 42
deltachat-ios/AppTabBarController.swift

@@ -9,46 +9,46 @@
 import UIKit
 
 class AppTabBarController: UITabBarController {
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        let contactListController = ContactListController()
-        let contactNavigationController = NavigationController(rootViewController: contactListController)
-        let contactImage = UIImage(named: "contacts")
-        contactNavigationController.tabBarItem = UITabBarItem(title: "Contacts", image: contactImage, tag: 0)
-
-        let mailboxController = ChatViewController(chatId: Int(DC_CHAT_ID_DEADDROP), title: "Mailbox")
-        mailboxController.disableWriting = true
-        let mailboxNavigationController = NavigationController(rootViewController: mailboxController)
-        let mailboxImage = UIImage(named: "message")
-        mailboxNavigationController.tabBarItem = UITabBarItem(title: "Mailbox", image: mailboxImage, tag: 1)
-
-        let profileController = ProfileViewController()
-        let profileNavigationController = NavigationController(rootViewController: profileController)
-        let profileImage = UIImage(named: "report_card")
-        profileNavigationController.tabBarItem = UITabBarItem(title: "My Profile", image: profileImage, tag: 2)
-
-        let chatListController = ChatListController()
-        let chatNavigationController = NavigationController(rootViewController: chatListController)
-        let chatImage = UIImage(named: "chat")
-        chatNavigationController.tabBarItem = UITabBarItem(title: "Chats", image: chatImage, tag: 3)
-
-        let settingsController = SettingsViewController()
-        let settingsNavigationController = NavigationController(rootViewController: settingsController)
-        let settingsImage = UIImage(named: "settings")
-        settingsNavigationController.tabBarItem = UITabBarItem(title: "Settings", image: settingsImage, tag: 4)
-
-        let tabBarList = [
-            contactNavigationController,
-            mailboxNavigationController,
-            profileNavigationController,
-            chatNavigationController,
-            settingsNavigationController,
-        ]
-
-        viewControllers = tabBarList
-        selectedIndex = 3
-
-        tabBar.tintColor = Constants.primaryColor
-    }
+  override func viewDidLoad() {
+    super.viewDidLoad()
+
+    let contactListController = ContactListController()
+    let contactNavigationController = NavigationController(rootViewController: contactListController)
+    let contactImage = UIImage(named: "contacts")
+    contactNavigationController.tabBarItem = UITabBarItem(title: "Contacts", image: contactImage, tag: 0)
+
+    let mailboxController = ChatViewController(chatId: Int(DC_CHAT_ID_DEADDROP), title: "Mailbox")
+    mailboxController.disableWriting = true
+    let mailboxNavigationController = NavigationController(rootViewController: mailboxController)
+    let mailboxImage = UIImage(named: "message")
+    mailboxNavigationController.tabBarItem = UITabBarItem(title: "Mailbox", image: mailboxImage, tag: 1)
+
+    let profileController = ProfileViewController()
+    let profileNavigationController = NavigationController(rootViewController: profileController)
+    let profileImage = UIImage(named: "report_card")
+    profileNavigationController.tabBarItem = UITabBarItem(title: "My Profile", image: profileImage, tag: 2)
+
+    let chatListController = ChatListController()
+    let chatNavigationController = NavigationController(rootViewController: chatListController)
+    let chatImage = UIImage(named: "chat")
+    chatNavigationController.tabBarItem = UITabBarItem(title: "Chats", image: chatImage, tag: 3)
+
+    let settingsController = SettingsViewController()
+    let settingsNavigationController = NavigationController(rootViewController: settingsController)
+    let settingsImage = UIImage(named: "settings")
+    settingsNavigationController.tabBarItem = UITabBarItem(title: "Settings", image: settingsImage, tag: 4)
+
+    let tabBarList = [
+      contactNavigationController,
+      mailboxNavigationController,
+      profileNavigationController,
+      chatNavigationController,
+      settingsNavigationController,
+    ]
+
+    viewControllers = tabBarList
+    selectedIndex = 3
+
+    tabBar.tintColor = Constants.primaryColor
+  }
 }

+ 51 - 51
deltachat-ios/BaseController.swift

@@ -9,72 +9,72 @@
 import UIKit
 
 class ProgressViewContainer: UIView {
-    let progressView = UIProgressView(progressViewStyle: .default)
+  let progressView = UIProgressView(progressViewStyle: .default)
 
-    init() {
-        super.init(frame: .zero)
-        backgroundColor = .lightGray
+  init() {
+    super.init(frame: .zero)
+    backgroundColor = .lightGray
 
-        let label = UILabel()
-        label.translatesAutoresizingMaskIntoConstraints = false
-        addSubview(label)
-        label.textAlignment = .center
-        label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
-        label.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -50).isActive = true
-        label.textColor = .darkGray
-        label.text = "Configuring…"
+    let label = UILabel()
+    label.translatesAutoresizingMaskIntoConstraints = false
+    addSubview(label)
+    label.textAlignment = .center
+    label.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
+    label.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -50).isActive = true
+    label.textColor = .darkGray
+    label.text = "Configuring…"
 
-        let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
-        activityIndicator.translatesAutoresizingMaskIntoConstraints = false
-        addSubview(activityIndicator)
+    let activityIndicator = UIActivityIndicatorView(style: .whiteLarge)
+    activityIndicator.translatesAutoresizingMaskIntoConstraints = false
+    addSubview(activityIndicator)
 
-        activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
-        activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 50).isActive = true
-        activityIndicator.startAnimating()
+    activityIndicator.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true
+    activityIndicator.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 50).isActive = true
+    activityIndicator.startAnimating()
 
-        progressView.progressTintColor = .darkGray
-        progressView.trackTintColor = .white
-        progressView.progress = 0.0
+    progressView.progressTintColor = .darkGray
+    progressView.trackTintColor = .white
+    progressView.progress = 0.0
 
-        addSubview(progressView)
+    addSubview(progressView)
 
-        progressView.translatesAutoresizingMaskIntoConstraints = false
-        progressView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
-        progressView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
-        progressView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
-    }
+    progressView.translatesAutoresizingMaskIntoConstraints = false
+    progressView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
+    progressView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10).isActive = true
+    progressView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10).isActive = true
+  }
 
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
 }
 
 class BaseController: UIViewController {
-    let progressViewContainer = ProgressViewContainer()
-    var progressChangedObserver: Any?
+  let progressViewContainer = ProgressViewContainer()
+  var progressChangedObserver: Any?
 
-    override func loadView() {
-        view = progressViewContainer
-    }
+  override func loadView() {
+    view = progressViewContainer
+  }
 
-    override func viewDidLoad() {}
+  override func viewDidLoad() {}
 
-    override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-        let nc = NotificationCenter.default
-        progressChangedObserver = nc.addObserver(forName: Notification.Name(rawValue: "ProgressUpdated"),
-                                                 object: nil, queue: nil) {
-            _ in
-            logger.info("----------- ProgressUpdated notification received --------")
-            self.progressViewContainer.progressView.progress = AppDelegate.progress
-        }
+  override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
+    let nc = NotificationCenter.default
+    progressChangedObserver = nc.addObserver(forName: Notification.Name(rawValue: "ProgressUpdated"),
+                                             object: nil, queue: nil) {
+      _ in
+      logger.info("----------- ProgressUpdated notification received --------")
+      self.progressViewContainer.progressView.progress = AppDelegate.progress
     }
+  }
 
-    override func viewDidDisappear(_ animated: Bool) {
-        super.viewDidDisappear(animated)
-        let nc = NotificationCenter.default
-        if let progressChangedObserver = self.progressChangedObserver {
-            nc.removeObserver(progressChangedObserver)
-        }
+  override func viewDidDisappear(_ animated: Bool) {
+    super.viewDidDisappear(animated)
+    let nc = NotificationCenter.default
+    if let progressChangedObserver = self.progressChangedObserver {
+      nc.removeObserver(progressChangedObserver)
     }
+  }
 }

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 689 - 683
deltachat-ios/ChatViewController.swift


+ 16 - 16
deltachat-ios/Constants.swift

@@ -9,24 +9,24 @@
 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 primaryColor = UIColor(red: 81 / 255, green: 73 / 255, blue: 255 / 255, alpha: 1)
-    static let messagePrimaryColor = Constants.primaryColor.withAlphaComponent(0.5)
-    static let messageSecondaryColor = UIColor(red: 245 / 255, green: 245 / 255, blue: 245 / 255, alpha: 1)
+  static let primaryColor = UIColor(red: 81 / 255, green: 73 / 255, blue: 255 / 255, alpha: 1)
+  static let messagePrimaryColor = Constants.primaryColor.withAlphaComponent(0.5)
+  static let messageSecondaryColor = UIColor(red: 245 / 255, green: 245 / 255, blue: 245 / 255, alpha: 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 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"
 }

+ 118 - 118
deltachat-ios/ContactCell.swift

@@ -9,123 +9,123 @@
 import UIKit
 
 class ContactCell: UITableViewCell {
-    let avatar = UIView()
-    let imgView = UIImageView()
-    let initialsLabel = UILabel()
-    let nameLabel = UILabel()
-    let emailLabel = UILabel()
-
-    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)
-
-        // configure and layout initialsLabel
-        let initialsLabelSize: CGFloat = 54
-        let initialsLabelCornerRadius = (initialsLabelSize - 6) / 2
-        let margin: CGFloat = 15
-
-        initialsLabel.textAlignment = NSTextAlignment.center
-        initialsLabel.textColor = UIColor.white
-        initialsLabel.font = UIFont.systemFont(ofSize: 22)
-        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
-
-        initialsLabel.backgroundColor = UIColor.green
-
-        initialsLabel.layer.cornerRadius = initialsLabelCornerRadius
-        initialsLabel.clipsToBounds = 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 = 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
-
-        let myStackView = UIStackView()
-        myStackView.translatesAutoresizingMaskIntoConstraints = false
-        myStackView.clipsToBounds = true
-
-        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(nameLabel)
-        myStackView.addArrangedSubview(emailLabel)
-
-        nameLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
-        nameLabel.lineBreakMode = .byTruncatingTail
-        nameLabel.textColor = UIColor(hexString: "2f3944")
-
-        emailLabel.font = UIFont.systemFont(ofSize: 14)
-        emailLabel.textColor = UIColor(hexString: "848ba7")
-        emailLabel.lineBreakMode = .byTruncatingTail
-
-        let img = UIImage(named: "approval")!.withRenderingMode(.alwaysTemplate)
-        let imgSize: CGFloat = 25
-
-        imgView.isHidden = true
-        imgView.image = img
-        imgView.bounds = CGRect(
-            x: 0,
-            y: 0,
-            width: imgSize, height: imgSize
-        )
-        imgView.tintColor = Constants.primaryColor
-
-        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 setColor(_ color: UIColor) {
-        initialsLabel.backgroundColor = color
-    }
-
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
+  let avatar = UIView()
+  let imgView = UIImageView()
+  let initialsLabel = UILabel()
+  let nameLabel = UILabel()
+  let emailLabel = UILabel()
+
+  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)
+
+    // configure and layout initialsLabel
+    let initialsLabelSize: CGFloat = 54
+    let initialsLabelCornerRadius = (initialsLabelSize - 6) / 2
+    let margin: CGFloat = 15
+
+    initialsLabel.textAlignment = NSTextAlignment.center
+    initialsLabel.textColor = UIColor.white
+    initialsLabel.font = UIFont.systemFont(ofSize: 22)
+    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
+
+    initialsLabel.backgroundColor = UIColor.green
+
+    initialsLabel.layer.cornerRadius = initialsLabelCornerRadius
+    initialsLabel.clipsToBounds = 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
+
+    let myStackView = UIStackView()
+    myStackView.translatesAutoresizingMaskIntoConstraints = false
+    myStackView.clipsToBounds = true
+
+    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(nameLabel)
+    myStackView.addArrangedSubview(emailLabel)
+
+    nameLabel.font = UIFont.systemFont(ofSize: 16, weight: .medium)
+    nameLabel.lineBreakMode = .byTruncatingTail
+    nameLabel.textColor = UIColor(hexString: "2f3944")
+
+    emailLabel.font = UIFont.systemFont(ofSize: 14)
+    emailLabel.textColor = UIColor(hexString: "848ba7")
+    emailLabel.lineBreakMode = .byTruncatingTail
+
+    let img = UIImage(named: "approval")!.withRenderingMode(.alwaysTemplate)
+    let imgSize: CGFloat = 25
+
+    imgView.isHidden = true
+    imgView.image = img
+    imgView.bounds = CGRect(
+      x: 0,
+      y: 0,
+      width: imgSize, height: imgSize
+    )
+    imgView.tintColor = Constants.primaryColor
+
+    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 setColor(_ color: UIColor) {
+    initialsLabel.backgroundColor = color
+  }
+
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
 }

+ 94 - 94
deltachat-ios/ContactProfileViewController.swift

@@ -9,120 +9,120 @@
 import UIKit
 
 class ContactProfileViewController: UITableViewController {
-    let contactId: Int
+  let contactId: Int
 
-    var contact: MRContact {
-        return MRContact(id: contactId)
-    }
-
-    init(contactId: Int) {
-        self.contactId = contactId
-        super.init(style: .plain)
-    }
+  var contact: MRContact {
+    return MRContact(id: contactId)
+  }
 
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
+  init(contactId: Int) {
+    self.contactId = contactId
+    super.init(style: .plain)
+  }
 
-    override func viewWillAppear(_: Bool) {
-        navigationController?.navigationBar.prefersLargeTitles = false
-        tableView.reloadData()
-    }
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
 
-    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 1
-    }
+  // MARK: - Table view data source
 
-    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-        if section == 0 {
-            return 3
-        }
+  override func numberOfSections(in _: UITableView) -> Int {
+    return 1
+  }
 
-        return 0
+  override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+    if section == 0 {
+      return 3
     }
 
-    override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let row = indexPath.row
+    return 0
+  }
 
-        let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
+  override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    let row = indexPath.row
 
-        let settingsImage = #imageLiteral(resourceName: "baseline_settings_black_18pt").withRenderingMode(.alwaysTemplate)
-        cell.imageView?.image = settingsImage
-        cell.imageView?.tintColor = UIColor.clear
+    let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
 
-        if row == 0 {
-            cell.textLabel?.text = "Settings"
-            cell.imageView?.tintColor = UIColor.gray
-        }
-        if row == 1 {
-            cell.textLabel?.text = "Edit name"
-        }
+    let settingsImage = #imageLiteral(resourceName: "baseline_settings_black_18pt").withRenderingMode(.alwaysTemplate)
+    cell.imageView?.image = settingsImage
+    cell.imageView?.tintColor = UIColor.clear
 
-        if row == 2 {
-            cell.textLabel?.text = "New chat"
-        }
-        return cell
+    if row == 0 {
+      cell.textLabel?.text = "Settings"
+      cell.imageView?.tintColor = UIColor.gray
     }
-
-    override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let row = indexPath.row
-
-        if row == 1 {
-            let alert = UIAlertController(title: "Not implemented", message: "Settings are not implemented yet.", preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
-            present(alert, animated: true, completion: nil)
-        }
-        if row == 2 {
-            let newContactController = NewContactController(contactIdForUpdate: contactId)
-            navigationController?.pushViewController(newContactController, animated: true)
-        }
-        if row == 3 {
-            displayNewChat(contactId: contactId)
-        }
+    if row == 1 {
+      cell.textLabel?.text = "Edit name"
     }
 
-    override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
-        return 80
+    if row == 2 {
+      cell.textLabel?.text = "New chat"
     }
+    return cell
+  }
+
+  override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+    let row = indexPath.row
 
-    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()
-            contactCell.backgroundColor = bg
-            contactCell.nameLabel.text = contact.name
-            contactCell.emailLabel.text = contact.email
-            contactCell.darkMode = false
-            contactCell.selectionStyle = .none
-            if let img = contact.profileImage {
-                contactCell.setImage(img)
-            } else {
-                contactCell.setBackupImage(name: contact.name, color: contact.color)
-            }
-
-            contactCell.setVerified(isVerified: contact.isVerified)
-
-            return contactCell
-        }
-
-        let vw = UIView()
-        vw.backgroundColor = bg
-
-        return vw
+    if row == 1 {
+      let alert = UIAlertController(title: "Not implemented", message: "Settings are not implemented yet.", preferredStyle: .alert)
+      alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
+      present(alert, animated: true, completion: nil)
+    }
+    if row == 2 {
+      let newContactController = NewContactController(contactIdForUpdate: contactId)
+      navigationController?.pushViewController(newContactController, animated: true)
+    }
+    if row == 3 {
+      displayNewChat(contactId: contactId)
     }
+  }
+
+  override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
+    return 80
+  }
+
+  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()
+      contactCell.backgroundColor = bg
+      contactCell.nameLabel.text = contact.name
+      contactCell.emailLabel.text = contact.email
+      contactCell.darkMode = false
+      contactCell.selectionStyle = .none
+      if let img = contact.profileImage {
+        contactCell.setImage(img)
+      } else {
+        contactCell.setBackupImage(name: contact.name, color: contact.color)
+      }
+
+      contactCell.setVerified(isVerified: contact.isVerified)
+
+      return contactCell
+    }
+
+    let vw = UIView()
+    vw.backgroundColor = bg
+
+    return vw
+  }
 }

+ 27 - 27
deltachat-ios/CustomCell.swift

@@ -9,37 +9,37 @@ import MessageKit
 import UIKit
 
 open class CustomCell: UICollectionViewCell {
-    let label = UILabel()
+  let label = UILabel()
 
-    public override init(frame: CGRect) {
-        super.init(frame: frame)
-        setupSubviews()
-    }
+  public override init(frame: CGRect) {
+    super.init(frame: frame)
+    setupSubviews()
+  }
 
-    public required init?(coder aDecoder: NSCoder) {
-        super.init(coder: aDecoder)
-        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 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 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
-        }
+  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
     }
+  }
 }

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

@@ -9,26 +9,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)
+  }
 }

+ 33 - 33
deltachat-ios/Extensions/UIViewController+Extension.swift

@@ -9,40 +9,40 @@
 import UIKit
 
 extension UIViewController {
-    func updateTitleView(title: String, subtitle: String?, baseColor: UIColor = .darkText) {
-        let titleLabel = UILabel(frame: CGRect(x: 0, y: -2, width: 0, height: 0))
-        titleLabel.backgroundColor = UIColor.clear
-        titleLabel.textColor = baseColor
-        titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
-        titleLabel.text = title
-        titleLabel.textAlignment = .center
-        titleLabel.adjustsFontSizeToFitWidth = true
-        titleLabel.sizeToFit()
+  func updateTitleView(title: String, subtitle: String?, baseColor: UIColor = .darkText) {
+    let titleLabel = UILabel(frame: CGRect(x: 0, y: -2, width: 0, height: 0))
+    titleLabel.backgroundColor = UIColor.clear
+    titleLabel.textColor = baseColor
+    titleLabel.font = UIFont.systemFont(ofSize: 16, weight: .semibold)
+    titleLabel.text = title
+    titleLabel.textAlignment = .center
+    titleLabel.adjustsFontSizeToFitWidth = true
+    titleLabel.sizeToFit()
 
-        let subtitleLabel = UILabel(frame: CGRect(x: 0, y: 18, width: 0, height: 0))
-        subtitleLabel.textColor = baseColor.withAlphaComponent(0.95)
-        subtitleLabel.font = UIFont.systemFont(ofSize: 12)
-        subtitleLabel.text = subtitle
-        subtitleLabel.textAlignment = .center
-        subtitleLabel.adjustsFontSizeToFitWidth = true
-        subtitleLabel.sizeToFit()
+    let subtitleLabel = UILabel(frame: CGRect(x: 0, y: 18, width: 0, height: 0))
+    subtitleLabel.textColor = baseColor.withAlphaComponent(0.95)
+    subtitleLabel.font = UIFont.systemFont(ofSize: 12)
+    subtitleLabel.text = subtitle
+    subtitleLabel.textAlignment = .center
+    subtitleLabel.adjustsFontSizeToFitWidth = true
+    subtitleLabel.sizeToFit()
 
-        let titleView = UIView(frame: CGRect(x: 0, y: 0, width: max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), height: 30))
-        titleView.addSubview(titleLabel)
-        if subtitle != nil {
-            titleView.addSubview(subtitleLabel)
-        } else {
-            titleLabel.frame = titleView.frame
-        }
-        let widthDiff = subtitleLabel.frame.size.width - titleLabel.frame.size.width
-        if widthDiff < 0 {
-            let newX = widthDiff / 2
-            subtitleLabel.frame.origin.x = abs(newX)
-        } else {
-            let newX = widthDiff / 2
-            titleLabel.frame.origin.x = newX
-        }
-
-        navigationItem.titleView = titleView
+    let titleView = UIView(frame: CGRect(x: 0, y: 0, width: max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), height: 30))
+    titleView.addSubview(titleLabel)
+    if subtitle != nil {
+      titleView.addSubview(subtitleLabel)
+    } else {
+      titleLabel.frame = titleView.frame
+    }
+    let widthDiff = subtitleLabel.frame.size.width - titleLabel.frame.size.width
+    if widthDiff < 0 {
+      let newX = widthDiff / 2
+      subtitleLabel.frame.origin.x = abs(newX)
+    } else {
+      let newX = widthDiff / 2
+      titleLabel.frame.origin.x = newX
     }
+
+    navigationItem.titleView = titleView
+  }
 }

+ 66 - 66
deltachat-ios/GroupNameController.swift

@@ -9,83 +9,83 @@
 import UIKit
 
 class GroupNameController: UIViewController {
-    var doneButton: UIBarButtonItem!
-    let groupNameTextField = UITextField()
-    let contactIdsForGroup: Set<Int>
-    var groupName = "" {
-        didSet {
-            if groupName.isEmpty {
-                logger.info("empty")
-                doneButton.isEnabled = false
-            } else {
-                logger.info("something")
-                doneButton.isEnabled = true
-            }
-        }
+  var doneButton: UIBarButtonItem!
+  let groupNameTextField = UITextField()
+  let contactIdsForGroup: Set<Int>
+  var groupName = "" {
+    didSet {
+      if groupName.isEmpty {
+        logger.info("empty")
+        doneButton.isEnabled = false
+      } else {
+        logger.info("something")
+        doneButton.isEnabled = true
+      }
     }
+  }
 
-    init(contactIdsForGroup: Set<Int>) {
-        self.contactIdsForGroup = contactIdsForGroup
-        super.init(nibName: nil, bundle: nil)
-    }
+  init(contactIdsForGroup: Set<Int>) {
+    self.contactIdsForGroup = contactIdsForGroup
+    super.init(nibName: nil, bundle: nil)
+  }
 
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
 
-    func layoutTextField() {
-        groupNameTextField.translatesAutoresizingMaskIntoConstraints = false
-        view.addSubview(groupNameTextField)
-        groupNameTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
-        groupNameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
-        groupNameTextField.placeholder = "Group Name"
-        groupNameTextField.becomeFirstResponder()
-    }
+  func layoutTextField() {
+    groupNameTextField.translatesAutoresizingMaskIntoConstraints = false
+    view.addSubview(groupNameTextField)
+    groupNameTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
+    groupNameTextField.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20).isActive = true
+    groupNameTextField.placeholder = "Group Name"
+    groupNameTextField.becomeFirstResponder()
+  }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        title = "Group Name"
-        groupNameTextField.delegate = self
-        layoutTextField()
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    title = "Group Name"
+    groupNameTextField.delegate = self
+    layoutTextField()
 
-        doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(didPressDoneButton))
-        navigationItem.rightBarButtonItem = doneButton
-        doneButton.isEnabled = false
-    }
+    doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(didPressDoneButton))
+    navigationItem.rightBarButtonItem = doneButton
+    doneButton.isEnabled = false
+  }
 
-    @objc func didPressDoneButton() {
-        logger.info("Done Button pressed")
-        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)")
-            }
-        }
-        groupNameTextField.resignFirstResponder()
-        let root = navigationController?.presentingViewController
-        navigationController?.dismiss(animated: true) {
-            let chatVC = ChatViewController(chatId: Int(groupChatId))
-            if let navigationRoot = root as? UINavigationController {
-                navigationRoot.pushViewController(chatVC, animated: true)
-            }
-        }
+  @objc func didPressDoneButton() {
+    logger.info("Done Button pressed")
+    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)")
+      }
     }
-
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
+    groupNameTextField.resignFirstResponder()
+    let root = navigationController?.presentingViewController
+    navigationController?.dismiss(animated: true) {
+      let chatVC = ChatViewController(chatId: Int(groupChatId))
+      if let navigationRoot = root as? UINavigationController {
+        navigationRoot.pushViewController(chatVC, animated: true)
+      }
     }
+  }
+
+  override func didReceiveMemoryWarning() {
+    super.didReceiveMemoryWarning()
+    // Dispose of any resources that can be recreated.
+  }
 }
 
 extension GroupNameController: UITextFieldDelegate {
-    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
-        let text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
-        groupName = text
+  func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
+    let text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
+    groupName = text
 
-        return true
-    }
+    return true
+  }
 }

+ 58 - 58
deltachat-ios/Message.swift

@@ -11,75 +11,75 @@ 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
+  }
 }
 
 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
+  }
 }
 
 struct Message: MessageType {
-    var messageId: String
-    var sender: Sender
-    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: Sender
+  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)
+  }
 }

+ 91 - 91
deltachat-ios/MessageInfoViewController.swift

@@ -9,99 +9,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
+  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")
     }
 
-    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
+    if indexPath.section == 0 {
+      if indexPath.row == 0 {
+        cell.textLabel?.text = message.text
+      }
     }
 
-    /*
-     // 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.
-     }
-     */
+    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.
+   }
+   */
 }

+ 39 - 39
deltachat-ios/NavigationController.swift

@@ -9,49 +9,49 @@
 import UIKit
 
 final class NavigationController: UINavigationController {
-    var stateChangedObserver: Any?
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-
-        if #available(iOS 11.0, *) {
-            navigationBar.prefersLargeTitles = true
-        } else {
-            navigationBar.setBackgroundImage(UIImage(), for: .default)
-        }
-
-        setShadow(nil)
-
-        let nc = NotificationCenter.default
-        stateChangedObserver = nc.addObserver(
-            forName: dc_notificationStateChanged,
-            object: nil,
-            queue: nil
-        ) {
-            notification in
-            if let state = notification.userInfo?["state"] {
-                self.setShadow(state as? String)
-            }
-        }
+  var stateChangedObserver: Any?
+
+  override func viewDidLoad() {
+    super.viewDidLoad()
+
+    if #available(iOS 11.0, *) {
+      navigationBar.prefersLargeTitles = true
+    } else {
+      navigationBar.setBackgroundImage(UIImage(), for: .default)
     }
 
-    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)
+
+    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)
+      }
+    }
+  }
+
+  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 viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
+  override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
 
-        let nc = NotificationCenter.default
-        if let stateChangedObserver = self.stateChangedObserver {
-            nc.removeObserver(stateChangedObserver)
-        }
+    let nc = NotificationCenter.default
+    if let stateChangedObserver = self.stateChangedObserver {
+      nc.removeObserver(stateChangedObserver)
     }
+  }
 }

+ 179 - 179
deltachat-ios/NewChatViewController.swift

@@ -10,215 +10,215 @@ import ALCameraViewController
 import UIKit
 
 protocol ChatDisplayer: class {
-    func displayNewChat(contactId: Int)
-    func displayChatForId(chatId: Int)
+  func displayNewChat(contactId: Int)
+  func displayChatForId(chatId: Int)
 }
 
 class NewChatViewController: UITableViewController {
-    var contactIds: [Int] = Utils.getContactIds()
-    weak var chatDisplayer: ChatDisplayer?
+  var contactIds: [Int] = Utils.getContactIds()
+  weak var chatDisplayer: ChatDisplayer?
 
-    var syncObserver: Any?
-    var hud: ProgressHud?
+  var syncObserver: Any?
+  var hud: ProgressHud?
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
+  override func viewDidLoad() {
+    super.viewDidLoad()
 
-        title = "New Chat"
-        navigationController?.navigationBar.prefersLargeTitles = true
+    title = "New Chat"
+    navigationController?.navigationBar.prefersLargeTitles = true
 
-        let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(NewChatViewController.cancelButtonPressed))
+    let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(NewChatViewController.cancelButtonPressed))
 
-        navigationItem.rightBarButtonItem = cancelButton
-    }
+    navigationItem.rightBarButtonItem = cancelButton
+  }
 
-    override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-
-        contactIds = Utils.getContactIds()
-        tableView.reloadData()
-
-        let nc = NotificationCenter.default
-        syncObserver = nc.addObserver(
-            forName: dc_notificationSecureJoinerProgress,
-            object: nil,
-            queue: nil
-        ) {
-            notification in
-            if let ui = notification.userInfo {
-                if ui["error"] as! Bool {
-                    self.hud?.error(ui["errorMessage"] as? String)
-                } else if ui["done"] as! Bool {
-                    self.hud?.done()
-                } else {
-                    self.hud?.progress(ui["progress"] as! Int)
-                }
-            }
-        }
-    }
+  override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
 
-    override func viewDidDisappear(_ animated: Bool) {
-        super.viewDidDisappear(animated)
+    contactIds = Utils.getContactIds()
+    tableView.reloadData()
 
-        let nc = NotificationCenter.default
-        if let syncObserver = self.syncObserver {
-            nc.removeObserver(syncObserver)
+    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)
         }
+      }
     }
+  }
 
-    @objc func cancelButtonPressed() {
-        dismiss(animated: true, completion: nil)
-    }
+  override func viewDidDisappear(_ animated: Bool) {
+    super.viewDidDisappear(animated)
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
+    let nc = NotificationCenter.default
+    if let syncObserver = self.syncObserver {
+      nc.removeObserver(syncObserver)
     }
-
-    // MARK: - Table view data source
-
-    override func numberOfSections(in _: UITableView) -> Int {
-        return 1
+  }
+
+  @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 1
+  }
+
+  override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+    return contactIds.count + 3
+  }
+
+  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    let row = indexPath.row
+    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
     }
-
-    override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-        return contactIds.count + 3
+    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
     }
 
-    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let row = indexPath.row
-        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
+    }
 
-        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
-        }
+    let cell: ContactCell
+    if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
+      cell = c
+    } else {
+      cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
+    }
 
-        let cell: ContactCell
-        if let c = tableView.dequeueReusableCell(withIdentifier: "contactCell") as? ContactCell {
-            cell = c
-        } else {
-            cell = ContactCell(style: .default, reuseIdentifier: "contactCell")
-        }
+    let contactRow = row - 3
 
-        let contactRow = row - 3
+    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.setColor(contact.color)
 
-        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.setColor(contact.color)
+    cell.accessoryType = .detailDisclosureButton
+    return cell
+  }
 
-        cell.accessoryType = .detailDisclosureButton
-        return cell
+  override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+    let row = indexPath.row
+    if row == 0 {
+      let newGroupController = NewGroupViewController()
+      navigationController?.pushViewController(newGroupController, animated: true)
     }
-
-    override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let row = indexPath.row
-        if row == 0 {
-            let newGroupController = NewGroupViewController()
-            navigationController?.pushViewController(newGroupController, animated: true)
-        }
-        if row == 1 {
-            if UIImagePickerController.isSourceTypeAvailable(.camera) {
-                let controller = QrCodeReaderController()
-                controller.delegate = self
-                present(controller, animated: true, completion: nil)
-
-            } else {
-                let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
-                alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-                    self.dismiss(animated: true, completion: nil)
-                }))
-                present(alert, animated: true, completion: nil)
-            }
-        }
-        if row == 2 {
-            let newContactController = NewContactController()
-            navigationController?.pushViewController(newContactController, animated: true)
-        }
-        if row > 2 {
-            let contactIndex = row - 3
-            let contactId = contactIds[contactIndex]
-            dismiss(animated: false) {
-                self.chatDisplayer?.displayNewChat(contactId: contactId)
-            }
-        }
+    if row == 1 {
+      if UIImagePickerController.isSourceTypeAvailable(.camera) {
+        let controller = QrCodeReaderController()
+        controller.delegate = self
+        present(controller, animated: true, completion: nil)
+
+      } else {
+        let alert = UIAlertController(title: "Camera is not available", message: nil, preferredStyle: .alert)
+        alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+          self.dismiss(animated: true, completion: nil)
+        }))
+        present(alert, animated: true, completion: nil)
+      }
     }
-
-    override func tableView(_: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
-        let row = indexPath.row
-        if row > 2 {
-            let contactIndex = row - 3
-            let contactId = contactIds[contactIndex]
-            // let newContactController = NewContactController(contactIdForUpdate: contactId)
-            // navigationController?.pushViewController(newContactController, animated: true)
-            let contactProfileController = ContactProfileViewController(contactId: contactId)
-            navigationController?.pushViewController(contactProfileController, animated: true)
-        }
+    if row == 2 {
+      let newContactController = NewContactController()
+      navigationController?.pushViewController(newContactController, animated: true)
+    }
+    if row > 2 {
+      let contactIndex = row - 3
+      let contactId = contactIds[contactIndex]
+      dismiss(animated: false) {
+        self.chatDisplayer?.displayNewChat(contactId: contactId)
+      }
     }
+  }
+
+  override func tableView(_: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
+    let row = indexPath.row
+    if row > 2 {
+      let contactIndex = row - 3
+      let contactId = contactIds[contactIndex]
+      // let newContactController = NewContactController(contactIdForUpdate: contactId)
+      // navigationController?.pushViewController(newContactController, animated: true)
+      let contactProfileController = ContactProfileViewController(contactId: contactId)
+      navigationController?.pushViewController(contactProfileController, animated: true)
+    }
+  }
 }
 
 extension NewChatViewController: QrCodeReaderDelegate {
-    func handleQrCode(_ code: String) {
-        logger.info("decoded: \(code)")
-
-        let check = dc_check_qr(mailboxPointer, code)!
-        logger.info("got ver: \(check)")
-
-        if dc_lot_get_state(check) == DC_QR_ASK_VERIFYGROUP {
-            hud = ProgressHud("Synchronizing Account", in: view)
-            DispatchQueue.global(qos: .userInitiated).async {
-                let id = dc_join_securejoin(mailboxPointer, code)
-
-                DispatchQueue.main.async {
-                    self.dismiss(animated: true) {
-                        self.chatDisplayer?.displayChatForId(chatId: Int(id))
-                    }
-                }
-            }
-        } else {
-            let alert = UIAlertController(title: "Not a valid group QR Code", message: code, preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-                self.dismiss(animated: true, completion: nil)
-            }))
-            present(alert, animated: true, completion: nil)
-        }
+  func handleQrCode(_ code: String) {
+    logger.info("decoded: \(code)")
+
+    let check = dc_check_qr(mailboxPointer, code)!
+    logger.info("got ver: \(check)")
 
-        dc_lot_unref(check)
+    if dc_lot_get_state(check) == DC_QR_ASK_VERIFYGROUP {
+      hud = ProgressHud("Synchronizing Account", in: view)
+      DispatchQueue.global(qos: .userInitiated).async {
+        let id = dc_join_securejoin(mailboxPointer, code)
+
+        DispatchQueue.main.async {
+          self.dismiss(animated: true) {
+            self.chatDisplayer?.displayChatForId(chatId: Int(id))
+          }
+        }
+      }
+    } else {
+      let alert = UIAlertController(title: "Not a valid group QR Code", message: code, preferredStyle: .alert)
+      alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+        self.dismiss(animated: true, completion: nil)
+      }))
+      present(alert, animated: true, completion: nil)
     }
+
+    dc_lot_unref(check)
+  }
 }

+ 111 - 111
deltachat-ios/NewContactController.swift

@@ -9,144 +9,144 @@
 import UIKit
 
 class NewContactController: UITableViewController {
-    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 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]
+  let cells: [UITableViewCell]
 
-    // for editing existing contacts (only
-    // the name may be edited, therefore disable
-    // the email field)
-    convenience init(contactIdForUpdate: Int) {
-        self.init()
-        title = "Edit Contact"
+  // for editing existing contacts (only
+  // the name may be edited, therefore disable
+  // the email field)
+  convenience init(contactIdForUpdate: Int) {
+    self.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
+    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
+    model.name = contact.name
+    model.email = contact.email
 
-        if contactIsValid() {
-            doneButton?.isEnabled = true
-        }
+    if contactIsValid() {
+      doneButton?.isEnabled = true
     }
+  }
 
-    override func viewWillAppear(_: Bool) {
-        navigationController?.setNavigationBarHidden(false, animated: false)
-    }
+  override func viewWillAppear(_: Bool) {
+    navigationController?.setNavigationBarHidden(false, animated: false)
+  }
 
-    // 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(NewContactController.saveContactButtonPressed))
-        doneButton?.isEnabled = false
-        navigationItem.rightBarButtonItem = doneButton
+  // for creating a new contact
+  init() {
+    cells = [emailCell, nameCell]
+    super.init(style: .grouped)
+    emailCell.textField.delegate = self
+    nameCell.textField.delegate = self
 
-        cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(NewContactController.cancelButtonPressed))
-        navigationItem.leftBarButtonItem = cancelButton
+    // always show return key with name field, because
+    // name is optional
+    nameCell.textField.enablesReturnKeyAutomatically = false
+    emailCell.textField.returnKeyType = .next
+    nameCell.textField.returnKeyType = .done
 
-        emailCell.textField.addTarget(self, action: #selector(NewContactController.emailTextChanged), for: UIControl.Event.editingChanged)
-        nameCell.textField.addTarget(self, action: #selector(NewContactController.nameTextChanged), for: UIControl.Event.editingChanged)
-    }
+    title = "New Contact"
+    doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(NewContactController.saveContactButtonPressed))
+    doneButton?.isEnabled = false
+    navigationItem.rightBarButtonItem = doneButton
 
-    override func viewDidAppear(_: Bool) {
-        if emailCell.textField.isEnabled {
-            emailCell.textField.becomeFirstResponder()
-        } else {
-            nameCell.textField.becomeFirstResponder()
-        }
-    }
+    cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(NewContactController.cancelButtonPressed))
+    navigationItem.leftBarButtonItem = cancelButton
 
-    @objc func emailTextChanged() {
-        let emailText = emailCell.textField.text ?? ""
+    emailCell.textField.addTarget(self, action: #selector(NewContactController.emailTextChanged), for: UIControl.Event.editingChanged)
+    nameCell.textField.addTarget(self, action: #selector(NewContactController.nameTextChanged), for: UIControl.Event.editingChanged)
+  }
 
-        model.email = emailText
+  override func viewDidAppear(_: Bool) {
+    if emailCell.textField.isEnabled {
+      emailCell.textField.becomeFirstResponder()
+    } else {
+      nameCell.textField.becomeFirstResponder()
     }
+  }
 
-    @objc func nameTextChanged() {
-        let nameText = nameCell.textField.text ?? ""
+  @objc func emailTextChanged() {
+    let emailText = emailCell.textField.text ?? ""
 
-        model.name = nameText
-    }
+    model.email = emailText
+  }
 
-    @objc func saveContactButtonPressed() {
-        dc_create_contact(mailboxPointer, model.name, model.email)
-        navigationController?.popViewController(animated: true)
-    }
+  @objc func nameTextChanged() {
+    let nameText = nameCell.textField.text ?? ""
 
-    @objc func cancelButtonPressed() {
-        navigationController?.popViewController(animated: true)
-    }
+    model.name = nameText
+  }
 
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
+  @objc func saveContactButtonPressed() {
+    dc_create_contact(mailboxPointer, model.name, model.email)
+    navigationController?.popViewController(animated: true)
+  }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
+  @objc func cancelButtonPressed() {
+    navigationController?.popViewController(animated: true)
+  }
 
-        navigationController?.navigationBar.prefersLargeTitles = true
-    }
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
 
-    override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-        return cells.count
-    }
+  override func viewDidLoad() {
+    super.viewDidLoad()
 
-    override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let row = indexPath.row
+    navigationController?.navigationBar.prefersLargeTitles = true
+  }
 
-        return cells[row]
-    }
+  override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+    return cells.count
+  }
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
-    }
+  override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    let row = indexPath.row
+
+    return cells[row]
+  }
+
+  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()
-            }
-        }
-        return true
-    }
+  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
+  }
 }

+ 63 - 63
deltachat-ios/NewGroupMemberChoiceController.swift

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

+ 54 - 54
deltachat-ios/NewGroupViewController.swift

@@ -9,70 +9,70 @@
 import UIKit
 
 class NewGroupViewController: UITableViewController {
-    let contactCellReuseIdentifier = "xyz"
-    var contactIds: [Int] = Utils.getContactIds()
-    var contactIdsForGroup: Set<Int> = [] {
-        didSet {
-            let c = contactIdsForGroup.count
-            navigationItem.prompt = "\(c) members and me"
-        }
+  let contactCellReuseIdentifier = "xyz"
+  var contactIds: [Int] = Utils.getContactIds()
+  var contactIdsForGroup: Set<Int> = [] {
+    didSet {
+      let c = contactIdsForGroup.count
+      navigationItem.prompt = "\(c) members and me"
     }
+  }
 
-    @objc func didPressGroupCreationNextButton() {
-        navigationController?.pushViewController(GroupNameController(contactIdsForGroup: contactIdsForGroup), animated: true)
-    }
+  @objc func didPressGroupCreationNextButton() {
+    navigationController?.pushViewController(GroupNameController(contactIdsForGroup: contactIdsForGroup), animated: true)
+  }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        title = "New Group"
-        navigationItem.prompt = "0 members and me"
-        tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
-        navigationController?.navigationBar.prefersLargeTitles = false
-        let groupCreationNextButton = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(didPressGroupCreationNextButton))
-        navigationItem.rightBarButtonItem = groupCreationNextButton
-    }
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    title = "New Group"
+    navigationItem.prompt = "0 members and me"
+    tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
+    navigationController?.navigationBar.prefersLargeTitles = false
+    let groupCreationNextButton = UIBarButtonItem(title: "Next", style: .done, target: self, action: #selector(didPressGroupCreationNextButton))
+    navigationItem.rightBarButtonItem = groupCreationNextButton
+  }
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-    }
+  override func didReceiveMemoryWarning() {
+    super.didReceiveMemoryWarning()
+  }
 
-    override func numberOfSections(in _: UITableView) -> Int {
-        return 1
-    }
+  override func numberOfSections(in _: UITableView) -> Int {
+    return 1
+  }
 
-    override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-        return contactIds.count
-    }
+  override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+    return contactIds.count
+  }
 
-    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(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    guard let cell: ContactCell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as? ContactCell else {
+      fatalError("shouldn't happen")
+    }
 
-        let row = indexPath.row
-        let contactRow = row
+    let row = indexPath.row
+    let contactRow = row
 
-        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.setColor(contact.color)
+    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.setColor(contact.color)
 
-        return cell
-    }
+    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 contactIdsForGroup.contains(contactId) {
-                contactIdsForGroup.remove(contactId)
-                cell.accessoryType = .none
-            } else {
-                contactIdsForGroup.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 contactIdsForGroup.contains(contactId) {
+        contactIdsForGroup.remove(contactId)
+        cell.accessoryType = .none
+      } else {
+        contactIdsForGroup.insert(contactId)
+        cell.accessoryType = .checkmark
+      }
     }
+  }
 }

+ 40 - 40
deltachat-ios/ProgressHud.swift

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

+ 64 - 64
deltachat-ios/QrCodeReaderController.swift

@@ -10,94 +10,94 @@ import AVFoundation
 import UIKit
 
 protocol QrCodeReaderDelegate: class {
-    func handleQrCode(_ code: String)
+  func handleQrCode(_ code: String)
 }
 
 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.count == 0 {
-            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!)
+          }
         }
+      }
     }
+  }
 }

+ 35 - 35
deltachat-ios/QrCodeView.swift

@@ -8,47 +8,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)
+  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
     }
 
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
+    filter.setValue(data, forKey: "inputMessage")
 
-    override func layoutSubviews() {
-        super.layoutSubviews()
-        imageView.frame = bounds
+    guard let ciImage = filter.outputImage else {
+      return
     }
 
-    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 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)
     }
+  }
 }

+ 23 - 23
deltachat-ios/TableViewCell.swift

@@ -9,37 +9,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")
+  }
 }

+ 59 - 59
deltachat-ios/TextFieldCell.swift

@@ -9,85 +9,85 @@
 import UIKit
 
 class TextFieldCell: UITableViewCell {
-    let textField = UITextField()
+  let textField = UITextField()
 
-    init(description: String, placeholder: String) {
-        super.init(style: .value1, reuseIdentifier: nil)
+  init(description: String, placeholder: String) {
+    super.init(style: .value1, reuseIdentifier: nil)
 
-        textLabel?.text = "\(description):"
-        contentView.addSubview(textField)
+    textLabel?.text = "\(description):"
+    contentView.addSubview(textField)
 
-        textField.translatesAutoresizingMaskIntoConstraints = false
+    textField.translatesAutoresizingMaskIntoConstraints = false
 
-        // see: https://stackoverflow.com/a/35903650
-        // this makes the textField respect the trailing margin of
-        // the table view cell
-        let margins = contentView.layoutMarginsGuide
-        let trailing = margins.trailingAnchor
-        textField.trailingAnchor.constraint(equalTo: trailing).isActive = true
-        textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
-        textField.textAlignment = .right
+    // see: https://stackoverflow.com/a/35903650
+    // this makes the textField respect the trailing margin of
+    // the table view cell
+    let margins = contentView.layoutMarginsGuide
+    let trailing = margins.trailingAnchor
+    textField.trailingAnchor.constraint(equalTo: trailing).isActive = true
+    textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
+    textField.textAlignment = .right
 
-        textField.placeholder = placeholder
+    textField.placeholder = placeholder
 
-        selectionStyle = .none
+    selectionStyle = .none
 
-        textField.enablesReturnKeyAutomatically = true
-    }
+    textField.enablesReturnKeyAutomatically = true
+  }
 
-    override func setSelected(_ selected: Bool, animated _: Bool) {
-        if selected {
-            textField.becomeFirstResponder()
-        }
+  override func setSelected(_ selected: Bool, animated _: Bool) {
+    if selected {
+      textField.becomeFirstResponder()
     }
+  }
 
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
 
-    static func makeEmailCell() -> TextFieldCell {
-        let emailCell = TextFieldCell(description: "Email", placeholder: "you@example.com")
+  static func makeEmailCell() -> TextFieldCell {
+    let emailCell = TextFieldCell(description: "Email", placeholder: "you@example.com")
 
-        emailCell.textField.keyboardType = .emailAddress
-        // switch off quicktype
-        emailCell.textField.autocorrectionType = .no
-        emailCell.textField.autocapitalizationType = .none
+    emailCell.textField.keyboardType = .emailAddress
+    // switch off quicktype
+    emailCell.textField.autocorrectionType = .no
+    emailCell.textField.autocapitalizationType = .none
 
-        return emailCell
-    }
+    return emailCell
+  }
 
-    static func makePasswordCell() -> TextFieldCell {
-        let passwordCell = TextFieldCell(description: "Password", placeholder: "your IMAP password")
+  static func makePasswordCell() -> TextFieldCell {
+    let passwordCell = TextFieldCell(description: "Password", placeholder: "your IMAP password")
 
-        passwordCell.textField.textContentType = UITextContentType.password
-        passwordCell.textField.isSecureTextEntry = true
+    passwordCell.textField.textContentType = UITextContentType.password
+    passwordCell.textField.isSecureTextEntry = true
 
-        return passwordCell
-    }
+    return passwordCell
+  }
 
-    static func makeNameCell() -> TextFieldCell {
-        let nameCell = TextFieldCell(description: "Name", placeholder: "new contacts nickname")
+  static func makeNameCell() -> TextFieldCell {
+    let nameCell = TextFieldCell(description: "Name", placeholder: "new contacts nickname")
 
-        nameCell.textField.autocapitalizationType = .words
-        nameCell.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
-        nameCell.textField.keyboardType = .default
+    nameCell.textField.autocapitalizationType = .words
+    nameCell.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
+    nameCell.textField.keyboardType = .default
 
-        return nameCell
-    }
+    return nameCell
+  }
 
-    static func makeConfigCell(label: String, placeholder: String) -> TextFieldCell {
-        let nameCell = TextFieldCell(description: label, placeholder: placeholder)
+  static func makeConfigCell(label: String, placeholder: String) -> TextFieldCell {
+    let nameCell = TextFieldCell(description: label, placeholder: placeholder)
 
-        nameCell.textField.autocapitalizationType = .words
-        nameCell.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
-        nameCell.textField.keyboardType = .default
+    nameCell.textField.autocapitalizationType = .words
+    nameCell.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
+    nameCell.textField.keyboardType = .default
 
-        return nameCell
-    }
+    return nameCell
+  }
 }

+ 155 - 155
deltachat-ios/TopViews/ChatListController.swift

@@ -9,203 +9,203 @@
 import UIKit
 
 class ChatListController: UIViewController {
-    var chatList: MRChatList?
+  var chatList: MRChatList?
 
-    let chatTable = UITableView()
+  let chatTable = UITableView()
 
-    let chatTableDataSource = ChatTableDataSource()
-    let chatTableDelegate = ChatTableDelegate()
+  let chatTableDataSource = ChatTableDataSource()
+  let chatTableDelegate = ChatTableDelegate()
 
-    var msgChangedObserver: Any?
-    var incomingMsgObserver: Any?
-    var viewChatObserver: Any?
+  var msgChangedObserver: Any?
+  var incomingMsgObserver: Any?
+  var viewChatObserver: Any?
 
-    var newButton: UIBarButtonItem!
+  var newButton: UIBarButtonItem!
 
-    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)
-
-        chatTableDataSource.chatList = chatList
-        chatTable.reloadData()
+  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)
 
-    override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
+    chatTableDataSource.chatList = chatList
+    chatTable.reloadData()
+  }
 
-        if #available(iOS 11.0, *) {
-            navigationController?.navigationBar.prefersLargeTitles = true
-        }
+  override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
 
-        getChatList()
+    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
-        }
-    }
+    getChatList()
+  }
 
-    override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        let nc = NotificationCenter.default
-        msgChangedObserver = nc.addObserver(forName: dc_notificationChanged,
-                                            object: nil, queue: nil) {
-            _ in
-            self.getChatList()
-        }
-
-        incomingMsgObserver = nc.addObserver(forName: dc_notificationIncoming,
-                                             object: nil, queue: nil) {
-            _ in
-            self.getChatList()
-        }
-
-        viewChatObserver = nc.addObserver(forName: dc_notificationViewChat, object: nil, queue: nil) {
-            notification in
-            if let chatId = notification.userInfo?["chat_id"] as? Int {
-                self.displayChatForId(chatId: chatId)
-            }
-        }
+  override func 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()
     }
 
-    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)
-        }
+    incomingMsgObserver = nc.addObserver(forName: dcNotificationIncoming,
+                                         object: nil, queue: nil) {
+      _ in
+      self.getChatList()
     }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        title = "Chats"
-        navigationController?.navigationBar.prefersLargeTitles = true
-        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
-        chatTable.dataSource = chatTableDataSource
-        chatTableDelegate.chatPresenter = self
-        chatTable.delegate = chatTableDelegate
-
-        chatTable.rowHeight = 80
-
-        let newImage = UIImage(named: "create_new")!
-        newButton = UIBarButtonItem(image: newImage, landscapeImagePhone: nil, style: .plain, target: self, action: #selector(didPressNewChat))
-
-        newButton.tintColor = Constants.primaryColor
-        navigationItem.rightBarButtonItem = newButton
+    viewChatObserver = nc.addObserver(forName: dcNotificationViewChat, object: nil, queue: nil) {
+      notification in
+      if let chatId = notification.userInfo?["chat_id"] as? Int {
+        self.displayChatForId(chatId: chatId)
+      }
     }
+  }
 
-    @objc func didPressNewChat() {
-        let ncv = NewChatViewController()
-        ncv.chatDisplayer = self
-        let nav = UINavigationController(rootViewController: ncv)
-        present(nav, animated: true, completion: nil)
+  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
+    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
+    chatTable.dataSource = chatTableDataSource
+    chatTableDelegate.chatPresenter = self
+    chatTable.delegate = chatTableDelegate
+
+    chatTable.rowHeight = 80
+
+    let newImage = UIImage(named: "create_new")!
+    newButton = UIBarButtonItem(image: newImage, landscapeImagePhone: nil, style: .plain, target: self, action: #selector(didPressNewChat))
+
+    newButton.tintColor = Constants.primaryColor
+    navigationItem.rightBarButtonItem = newButton
+  }
+
+  @objc func didPressNewChat() {
+    let ncv = NewChatViewController()
+    ncv.chatDisplayer = self
+    let nav = UINavigationController(rootViewController: ncv)
+    present(nav, animated: true, completion: nil)
+  }
 }
 
 extension ChatListController: ChatPresenter {
-    func displayChat(index: Int) {
-        guard let chatList = self.chatList else {
-            fatalError("chatList was nil in ChatPresenter extension")
-        }
+  func displayChat(index: Int) {
+    guard let chatList = self.chatList else {
+      fatalError("chatList was nil in ChatPresenter extension")
+    }
 
-        let chatId = chatList.getChatId(index: index)
-        let chatVC = ChatViewController(chatId: chatId)
+    let chatId = chatList.getChatId(index: index)
+    let chatVC = ChatViewController(chatId: chatId)
 
-        chatVC.hidesBottomBarWhenPushed = true
-        navigationController?.pushViewController(chatVC, animated: true)
-    }
+    chatVC.hidesBottomBarWhenPushed = true
+    navigationController?.pushViewController(chatVC, animated: true)
+  }
 }
 
 extension ChatListController: ChatDisplayer {
-    func displayNewChat(contactId: Int) {
-        let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
-        displayChatForId(chatId: Int(chatId))
-    }
+  func displayNewChat(contactId: Int) {
+    let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
+    displayChatForId(chatId: Int(chatId))
+  }
 
-    func displayChatForId(chatId: Int) {
-        let chatVC = ChatViewController(chatId: chatId)
+  func displayChatForId(chatId: Int) {
+    let chatVC = ChatViewController(chatId: chatId)
 
-        chatVC.hidesBottomBarWhenPushed = true
-        navigationController?.pushViewController(chatVC, animated: true)
-    }
+    chatVC.hidesBottomBarWhenPushed = true
+    navigationController?.pushViewController(chatVC, animated: true)
+  }
 }
 
 class ChatTableDataSource: NSObject, UITableViewDataSource {
-    weak var chatList: MRChatList?
+  weak var chatList: MRChatList?
+
+  func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+    guard let chatList = self.chatList else {
+      fatalError("chatList was nil in data source")
+    }
 
-    func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-        guard let chatList = self.chatList else {
-            fatalError("chatList was nil in data source")
-        }
+    return chatList.length
+  }
 
-        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")
     }
 
-    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
-        return cell
+    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
+    return cell
+  }
 }
 
 protocol ChatPresenter: class {
-    func displayChat(index: Int)
+  func displayChat(index: Int)
 }
 
 class ChatTableDelegate: NSObject, UITableViewDelegate {
-    weak var chatPresenter: ChatPresenter?
+  weak var chatPresenter: ChatPresenter?
 
-    func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let row = indexPath.row
-        chatPresenter?.displayChat(index: row)
-    }
+  func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+    let row = indexPath.row
+    chatPresenter?.displayChat(index: row)
+  }
 }

+ 69 - 69
deltachat-ios/TopViews/ContactListController.swift

@@ -9,84 +9,84 @@
 import UIKit
 
 class ContactListController: UITableViewController {
-    let contactCellReuseIdentifier = "xyz"
-    var contactIds: [Int] = Utils.getContactIds()
-    var contactIdsForGroup: Set<Int> = []
+  let contactCellReuseIdentifier = "xyz"
+  var contactIds: [Int] = Utils.getContactIds()
+  var contactIdsForGroup: Set<Int> = []
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        title = "Contacts"
-        navigationController?.navigationBar.prefersLargeTitles = true
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    title = "Contacts"
+    navigationController?.navigationBar.prefersLargeTitles = true
 
-        tableView.rowHeight = 80
-        tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
-    }
-
-    private func getContactIds() {
-        contactIds = Utils.getContactIds()
-        tableView.reloadData()
-    }
-
-    override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
-
-        if #available(iOS 11.0, *) {
-            navigationController?.navigationBar.prefersLargeTitles = true
-        }
+    tableView.rowHeight = 80
+    tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
+  }
 
-        getContactIds()
-    }
+  private func getContactIds() {
+    contactIds = Utils.getContactIds()
+    tableView.reloadData()
+  }
 
-    override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
-        if #available(iOS 11.0, *) {
-            navigationController?.navigationBar.prefersLargeTitles = false
-        }
-    }
+  override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
+    if #available(iOS 11.0, *) {
+      navigationController?.navigationBar.prefersLargeTitles = true
     }
 
-    override func numberOfSections(in _: UITableView) -> Int {
-        return 1
-    }
+    getContactIds()
+  }
 
-    override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-        return contactIds.count
+  override func viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    if #available(iOS 11.0, *) {
+      navigationController?.navigationBar.prefersLargeTitles = false
     }
-
-    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let cell: ContactCell
-        if let c = tableView.dequeueReusableCell(withIdentifier: "ChatCell") as? ContactCell {
-            cell = c
-        } else {
-            cell = ContactCell(style: .subtitle, reuseIdentifier: "ChatCell")
-        }
-        let row = indexPath.row
-        let contactRow = row
-
-        if contactRow < contactIds.count {
-            let contact = MRContact(id: contactIds[contactRow])
-            cell.nameLabel.text = contact.name
-            cell.emailLabel.text = contact.email
-
-            // TODO: provider a nice selection
-            cell.selectionStyle = .none
-
-            if let img = contact.profileImage {
-                cell.setImage(img)
-            } else {
-                cell.setBackupImage(name: contact.name, color: contact.color)
-            }
-            cell.setVerified(isVerified: contact.isVerified)
-        }
-        return cell
+  }
+
+  override func didReceiveMemoryWarning() {
+    super.didReceiveMemoryWarning()
+  }
+
+  override func numberOfSections(in _: UITableView) -> Int {
+    return 1
+  }
+
+  override func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
+    return contactIds.count
+  }
+
+  override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    let cell: ContactCell
+    if let c = tableView.dequeueReusableCell(withIdentifier: "ChatCell") as? ContactCell {
+      cell = c
+    } else {
+      cell = ContactCell(style: .subtitle, reuseIdentifier: "ChatCell")
     }
-
-    override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let contactId = contactIds[indexPath.row]
-        let contactProfileController = ContactProfileViewController(contactId: contactId)
-        navigationController?.pushViewController(contactProfileController, animated: true)
+    let row = indexPath.row
+    let contactRow = row
+
+    if contactRow < contactIds.count {
+      let contact = MRContact(id: contactIds[contactRow])
+      cell.nameLabel.text = contact.name
+      cell.emailLabel.text = contact.email
+
+      // TODO: provider a nice selection
+      cell.selectionStyle = .none
+
+      if let img = contact.profileImage {
+        cell.setImage(img)
+      } else {
+        cell.setBackupImage(name: contact.name, color: contact.color)
+      }
+      cell.setVerified(isVerified: contact.isVerified)
     }
+    return cell
+  }
+
+  override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
+    let contactId = contactIds[indexPath.row]
+    let contactProfileController = ContactProfileViewController(contactId: contactId)
+    navigationController?.pushViewController(contactProfileController, animated: true)
+  }
 }

+ 120 - 120
deltachat-ios/TopViews/ProfileViewController.swift

@@ -9,156 +9,156 @@
 import UIKit
 
 class ProfileViewController: UITableViewController {
-    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 contact: MRContact? {
+    // This is nil if we do not have an account setup yet
+    if !MRConfig.configured {
+      return nil
     }
+    return MRContact(id: Int(DC_CONTACT_ID_SELF))
+  }
 
-    var fingerprint: String? {
-        if !MRConfig.configured {
-            return nil
-        }
-
-        if let cString = dc_get_securejoin_qr(mailboxPointer, 0) {
-            return String(cString: cString)
-        }
-
-        return nil
+  var fingerprint: String? {
+    if !MRConfig.configured {
+      return nil
     }
 
-    init() {
-        super.init(style: .plain)
+    if let cString = dc_get_securejoin_qr(mailboxPointer, 0) {
+      return String(cString: cString)
     }
 
-    required init?(coder _: NSCoder) {
-        fatalError("init(coder:) has not been implemented")
-    }
+    return nil
+  }
 
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        title = "My Profile"
-    }
+  init() {
+    super.init(style: .plain)
+  }
 
-    override func viewWillAppear(_: Bool) {
-        navigationController?.navigationBar.prefersLargeTitles = false
-        tableView.reloadData()
-    }
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
 
-    func displayNewChat(contactId: Int) {
-        let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
-        let chatVC = ChatViewController(chatId: Int(chatId))
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    title = "My Profile"
+  }
 
-        chatVC.hidesBottomBarWhenPushed = true
-        navigationController?.pushViewController(chatVC, animated: true)
-    }
+  override func viewWillAppear(_: Bool) {
+    navigationController?.navigationBar.prefersLargeTitles = false
+    tableView.reloadData()
+  }
 
-    override func didReceiveMemoryWarning() {
-        super.didReceiveMemoryWarning()
-        // Dispose of any resources that can be recreated.
-    }
+  func displayNewChat(contactId: Int) {
+    let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
+    let chatVC = ChatViewController(chatId: Int(chatId))
 
-    // MARK: - Table view data source
+    chatVC.hidesBottomBarWhenPushed = true
+    navigationController?.pushViewController(chatVC, animated: true)
+  }
 
-    override func numberOfSections(in _: UITableView) -> Int {
-        return 2
-    }
+  override func didReceiveMemoryWarning() {
+    super.didReceiveMemoryWarning()
+    // Dispose of any resources that can be recreated.
+  }
 
-    override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-        if section == 0 {
-            return 2
-        }
+  // MARK: - Table view data source
+
+  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
-
-        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
-                }
-            }
-        }
+    return 0
+  }
 
-        if indexPath.section == 1 {
-            if row == 0 {}
+  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
         }
+      }
+    }
 
-        return cell
+    if indexPath.section == 1 {
+      if row == 0 {}
     }
 
-    override func tableView(_: UITableView, didSelectRowAt _: IndexPath) {}
+    return cell
+  }
 
-    override func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
-        if section == 0 {
-            return 80
-        }
+  override func tableView(_: UITableView, didSelectRowAt _: IndexPath) {}
 
-        return 20
+  override func tableView(_: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+    if section == 0 {
+      return 80
     }
 
-    override func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
-        if indexPath.row == 1 {
-            return 150
-        }
+    return 20
+  }
 
-        return 46
+  override func tableView(_: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+    if indexPath.row == 1 {
+      return 150
     }
 
-    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
+    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)
         }
+        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
+  }
 }

+ 364 - 362
deltachat-ios/TopViews/SettingsController.swift

@@ -13,415 +13,417 @@ import QuickTableViewController
 import UIKit
 
 internal final class SettingsViewController: QuickTableViewController {
-    let documentInteractionController = UIDocumentInteractionController()
-    var backupProgressObserver: Any?
-    var configureProgressObserver: Any?
-    var backupHud: JGProgressHUD?
-
-    // MARK: - View lifecycle
-
-    override func viewDidLoad() {
-        super.viewDidLoad()
-        title = "Settings"
-
-        documentInteractionController.delegate = self as? UIDocumentInteractionControllerDelegate
-
-        setTable()
-    }
-
-    override func viewDidAppear(_ animated: Bool) {
-        super.viewDidAppear(animated)
-        let nc = NotificationCenter.default
-        backupProgressObserver = nc.addObserver(
-            forName: dc_notificationBackupProgress,
-            object: nil,
-            queue: nil
-        ) {
-            notification in
-            if let ui = notification.userInfo {
-                if ui["error"] as! Bool {
-                    self.setHudError(ui["errorMessage"] as? String)
-                } else if ui["done"] as! Bool {
-                    self.setHudDone()
-                } else {
-                    self.setHudProgress(ui["progress"] as! Int)
-                }
-            }
+  let documentInteractionController = UIDocumentInteractionController()
+  var backupProgressObserver: Any?
+  var configureProgressObserver: Any?
+  var backupHud: JGProgressHUD?
+
+  // MARK: - View lifecycle
+
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    title = "Settings"
+
+    documentInteractionController.delegate = self as? UIDocumentInteractionControllerDelegate
+
+    setTable()
+  }
+
+  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.setHudError(ui["errorMessage"] as? String)
+        } else if ui["done"] as! Bool {
+          self.setHudDone()
+        } else {
+          self.setHudProgress(ui["progress"] as! Int)
         }
-        configureProgressObserver = nc.addObserver(
-            forName: dc_notificationConfigureProgress,
-            object: nil,
-            queue: nil
-        ) {
-            notification in
-            if let ui = notification.userInfo {
-                if ui["error"] as! Bool {
-                    self.setHudError(ui["errorMessage"] as? String)
-                } else if ui["done"] as! Bool {
-                    self.setHudDone()
-                } else {
-                    self.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.setHudError(ui["errorMessage"] as? String)
+        } else if ui["done"] as! Bool {
+          self.setHudDone()
+        } else {
+          self.setHudProgress(ui["progress"] as! Int)
         }
+      }
     }
+  }
 
-    override func viewWillAppear(_ animated: Bool) {
-        super.viewWillAppear(animated)
+  override func viewWillAppear(_ animated: Bool) {
+    super.viewWillAppear(animated)
 
-        if #available(iOS 11.0, *) {
-            navigationController?.navigationBar.prefersLargeTitles = true
-        }
+    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 viewWillDisappear(_ animated: Bool) {
+    super.viewWillDisappear(animated)
+    if #available(iOS 11.0, *) {
+      navigationController?.navigationBar.prefersLargeTitles = false
     }
-
-    private func setHudError(_ message: String?) {
-        if let hud = self.backupHud {
-            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
-                UIView.animate(
-                    withDuration: 0.1, animations: {
-                        hud.textLabel.text = message ?? "Error"
-                        hud.detailTextLabel.text = nil
-                        hud.indicatorView = JGProgressHUDErrorIndicatorView()
-                    }
-                )
-
-                hud.dismiss(afterDelay: 5.0)
-            }
-        }
+  }
+
+  private 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)
+      }
     }
-
-    private func setHudDone() {
-        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()
-                    }
-                )
-
-                self.setTable()
-                self.tableView.reloadData()
-
-                hud.dismiss(afterDelay: 1.0)
-            }
-        }
+  }
+
+  private func setHudDone() {
+    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()
+          }
+        )
+
+        self.setTable()
+        self.tableView.reloadData()
+
+        hud.dismiss(afterDelay: 1.0)
+      }
     }
+  }
 
-    private func setHudProgress(_ progress: Int) {
-        if let hud = self.backupHud {
-            hud.progress = Float(progress) / 1000.0
-            hud.detailTextLabel.text = "\(progress / 10)% Complete"
-        }
+  private func setHudProgress(_ progress: Int) {
+    if let hud = self.backupHud {
+      hud.progress = Float(progress) / 1000.0
+      hud.detailTextLabel.text = "\(progress / 10)% Complete"
     }
+  }
 
-    private func showBackupHud(_ text: String) {
-        DispatchQueue.main.async {
-            let hud = JGProgressHUD(style: .dark)
-            hud.vibrancyEnabled = true
-            hud.indicatorView = JGProgressHUDPieIndicatorView()
+  private 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)
+      hud.detailTextLabel.text = "0% Complete"
+      hud.textLabel.text = text
+      hud.show(in: self.view)
 
-            self.backupHud = hud
-        }
+      self.backupHud = hud
     }
+  }
 
-    override func viewDidDisappear(_ animated: Bool) {
-        super.viewDidDisappear(animated)
+  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)
-        }
+    let nc = NotificationCenter.default
+    if let backupProgressObserver = self.backupProgressObserver {
+      nc.removeObserver(backupProgressObserver)
+    }
+    if let configureProgressObserver = self.configureProgressObserver {
+      nc.removeObserver(configureProgressObserver)
+    }
+  }
+
+  private func setTable() {
+    let basicsRows: [Row & RowStyle] = [
+      NavigationRow(title: "Email", subtitle: .rightAligned(MRConfig.addr ?? ""), action: editCell()),
+      NavigationRow(title: "Password", subtitle: .rightAligned("********"), action: editCell()),
+      TapActionRow(title: "Configure", action: { [weak self] in self?.configure($0) }),
+    ]
+    var backupRows = [
+      TapActionRow(title: "Create backup", action: { [weak self] in self?.createBackup($0) }),
+      TapActionRow(title: "Restore from backup", action: { [weak self] in self?.restoreBackup($0) }),
+    ]
+
+    let deleteRow = TapActionRow(title: "Delete Account", action: { [weak self] in self?.deleteAccount($0) })
+
+    if MRConfig.configured {
+      backupRows.removeLast()
     }
 
-    private func setTable() {
-        let basicsRows: [Row & RowStyle] = [
-            NavigationRow(title: "Email", subtitle: .rightAligned(MRConfig.addr ?? ""), action: editCell()),
-            NavigationRow(title: "Password", subtitle: .rightAligned("********"), action: editCell()),
-            TapActionRow(title: "Configure", action: { [weak self] in self?.configure($0) }),
+    tableContents = [
+      Section(
+        title: "Basics",
+        rows: basicsRows
+      ),
+
+      Section(
+        title: "User Details",
+        rows: [
+          NavigationRow(title: "Display Name", subtitle: .rightAligned(MRConfig.displayname ?? ""), action: editCell()),
+          NavigationRow(title: "Status", subtitle: .rightAligned(MRConfig.selfstatus ?? ""), action: editCell()),
         ]
-        var backupRows = [
-            TapActionRow(title: "Create backup", action: { [weak self] in self?.createBackup($0) }),
-            TapActionRow(title: "Restore from backup", action: { [weak self] in self?.restoreBackup($0) }),
+      ),
+
+      Section(
+        title: "Advanced",
+        rows: [
+          NavigationRow(title: "IMAP Server", subtitle: .rightAligned(MRConfig.mailServer ?? MRConfig.configuredMailServer), action: editCell()),
+          NavigationRow(title: "IMAP User", subtitle: .rightAligned(MRConfig.mailUser ?? MRConfig.configuredMailUser), action: editCell()),
+          NavigationRow(title: "IMAP Port", subtitle: .rightAligned(MRConfig.mailPort ?? MRConfig.configuredMailPort), action: editCell()),
+
+          NavigationRow(title: "SMTP Server", subtitle: .rightAligned(MRConfig.sendServer ?? MRConfig.configuredSendServer), action: editCell()),
+          NavigationRow(title: "SMTP User", subtitle: .rightAligned(MRConfig.sendUser ?? MRConfig.configuredSendUser), action: editCell()),
+          NavigationRow(title: "SMTP Port", subtitle: .rightAligned(MRConfig.sendPort ?? MRConfig.configuredSendPort), action: editCell()),
+          NavigationRow(title: "SMTP Password", subtitle: .rightAligned("********"), action: editCell()),
         ]
+      ),
+
+      Section(
+        title: "Flags",
+        rows: [
+          SwitchRow(title: "E2EE enabled", switchValue: MRConfig.e2eeEnabled, action: editCell()),
+          SwitchRow(title: "Read Receipts", switchValue: MRConfig.mdnsEnabled, action: editCell()),
+          SwitchRow(title: "Watch Inbox", switchValue: MRConfig.inboxWatch, action: editCell()),
+          SwitchRow(title: "Watch Sentbox", switchValue: MRConfig.sentboxWatch, action: editCell()),
+          SwitchRow(title: "Watch Mvbox", switchValue: MRConfig.mvboxWatch, action: editCell()),
+          SwitchRow(title: "Move to Mvbox", switchValue: MRConfig.mvboxMove, action: editCell()),
+          SwitchRow(title: "Save Mime Headers", switchValue: MRConfig.saveMimeHeaders, action: editCell()),
+        ]
+      ),
+
+      Section(
+        title: "Backup",
+        rows: backupRows
+      ),
 
-        let deleteRow = TapActionRow(title: "Delete Account", action: { [weak self] in self?.deleteAccount($0) })
+      Section(title: "Danger", rows: [
+        deleteRow
+      ])
+    ]
+  }
 
+  // MARK: - Actions
+
+  // 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 ?? ""
+      let alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert)
+
+      if title == "Email" {
         if MRConfig.configured {
-            backupRows.removeLast()
+          // Don't change emails in the running system
+          return
+        }
+      }
+
+      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
+      }
+
+      let confirmAction = UIAlertAction(title: "Save", style: .default) { _ in
+        guard let textFields = alertController.textFields,
+          !textFields.isEmpty else {
+          // Could not find textfield
+          return
         }
 
-        tableContents = [
-            Section(
-                title: "Basics",
-                rows: basicsRows
-            ),
-
-            Section(
-                title: "User Details",
-                rows: [
-                    NavigationRow(title: "Display Name", subtitle: .rightAligned(MRConfig.displayname ?? ""), action: editCell()),
-                    NavigationRow(title: "Status", subtitle: .rightAligned(MRConfig.selfstatus ?? ""), action: editCell()),
-                ]
-            ),
-
-            Section(
-                title: "Advanced",
-                rows: [
-                    NavigationRow(title: "IMAP Server", subtitle: .rightAligned(MRConfig.mailServer ?? MRConfig.configuredMailServer), action: editCell()),
-                    NavigationRow(title: "IMAP User", subtitle: .rightAligned(MRConfig.mailUser ?? MRConfig.configuredMailUser), action: editCell()),
-                    NavigationRow(title: "IMAP Port", subtitle: .rightAligned(MRConfig.mailPort ?? MRConfig.configuredMailPort), action: editCell()),
-
-                    NavigationRow(title: "SMTP Server", subtitle: .rightAligned(MRConfig.sendServer ?? MRConfig.configuredSendServer), action: editCell()),
-                    NavigationRow(title: "SMTP User", subtitle: .rightAligned(MRConfig.sendUser ?? MRConfig.configuredSendUser), action: editCell()),
-                    NavigationRow(title: "SMTP Port", subtitle: .rightAligned(MRConfig.sendPort ?? MRConfig.configuredSendPort), action: editCell()),
-                    NavigationRow(title: "SMTP Password", subtitle: .rightAligned("********"), action: editCell()),
-                ]
-            ),
-
-            Section(
-                title: "Flags",
-                rows: [
-                    SwitchRow(title: "E2EE enabled", switchValue: MRConfig.e2eeEnabled, action: editCell()),
-                    SwitchRow(title: "Read Receipts", switchValue: MRConfig.mdnsEnabled, action: editCell()),
-                    SwitchRow(title: "Watch Inbox", switchValue: MRConfig.inboxWatch, action: editCell()),
-                    SwitchRow(title: "Watch Sentbox", switchValue: MRConfig.sentboxWatch, action: editCell()),
-                    SwitchRow(title: "Watch Mvbox", switchValue: MRConfig.mvboxWatch, action: editCell()),
-                    SwitchRow(title: "Move to Mvbox", switchValue: MRConfig.mvboxMove, action: editCell()),
-                    SwitchRow(title: "Save Mime Headers", switchValue: MRConfig.saveMimeHeaders, action: editCell()),
-                ]
-            ),
-
-            Section(
-                title: "Backup",
-                rows: backupRows
-            ),
-
-            Section(title: "Danger", rows: [
-                deleteRow,
-            ]),
-        ]
-    }
+        let field = textFields[0]
+
+        // TODO: add field validation
+        var needRefresh = false
+
+        switch title {
+        case "Email":
+          MRConfig.addr = field.text
+        case "Password":
+          MRConfig.mailPw = field.text
+        case "Display Name":
+          MRConfig.displayname = field.text
+          needRefresh = true
+        case "Status":
+          MRConfig.selfstatus = field.text
+          needRefresh = true
+        case "IMAP Server":
+          MRConfig.mailServer = field.text
+          needRefresh = true
+        case "IMAP User":
+          MRConfig.mailUser = field.text
+          needRefresh = true
+        case "IMAP Port":
+          MRConfig.mailPort = field.text
+          needRefresh = true
+        case "SMTP Server":
+          MRConfig.sendServer = field.text
+          needRefresh = true
+        case "SMTP User":
+          MRConfig.sendUser = field.text
+          needRefresh = true
+        case "SMTP Port":
+          MRConfig.sendPort = field.text
+          needRefresh = true
+        case "SMTP Password":
+          MRConfig.sendPw = field.text
+        default:
+          logger.info("unknown title", title)
+        }
 
-    // MARK: - Actions
-
-    private func editCell() -> (Row) -> Void {
-        return { [weak self] sender in
-            logger.info("row edit", sender.title)
-
-            let title = sender.title
-            let subtitle: String = sender.subtitle?.text ?? ""
-            let alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert)
-
-            if title == "Email" {
-                if MRConfig.configured {
-                    // Don't change emails in the running system
-                    return
-                }
-            }
-
-            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
-            }
-
-            let confirmAction = UIAlertAction(title: "Save", style: .default) { _ in
-                guard let textFields = alertController.textFields,
-                    textFields.count > 0 else {
-                    // Could not find textfield
-                    return
-                }
-
-                let field = textFields[0]
-
-                // TODO: add field validation
-                var needRefresh = false
-
-                switch title {
-                case "Email":
-                    MRConfig.addr = field.text
-                case "Password":
-                    MRConfig.mailPw = field.text
-                case "Display Name":
-                    MRConfig.displayname = field.text
-                    needRefresh = true
-                case "Status":
-                    MRConfig.selfstatus = field.text
-                    needRefresh = true
-                case "IMAP Server":
-                    MRConfig.mailServer = field.text
-                    needRefresh = true
-                case "IMAP User":
-                    MRConfig.mailUser = field.text
-                    needRefresh = true
-                case "IMAP Port":
-                    MRConfig.mailPort = field.text
-                    needRefresh = true
-                case "SMTP Server":
-                    MRConfig.sendServer = field.text
-                    needRefresh = true
-                case "SMTP User":
-                    MRConfig.sendUser = field.text
-                    needRefresh = true
-                case "SMTP Port":
-                    MRConfig.sendPort = field.text
-                    needRefresh = true
-                case "SMTP Password":
-                    MRConfig.sendPw = field.text
-                default:
-                    logger.info("unknown title", title)
-                }
-
-                if needRefresh {
-                    self?.setTable()
-                    self?.tableView.reloadData()
-                }
-            }
-
-            let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in
-                logger.info("canceled")
-            }
-
-            alertController.addTextField { textField in
-                textField.placeholder = subtitle
-                if title.contains("Password") {
-                    textField.isSecureTextEntry = true
-                    textField.textContentType = .password
-                }
-
-                if title == "Email" {
-                    textField.keyboardType = .emailAddress
-                    textField.textContentType = .username
-                }
-
-                if title.contains("Server") {
-                    textField.keyboardType = .URL
-                }
-
-                if title.contains("Port") {
-                    textField.keyboardType = .numberPad
-                }
-            }
-
-            alertController.addAction(confirmAction)
-            alertController.addAction(cancelAction)
-
-            self?.present(alertController, animated: true, completion: nil)
+        if needRefresh {
+          self?.setTable()
+          self?.tableView.reloadData()
         }
-    }
+      }
 
-    private func createBackup(_: Row) {
-        // if let documents = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.delta.chat.ios")?.path {
+      let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in
+        logger.info("canceled")
+      }
 
-        let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
-        if documents.count > 0 {
-            logger.info("create backup in \(documents)")
-            showBackupHud("Creating Backup")
-            DispatchQueue.main.async {
-                dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
-            }
-        } else {
-            logger.error("document directory not found")
+      alertController.addTextField { textField in
+        textField.placeholder = subtitle
+        if title.contains("Password") {
+          textField.isSecureTextEntry = true
+          textField.textContentType = .password
         }
-    }
 
-    private func restoreBackup(_: Row) {
-        logger.info("restoring backup")
-        if MRConfig.configured {
-            return
+        if title == "Email" {
+          textField.keyboardType = .emailAddress
+          textField.textContentType = .username
         }
-        let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
-        if documents.count > 0 {
-            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))")
+        if title.contains("Server") {
+          textField.keyboardType = .URL
+        }
 
-                showBackupHud("Restoring Backup")
-                dc_imex(mailboxPointer, DC_IMEX_IMPORT_BACKUP, file, nil)
+        if title.contains("Port") {
+          textField.keyboardType = .numberPad
+        }
+      }
 
-                return
-            }
+      alertController.addAction(confirmAction)
+      alertController.addAction(cancelAction)
 
-            let alert = UIAlertController(title: "Can not restore", message: "No Backup found", preferredStyle: .alert)
-            alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
-                self.dismiss(animated: true, completion: nil)
-            }))
-            present(alert, animated: true, completion: nil)
-            return
-        }
+      self?.present(alertController, animated: true, completion: nil)
+    }
+  }
+
+  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)")
+      showBackupHud("Creating Backup")
+      DispatchQueue.main.async {
+        dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
+      }
+    } else {
+      logger.error("document directory not found")
+    }
+  }
 
-        logger.error("no documents directory found")
+  private func restoreBackup(_: Row) {
+    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))")
 
-    private func configure(_: Row) {
-        showBackupHud("Configuring account")
-        dc_configure(mailboxPointer)
+        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
+        self.dismiss(animated: true, completion: nil)
+      }))
+      present(alert, animated: true, completion: nil)
+      return
     }
 
-    private func deleteAccount(_: Row) {
-        logger.info("deleting account")
-        let appDelegate = UIApplication.shared.delegate as! AppDelegate
+    logger.error("no documents directory found")
+  }
 
-        let dbfile = appDelegate.dbfile()
-        let dburl = URL(fileURLWithPath: dbfile, isDirectory: false)
-        let alert = UIAlertController(title: "Delete Account", message: "Are you sure you wante to delete your account data?", preferredStyle: .alert)
+  private func configure(_: Row) {
+    showBackupHud("Configuring account")
+    dc_configure(mailboxPointer)
+  }
 
-        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)")
-            }
+  private func deleteAccount(_: Row) {
+    logger.info("deleting account")
+    let appDelegate = UIApplication.shared.delegate as! AppDelegate
 
-            appDelegate.open()
-            appDelegate.start()
+    let dbfile = appDelegate.dbfile()
+    let dburl = URL(fileURLWithPath: dbfile, isDirectory: false)
+    let alert = UIAlertController(title: "Delete Account", message: "Are you sure you wante to delete your account data?", preferredStyle: .alert)
 
-            // refresh our view
-            self.setTable()
-            self.tableView.reloadData()
+    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)")
+      }
 
-            self.dismiss(animated: true, completion: nil)
-        }))
-        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+      appDelegate.open()
+      appDelegate.start()
 
-        present(alert, animated: true, completion: nil)
-    }
+      // refresh our view
+      self.setTable()
+      self.tableView.reloadData()
+
+      self.dismiss(animated: true, completion: nil)
+    }))
+    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
+
+    present(alert, animated: true, completion: nil)
+  }
 }

+ 142 - 136
deltachat-ios/Utils.swift

@@ -10,162 +10,168 @@ import Foundation
 import UIKit
 
 struct Utils {
-    static func getContactIds() -> [Int] {
-        let c_contacts = dc_get_contacts(mailboxPointer, 0, nil)
-        return Utils.copyAndFreeArray(inputArray: c_contacts)
+  static func getContactIds() -> [Int] {
+    let cContacts = dc_get_contacts(mailboxPointer, 0, nil)
+    return Utils.copyAndFreeArray(inputArray: cContacts)
+  }
+
+  static func getInitials(inputName: String) -> String {
+    let nameParts = inputName.split(separator: " ")
+    let initials: [Character] = nameParts.compactMap { part in part.capitalized.first }
+    let initialsString: String = String(initials)
+    return initialsString
+  }
+
+  static func copyAndFreeArray(inputArray: UnsafeMutablePointer<dc_array_t>?) -> [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 getInitials(inputName: String) -> String {
-        let nameParts = inputName.split(separator: " ")
-        let initials: [Character] = nameParts.compactMap { part in part.capitalized.first }
-        let initialsString: String = String(initials)
-        return initialsString
+    dc_array_unref(inputArray)
+
+    return acc
+  }
+
+  static func copyAndFreeArrayWithLen(inputArray: UnsafeMutablePointer<dc_array_t>?, len: Int = 0) -> [Int] {
+    var acc: [Int] = []
+    let arrayLen = dc_array_get_cnt(inputArray)
+    let start = max(0, arrayLen - len)
+    for i in start ..< arrayLen {
+      let e = dc_array_get_id(inputArray, i)
+      acc.append(Int(e))
     }
+    dc_array_unref(inputArray)
 
-    static func copyAndFreeArray(inputArray: UnsafeMutablePointer<dc_array_t>?) -> [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)
-
-        return acc
-    }
+    return acc
+  }
 
-    static func copyAndFreeArrayWithLen(inputArray: UnsafeMutablePointer<dc_array_t>?, len: Int = 0) -> [Int] {
-        var acc: [Int] = []
-        let arrayLen = dc_array_get_cnt(inputArray)
-        let start = max(0, arrayLen - len)
-        for i in start ..< arrayLen {
-            let e = dc_array_get_id(inputArray, i)
-            acc.append(Int(e))
-        }
-        dc_array_unref(inputArray)
-
-        return acc
-    }
+  static func copyAndFreeArrayWithOffset(inputArray: UnsafeMutablePointer<dc_array_t>?, len: Int = 0, from: Int = 0, skipEnd: Int = 0) -> [Int] {
+    let lenArray = dc_array_get_cnt(inputArray)
+    if lenArray <= skipEnd || lenArray == 0 {
+      dc_array_unref(inputArray)
+      return [] }
 
-    static func copyAndFreeArrayWithOffset(inputArray: UnsafeMutablePointer<dc_array_t>?, len: Int = 0, from: Int = 0, skipEnd: Int = 0) -> [Int] {
-        let lenArray = dc_array_get_cnt(inputArray)
-        if lenArray <= skipEnd || lenArray == 0 {
-            dc_array_unref(inputArray)
-            return [] }
+    let start = lenArray - 1 - skipEnd
+    let end = max(0, start - len)
+    let finalLen = start - end + (len > 0 ? 0 : 1)
+    var acc: [Int] = [Int](repeating: 0, count: finalLen)
 
-        let start = lenArray - 1 - skipEnd
-        let end = max(0, start - len)
-        let finalLen = start - end + (len > 0 ? 0 : 1)
-        var acc: [Int] = [Int](repeating: 0, count: finalLen)
+    for i in stride(from: start, to: end, by: -1) {
+      let index = finalLen - (start - i) - 1
+      acc[index] = Int(dc_array_get_id(inputArray, i))
+    }
 
-        for i in stride(from: start, to: end, by: -1) {
-            let index = finalLen - (start - i) - 1
-            acc[index] = Int(dc_array_get_id(inputArray, i))
-        }
+    dc_array_unref(inputArray)
+    logger.info("got: \(from) \(len) \(lenArray) - \(acc)")
+
+    return acc
+  }
+
+  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
+    }
 
-        dc_array_unref(inputArray)
-        logger.info("got: \(from) \(len) \(lenArray) - \(acc)")
+    let size = image.size.applying(CGAffineTransform(scaleX: 0.2, y: 0.2))
+    let hasAlpha = false
+    let scale: CGFloat = 0.0
 
-        return acc
-    }
+    UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
+    image.draw(in: CGRect(origin: CGPoint.zero, size: size))
 
-    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 scaledImageI = UIGraphicsGetImageFromCurrentImageContext()
+    UIGraphicsEndImageContext()
 
-        let emailTest = NSPredicate(format: "SELF MATCHES[c] %@", emailRegEx)
-        return emailTest.evaluate(with: email)
+    guard let scaledImage = scaledImageI else {
+      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.count > 0 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: ", ")
+    guard let data = scaledImage.jpegData(compressionQuality: 0.9) else {
+      return nil
     }
 
-    static func saveImage(image: UIImage) -> String? {
-        guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
-            return nil
-        }
-
-        let size = image.size.applying(CGAffineTransform(scaleX: 0.2, y: 0.2))
-        let hasAlpha = false
-        let scale: CGFloat = 0.0
-
-        UIGraphicsBeginImageContextWithOptions(size, !hasAlpha, scale)
-        image.draw(in: CGRect(origin: CGPoint.zero, size: size))
-
-        let _scaledImage = UIGraphicsGetImageFromCurrentImageContext()
-        UIGraphicsEndImageContext()
-
-        guard let scaledImage = _scaledImage else {
-            return nil
-        }
-
-        guard let data = scaledImage.jpegData(compressionQuality: 0.9) else {
-            return nil
-        }
-
-        do {
-            let timestamp = Int(Date().timeIntervalSince1970)
-            let path = directory.appendingPathComponent("\(timestamp).jpg")
-            try data.write(to: path!)
-            return path?.relativePath
-        } catch {
-            logger.info(error.localizedDescription)
-            return nil
-        }
+    do {
+      let timestamp = Int(Date().timeIntervalSince1970)
+      let path = directory.appendingPathComponent("\(timestamp).jpg")
+      try data.write(to: path!)
+      return path?.relativePath
+    } catch {
+      logger.info(error.localizedDescription)
+      return nil
     }
+  }
 }
 
 extension UIColor {
-    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)
+  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
     }
 
-    // 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)
+    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)
+  }
 }

+ 694 - 692
deltachat-ios/Wrapper.swift

@@ -11,831 +11,833 @@ 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: UnsafeMutablePointer<dc_contact_t>
-
-    var name: String {
-        if contactPointer.pointee.name == nil {
-            return email
-        }
-        let name = String(cString: contactPointer.pointee.name)
-        if name.isEmpty {
-            return email
-        }
-        return name
-    }
+  private var contactPointer: UnsafeMutablePointer<dc_contact_t>
 
-    var email: String {
-        if contactPointer.pointee.addr == nil {
-            return "error: no email in contact"
-        }
-        return String(cString: contactPointer.pointee.addr)
+  var name: String {
+    if contactPointer.pointee.name == nil {
+      return email
     }
-
-    var isVerified: Bool {
-        return dc_contact_is_verified(contactPointer) > 0
+    let name = String(cString: contactPointer.pointee.name)
+    if name.isEmpty {
+      return email
     }
+    return name
+  }
 
-    var isBlocked: Bool {
-        return dc_contact_is_blocked(contactPointer) == 1
+  var email: String {
+    if contactPointer.pointee.addr == nil {
+      return "error: no email in contact"
     }
+    return String(cString: contactPointer.pointee.addr)
+  }
 
-    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 isVerified: Bool {
+    return dc_contact_is_verified(contactPointer) > 0
+  }
 
-    var color: UIColor {
-        return UIColor(netHex: Int(dc_contact_get_color(contactPointer)))
-    }
+  var isBlocked: Bool {
+    return dc_contact_is_blocked(contactPointer) == 1
+  }
 
-    var id: Int {
-        return Int(contactPointer.pointee.id)
+  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
     }
 
-    init(id: Int) {
-        contactPointer = dc_get_contact(mailboxPointer, UInt32(id))
-    }
+    return nil
+  }()
 
-    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))
-    }
-}
+  var color: UIColor {
+    return UIColor(netHex: Int(dc_contact_get_color(contactPointer)))
+  }
 
-class MRMessage: MessageType {
-    private var messagePointer: UnsafeMutablePointer<dc_msg_t>
+  var id: Int {
+    return Int(contactPointer.pointee.id)
+  }
 
-    lazy var sender: Sender = {
-        Sender(id: "\(fromContactId)", displayName: fromContact.name)
-    }()
+  init(id: Int) {
+    contactPointer = dc_get_contact(mailboxPointer, UInt32(id))
+  }
 
-    lazy var sentDate: Date = {
-        Date(timeIntervalSince1970: Double(timestamp))
-    }()
+  deinit {
+    dc_contact_unref(contactPointer)
+  }
 
-    let localDateFormatter: DateFormatter = {
-        let result = DateFormatter()
-        result.dateStyle = .none
-        result.timeStyle = .short
-        return result
-    }()
+  func block() {
+    dc_block_contact(mailboxPointer, UInt32(id), 1)
+  }
 
-    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)
-        }
+  func unblock() {
+    dc_block_contact(mailboxPointer, UInt32(id), 0)
+  }
 
-        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)"
-    }
+  func marknoticed() {
+    dc_marknoticed_contact(mailboxPointer, UInt32(id))
+  }
+}
 
-    var id: Int {
-        return Int(messagePointer.pointee.id)
-    }
+class MRMessage: MessageType {
+  private var messagePointer: UnsafeMutablePointer<dc_msg_t>
+
+  lazy var sender: Sender = {
+    Sender(id: "\(fromContactId)", displayName: fromContact.name)
+  }()
+
+  lazy var sentDate: Date = {
+    Date(timeIntervalSince1970: Double(timestamp))
+  }()
+
+  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(messagePointer.pointee.id)
+  }
+
+  var fromContactId: Int {
+    return Int(messagePointer.pointee.from_id)
+  }
+
+  lazy var fromContact: MRContact = {
+    MRContact(id: fromContactId)
+  }()
+
+  lazy var toContact: MRContact = {
+    MRContact(id: toContactId)
+  }()
+
+  var toContactId: Int {
+    return Int(messagePointer.pointee.to_id)
+  }
+
+  var chatId: Int {
+    return Int(messagePointer.pointee.chat_id)
+  }
+
+  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
+  }
+
+  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(messagePointer.pointee.type)
+  }
+
+  // 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(messagePointer.pointee.timestamp)
+  }
 
-    var fromContactId: Int {
-        return Int(messagePointer.pointee.from_id)
-    }
+  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))
+  }
 
-    lazy var fromContact: MRContact = {
-        MRContact(id: fromContactId)
-    }()
+  deinit {
+    dc_msg_unref(messagePointer)
+  }
+}
 
-    lazy var toContact: MRContact = {
-        MRContact(id: toContactId)
-    }()
+class MRChat {
+  var chatPointer: UnsafeMutablePointer<dc_chat_t>
+
+  var id: Int {
+    return Int(chatPointer.pointee.id)
+  }
+
+  var name: String {
+    if chatPointer.pointee.name == nil {
+      return "Error - no name"
+    }
+    return String(cString: chatPointer.pointee.name)
+  }
+
+  var type: Int {
+    return Int(chatPointer.pointee.type)
+  }
+
+  var color: UIColor {
+    return UIColor(netHex: Int(dc_chat_get_color(chatPointer)))
+  }
+
+  var isVerified: Bool {
+    return dc_chat_is_verified(chatPointer) > 0
+  }
+
+  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 toContactId: Int {
-        return Int(messagePointer.pointee.to_id)
-    }
+class MRPoorText {
+  private var poorTextPointer: UnsafeMutablePointer<dc_lot_t>
 
-    var chatId: Int {
-        return Int(messagePointer.pointee.chat_id)
+  var text1: String? {
+    if poorTextPointer.pointee.text1 == nil {
+      return nil
     }
+    return String(cString: poorTextPointer.pointee.text1)
+  }
 
-    var text: String? {
-        guard let result = dc_msg_get_text(messagePointer) else { return nil }
-
-        return String(cString: result)
+  var text2: String? {
+    if poorTextPointer.pointee.text2 == nil {
+      return nil
     }
+    return String(cString: poorTextPointer.pointee.text2)
+  }
 
-    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 text1Meaning: Int {
+    return Int(poorTextPointer.pointee.text1_meaning)
+  }
 
-    var fileURL: URL? {
-        if let file = self.file {
-            return URL(fileURLWithPath: file, isDirectory: false)
-        }
-        return nil
-    }
+  var timeStamp: Int {
+    return Int(poorTextPointer.pointee.timestamp)
+  }
 
-    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 state: Int {
+    return Int(poorTextPointer.pointee.state)
+  }
 
-    var file: String? {
-        if let cStr = dc_msg_get_file(messagePointer) {
-            let str = String(cString: cStr)
+  // takes ownership of specified pointer
+  init(poorTextPointer: UnsafeMutablePointer<dc_lot_t>) {
+    self.poorTextPointer = poorTextPointer
+  }
 
-            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)
+  deinit {
+    dc_lot_unref(poorTextPointer)
+  }
+}
 
-            return str == "" ? nil : str
-        }
+class MRChatList {
+  private var chatListPointer: UnsafeMutablePointer<dc_chatlist_t>
 
-        return nil
-    }
+  var length: Int {
+    return dc_chatlist_get_cnt(chatListPointer)
+    // return Int(chatListPointer.pointee.m_cnt)
+  }
 
-    var filesize: Int {
-        return Int(dc_msg_get_filebytes(messagePointer))
-    }
-
-    // MR_MSG_*
-    var type: Int {
-        return Int(messagePointer.pointee.type)
-    }
-
-    // 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"
-        }
-    }
+  // takes ownership of specified pointer
+  init(chatListPointer: UnsafeMutablePointer<dc_chatlist_t>) {
+    self.chatListPointer = chatListPointer
+  }
 
-    var timestamp: Int64 {
-        return Int64(messagePointer.pointee.timestamp)
-    }
+  func getChatId(index: Int) -> Int {
+    return Int(dc_chatlist_get_chat_id(chatListPointer, index))
+  }
 
-    var isInfo: Bool {
-        return dc_msg_is_info(messagePointer) == 1
-    }
+  func getMessageId(index: Int) -> Int {
+    return Int(dc_chatlist_get_msg_id(chatListPointer, index))
+  }
 
-    init(id: Int) {
-        messagePointer = dc_get_msg(mailboxPointer, UInt32(id))
+  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)
+  }
 
-    func summary(chars: Int) -> String? {
-        guard let result = dc_msg_get_summarytext(messagePointer, Int32(chars)) else { return nil }
+  deinit {
+    dc_chatlist_unref(chatListPointer)
+  }
+}
 
-        return String(cString: result)
-    }
-    
-    func createChat() -> MRChat {
-        let chatId = dc_create_chat_by_msg_id(mailboxPointer, UInt32(id))
-        return MRChat(id: Int(chatId))
+func strToBool(_ value: String?) -> Bool {
+  if let vStr = value {
+    if let vInt = Int(vStr) {
+      return vInt == 1
     }
-    
+    return false
+  }
 
-    deinit {
-        dc_msg_unref(messagePointer)
-    }
+  return false
 }
 
-class MRChat {
-    var chatPointer: UnsafeMutablePointer<dc_chat_t>
-
-    var id: Int {
-        return Int(chatPointer.pointee.id)
-    }
-
-    var name: String {
-        if chatPointer.pointee.name == nil {
-            return "Error - no name"
-        }
-        return String(cString: chatPointer.pointee.name)
-    }
+class MRConfig {
+  private class func getOptStr(_ key: String) -> String? {
+    let p = dc_get_config(mailboxPointer, key)
 
-    var type: Int {
-        return Int(chatPointer.pointee.type)
+    if let pSafe = p {
+      let c = String(cString: pSafe)
+      if c == "" {
+        return nil
+      }
+      return c
     }
 
-    var color: UIColor {
-        return UIColor(netHex: Int(dc_chat_get_color(chatPointer)))
-    }
+    return nil
+  }
 
-    var isVerified: Bool {
-        return dc_chat_is_verified(chatPointer) > 0
+  private class func setOptStr(_ key: String, _ value: String?) {
+    if let v = value {
+      dc_set_config(mailboxPointer, key, v)
     }
+  }
 
-    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
-        }
+  private class func getBool(_ key: String) -> Bool {
+    return strToBool(getOptStr(key))
+  }
 
-        return nil
-    }()
+  private class func setBool(_ key: String, _ value: Bool) {
+    let vStr = value ? "1" : "0"
+    setOptStr(key, vStr)
+  }
 
-    var subtitle: String? {
-        if let cString = dc_chat_get_subtitle(chatPointer) {
-            let str = String(cString: cString)
-            return str == "" ? nil : str
-        }
-        return nil
+  /**
+   *  Address to display (always needed)
+   */
+  class var addr: String? {
+    set {
+      setOptStr("addr", newValue)
     }
-
-    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)
+    get {
+      return getOptStr("addr")
     }
-}
-
-class MRPoorText {
-    private var poorTextPointer: UnsafeMutablePointer<dc_lot_t>
+  }
 
-    var text1: String? {
-        if poorTextPointer.pointee.text1 == nil {
-            return nil
-        }
-        return String(cString: poorTextPointer.pointee.text1)
+  /**
+   *  IMAP-server, guessed if left out
+   */
+  class var mailServer: String? {
+    set {
+      setOptStr("mail_server", newValue)
     }
-
-    var text2: String? {
-        if poorTextPointer.pointee.text2 == nil {
-            return nil
-        }
-        return String(cString: poorTextPointer.pointee.text2)
+    get {
+      return getOptStr("mail_server")
     }
+  }
 
-    var text1Meaning: Int {
-        return Int(poorTextPointer.pointee.text1_meaning)
+  /**
+   *  IMAP-username, guessed if left out
+   */
+  class var mailUser: String? {
+    set {
+      setOptStr("mail_user", newValue)
     }
-
-    var timeStamp: Int {
-        return Int(poorTextPointer.pointee.timestamp)
+    get {
+      return getOptStr("mail_user")
     }
+  }
 
-    var state: Int {
-        return Int(poorTextPointer.pointee.state)
+  /**
+   *  IMAP-password (always needed)
+   */
+  class var mailPw: String? {
+    set {
+      setOptStr("mail_pw", newValue)
     }
-
-    // takes ownership of specified pointer
-    init(poorTextPointer: UnsafeMutablePointer<dc_lot_t>) {
-        self.poorTextPointer = poorTextPointer
+    get {
+      return getOptStr("mail_pw")
     }
+  }
 
-    deinit {
-        dc_lot_unref(poorTextPointer)
+  /**
+   *  IMAP-port, guessed if left out
+   */
+  class var mailPort: String? {
+    set {
+      setOptStr("mail_port", newValue)
     }
-}
-
-class MRChatList {
-    private var chatListPointer: UnsafeMutablePointer<dc_chatlist_t>
-
-    var length: Int {
-        return dc_chatlist_get_cnt(chatListPointer)
-        // return Int(chatListPointer.pointee.m_cnt)
+    get {
+      return getOptStr("mail_port")
     }
+  }
 
-    // takes ownership of specified pointer
-    init(chatListPointer: UnsafeMutablePointer<dc_chatlist_t>) {
-        self.chatListPointer = chatListPointer
+  /**
+   *  SMTP-server, guessed if left out
+   */
+  class var sendServer: String? {
+    set {
+      setOptStr("send_server", newValue)
     }
-
-    func getChatId(index: Int) -> Int {
-        return Int(dc_chatlist_get_chat_id(chatListPointer, index))
+    get {
+      return getOptStr("send_server")
     }
+  }
 
-    func getMessageId(index: Int) -> Int {
-        return Int(dc_chatlist_get_msg_id(chatListPointer, index))
+  /**
+   *  SMTP-user, guessed if left out
+   */
+  class var sendUser: String? {
+    set {
+      setOptStr("send_user", newValue)
     }
-
-    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)
+    get {
+      return getOptStr("send_user")
     }
+  }
 
-    deinit {
-        dc_chatlist_unref(chatListPointer)
+  /**
+   *  SMTP-password, guessed if left out
+   */
+  class var sendPw: String? {
+    set {
+      setOptStr("send_pw", newValue)
     }
-}
-
-func strToBool(_ value: String?) -> Bool {
-    if let vStr = value {
-        if let vInt = Int(vStr) {
-            return vInt == 1
-        }
-        return false
+    get {
+      return getOptStr("send_pw")
     }
-
-    return false
-}
-
-class MRConfig {
-    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 == "" {
-                return nil
-            }
-            return c
-        }
-
-        return nil
+  }
+
+  /**
+   * SMTP-port, guessed if left out
+   */
+  class var sendPort: String? {
+    set {
+      setOptStr("send_port", newValue)
     }
-
-    private class func setOptStr(_ key: String, _ value: String?) {
-        if let v = value {
-            dc_set_config(mailboxPointer, key, v)
-        }
+    get {
+      return getOptStr("send_port")
     }
+  }
 
-    private class func getBool(_ key: String) -> Bool {
-        return strToBool(getOptStr(key))
+  /**
+   * IMAP-/SMTP-flags as a combination of DC_LP flags, guessed if left out
+   */
+  class var serverFlags: String? {
+    set {
+      setOptStr("server_flags", newValue)
     }
-
-    private class func setBool(_ key: String, _ value: Bool) {
-        let vStr = value ? "1" : "0"
-        setOptStr(key, vStr)
+    get {
+      return getOptStr("server_flags")
     }
+  }
 
-    /**
-     *  Address to display (always needed)
-     */
-    class var addr: String? {
-        set {
-            setOptStr("addr", newValue)
-        }
-        get {
-            return getOptStr("addr")
-        }
+  /**
+   * 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)
     }
-
-    /**
-     *  IMAP-server, guessed if left out
-     */
-    class var mailServer: String? {
-        set {
-            setOptStr("mail_server", newValue)
-        }
-        get {
-            return getOptStr("mail_server")
-        }
+    get {
+      return getOptStr("displayname")
     }
+  }
 
-    /**
-     *  IMAP-username, guessed if left out
-     */
-    class var mailUser: String? {
-        set {
-            setOptStr("mail_user", newValue)
-        }
-        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)
     }
-
-    /**
-     *  IMAP-password (always needed)
-     */
-    class var mailPw: String? {
-        set {
-            setOptStr("mail_pw", newValue)
-        }
-        get {
-            return getOptStr("mail_pw")
-        }
+    get {
+      return getOptStr("selfstatus")
     }
+  }
 
-    /**
-     *  IMAP-port, guessed if left out
-     */
-    class var mailPort: String? {
-        set {
-            setOptStr("mail_port", newValue)
-        }
-        get {
-            return getOptStr("mail_port")
-        }
+  /**
+   * 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)
     }
-
-    /**
-     *  SMTP-server, guessed if left out
-     */
-    class var sendServer: String? {
-        set {
-            setOptStr("send_server", newValue)
-        }
-        get {
-            return getOptStr("send_server")
-        }
+    get {
+      return getOptStr("selfavatar")
     }
+  }
 
-    /**
-     *  SMTP-user, guessed if left out
-     */
-    class var sendUser: String? {
-        set {
-            setOptStr("send_user", newValue)
-        }
-        get {
-            return getOptStr("send_user")
-        }
+  /**
+   * 0=no end-to-end-encryption, 1=prefer end-to-end-encryption (default)
+   */
+  class var e2eeEnabled: Bool {
+    set {
+      setBool("e2ee_enabled", newValue)
     }
-
-    /**
-     *  SMTP-password, guessed if left out
-     */
-    class var sendPw: String? {
-        set {
-            setOptStr("send_pw", newValue)
-        }
-        get {
-            return getOptStr("send_pw")
-        }
+    get {
+      return getBool("e2ee_enabled")
     }
+  }
 
-    /**
-     * SMTP-port, guessed if left out
-     */
-    class var sendPort: String? {
-        set {
-            setOptStr("send_port", newValue)
-        }
-        get {
-            return getOptStr("send_port")
-        }
+  /**
+   * 0=do not send or request read receipts, 1=send and request read receipts (default)
+   */
+  class var mdnsEnabled: Bool {
+    set {
+      setBool("mdns_enabled", newValue)
     }
-
-    /**
-     * IMAP-/SMTP-flags as a combination of DC_LP flags, guessed if left out
-     */
-    class var serverFlags: String? {
-        set {
-            setOptStr("server_flags", newValue)
-        }
-        get {
-            return getOptStr("server_flags")
-        }
+    get {
+      return getBool("mdns_enabled")
     }
+  }
 
-    /**
-     * 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")
-        }
+  /**
+   * 1=watch INBOX-folder for changes (default), 0=do not watch the INBOX-folder
+   */
+  class var inboxWatch: Bool {
+    set {
+      setBool("inbox_watch", newValue)
     }
-
-    /**
-     * 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")
-        }
+    get {
+      return getBool("inbox_watch")
     }
+  }
 
-    /**
-     * 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")
-        }
+  /**
+   * 1=watch Sent-folder for changes (default), 0=do not watch the Sent-folder
+   */
+  class var sentboxWatch: Bool {
+    set {
+      setBool("sentbox_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("sentbox_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=watch DeltaChat-folder for changes (default), 0=do not watch the DeltaChat-folder
+   */
+  class var mvboxWatch: Bool {
+    set {
+      setBool("mvbox_watch", 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_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=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 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("mvbox_move")
     }
+  }
 
-    /**
-     * 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)
     }
-
-    /**
-     * 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")
-        }
+    get {
+      return getBool("save_mime_headers")
     }
+  }
 
-    class var configuredEmail: String {
-        get {
-            return getOptStr("configured_addr") ?? ""
-        }
-        set {}
+  class var configuredEmail: String {
+    get {
+      return getOptStr("configured_addr") ?? ""
     }
+    set {}
+  }
 
-    class var configuredMailServer: String {
-        get {
-            return getOptStr("configured_mail_server") ?? ""
-        }
-        set {}
+  class var configuredMailServer: String {
+    get {
+      return getOptStr("configured_mail_server") ?? ""
     }
+    set {}
+  }
 
-    class var configuredMailUser: String {
-        get {
-            return getOptStr("configured_mail_user") ?? ""
-        }
-        set {}
+  class var configuredMailUser: String {
+    get {
+      return getOptStr("configured_mail_user") ?? ""
     }
+    set {}
+  }
 
-    class var configuredMailPw: String {
-        get {
-            return getOptStr("configured_mail_pw") ?? ""
-        }
-        set {}
+  class var configuredMailPw: String {
+    get {
+      return getOptStr("configured_mail_pw") ?? ""
     }
+    set {}
+  }
 
-    class var configuredMailPort: String {
-        get {
-            return getOptStr("configured_mail_port") ?? ""
-        }
-        set {}
+  class var configuredMailPort: String {
+    get {
+      return getOptStr("configured_mail_port") ?? ""
     }
+    set {}
+  }
 
-    class var configuredSendServer: String {
-        get {
-            return getOptStr("configured_send_server") ?? ""
-        }
-        set {}
+  class var configuredSendServer: String {
+    get {
+      return getOptStr("configured_send_server") ?? ""
     }
+    set {}
+  }
 
-    class var configuredSendUser: String {
-        get {
-            return getOptStr("configured_send_user") ?? ""
-        }
-        set {}
+  class var configuredSendUser: String {
+    get {
+      return getOptStr("configured_send_user") ?? ""
     }
+    set {}
+  }
 
-    class var configuredSendPw: String {
-        get {
-            return getOptStr("configured_send_pw") ?? ""
-        }
-        set {}
+  class var configuredSendPw: String {
+    get {
+      return getOptStr("configured_send_pw") ?? ""
     }
+    set {}
+  }
 
-    class var configuredSendPort: String {
-        get {
-            return getOptStr("configured_send_port") ?? ""
-        }
-        set {}
+  class var configuredSendPort: String {
+    get {
+      return getOptStr("configured_send_port") ?? ""
     }
+    set {}
+  }
 
-    class var configuredServerFlags: String {
-        get {
-            return getOptStr("configured_server_flags") ?? ""
-        }
-        set {}
+  class var configuredServerFlags: String {
+    get {
+      return getOptStr("configured_server_flags") ?? ""
     }
+    set {}
+  }
 
-    /**
-     * Was configured executed beforeß
-     */
-    class var configured: Bool {
-        get {
-            return getBool("configured")
-        }
-        set {}
+  /**
+   * Was configured executed beforeß
+   */
+  class var configured: Bool {
+    get {
+      return getBool("configured")
     }
+    set {}
+  }
 }

+ 180 - 180
deltachat-ios/events.swift

@@ -8,192 +8,192 @@
 
 import UserNotifications
 
-let dc_notificationChanged = Notification.Name(rawValue: "MrEventMsgsChanged")
-let dc_notificationStateChanged = Notification.Name(rawValue: "MrEventStateChanged")
-let dc_notificationIncoming = Notification.Name(rawValue: "MrEventIncomingMsg")
-let dc_notificationBackupProgress = Notification.Name(rawValue: "MrEventBackupProgress")
-let dc_notificationConfigureProgress = Notification.Name(rawValue: "MrEventConfigureProgress")
-let dc_notificationSecureJoinerProgress = Notification.Name(rawValue: "MrEventSecureJoinerProgress")
-let dc_notificationSecureInviterProgress = Notification.Name(rawValue: "MrEventSecureInviterProgress")
-let dc_notificationViewChat = Notification.Name(rawValue: "MrEventViewChat")
+let dcNotificationChanged = Notification.Name(rawValue: "MrEventMsgsChanged")
+let dcNotificationStateChanged = Notification.Name(rawValue: "MrEventStateChanged")
+let dcNotificationIncoming = Notification.Name(rawValue: "MrEventIncomingMsg")
+let dcNotificationBackupProgress = Notification.Name(rawValue: "MrEventBackupProgress")
+let dcNotificationConfigureProgress = Notification.Name(rawValue: "MrEventConfigureProgress")
+let dcNotificationSecureJoinerProgress = Notification.Name(rawValue: "MrEventSecureJoinerProgress")
+let dcNotificationSecureInviterProgress = Notification.Name(rawValue: "MrEventSecureInviterProgress")
+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_HTTP_GET:
-        let urlString = String(cString: data1String)
-        logger.info("network: http get: \(urlString)")
-        guard let url = URL(string: urlString) else {
-            return nil
-        }
-        guard let configText = try? String(contentsOf: url) else {
-            return nil
-        }
-
-        // see the strdup tip here: https://oleb.net/blog/2016/10/swift-array-of-c-strings/#alternative-strdup-and-free
-        let p = UnsafePointer(strdup(configText))
-        return p
-    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: dc_notificationConfigureProgress,
-                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()
-                AppDelegate.appCoordinator.setupInnerViewControllers()
-                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: dc_notificationStateChanged,
-                        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: dc_notificationStateChanged,
-                    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: dc_notificationChanged,
-                    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),
+  switch event {
+  case DC_EVENT_HTTP_GET:
+    let urlString = String(cString: data1String)
+    logger.info("network: http get: \(urlString)")
+    guard let url = URL(string: urlString) else {
+      return nil
+    }
+    guard let configText = try? String(contentsOf: url) else {
+      return nil
+    }
+
+    // see the strdup tip here: https://oleb.net/blog/2016/10/swift-array-of-c-strings/#alternative-strdup-and-free
+    let p = UnsafePointer(strdup(configText))
+    return p
+  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()
+        AppDelegate.appCoordinator.setupInnerViewControllers()
+        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)")
+    }
 
-        DispatchQueue.main.async {
-            nc.post(name: dc_notificationIncoming,
-                    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: dc_notificationBackupProgress,
-                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: dc_notificationSecureInviterProgress,
+    let nc = NotificationCenter.default
+    DispatchQueue.main.async {
+      DispatchQueue.main.async {
+        nc.post(name: dcNotificationStateChanged,
                 object: nil,
-                userInfo: [
-                    "progress": Int(data2),
-                    "error": Int(data2) == 0,
-                    "done": Int(data2) == 1000,
-                ]
-            )
-        }
-    case DC_EVENT_SECUREJOIN_JOINER_PROGRESS:
-        logger.info("securejoin joiner progress \(data1)")
-        let nc = NotificationCenter.default
-        DispatchQueue.main.async {
-            nc.post(
-                name: dc_notificationSecureJoinerProgress,
-                object: nil,
-                userInfo: [
-                    "progress": Int(data2),
-                    "error": Int(data2) == 0,
-                    "done": Int(data2) == 1000,
-                ]
-            )
-        }
-    case DC_EVENT_GET_STRING:
-        // nothing to do for now
-        break
-    default:
-        logger.warning("unknown event: \(event)")
+                userInfo: ["state": "offline"])
+      }
     }
-
-    return nil
+  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,
+        ]
+      )
+    }
+  case DC_EVENT_GET_STRING:
+    // nothing to do for now
+    break
+  default:
+    logger.warning("unknown event: \(event)")
+  }
+
+  return nil
 }

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov