Pārlūkot izejas kodu

Merge pull request #53 from deltachat/AccountSetupFlow

Account setup flow
nayooti 6 gadi atpakaļ
vecāks
revīzija
5b4caf920a
40 mainītis faili ar 1507 papildinājumiem un 275 dzēšanām
  1. 1 1
      .swiftformat
  2. 23 0
      Assets.xcassets/ic_close_18pt.imageset/Contents.json
  3. BIN
      Assets.xcassets/ic_close_18pt.imageset/ic_close_18pt.png
  4. BIN
      Assets.xcassets/ic_close_18pt.imageset/ic_close_18pt_2x.png
  5. BIN
      Assets.xcassets/ic_close_18pt.imageset/ic_close_18pt_3x.png
  6. 23 0
      Assets.xcassets/ic_close_36pt.imageset/Contents.json
  7. BIN
      Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt.png
  8. BIN
      Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_2x.png
  9. BIN
      Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_3x.png
  10. 30 4
      deltachat-ios.xcodeproj/project.pbxproj
  11. 611 0
      deltachat-ios/AccountSetupController.swift
  12. 61 0
      deltachat-ios/ActionCell.swift
  13. 17 2
      deltachat-ios/AppCoordinator.swift
  14. 21 13
      deltachat-ios/AppDelegate.swift
  15. 23 0
      deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/Contents.json
  16. BIN
      deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/ic_close_18pt.png
  17. BIN
      deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/ic_close_18pt_2x.png
  18. BIN
      deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/ic_close_18pt_3x.png
  19. 20 0
      deltachat-ios/Assets.xcassets/ic_close_18pt.imageset/Contents.json
  20. 23 0
      deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/Contents.json
  21. BIN
      deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/ic_close_18pt.png
  22. BIN
      deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/ic_close_18pt_2x.png
  23. BIN
      deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/ic_close_18pt_3x.png
  24. 20 0
      deltachat-ios/Assets.xcassets/ic_close_18pt_3x.imageset/Contents.json
  25. 23 0
      deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/Contents.json
  26. BIN
      deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt.png
  27. BIN
      deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_2x.png
  28. BIN
      deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_3x.png
  29. 1 1
      deltachat-ios/ChatViewController.swift
  30. 15 0
      deltachat-ios/Colors.swift
  31. 73 0
      deltachat-ios/Extensions/Extensions.swift
  32. 74 0
      deltachat-ios/HudHandler.swift
  33. 9 0
      deltachat-ios/Info.plist
  34. 67 44
      deltachat-ios/TextFieldCell.swift
  35. 0 0
      deltachat-ios/TextFieldTableViewCell.swift
  36. 29 177
      deltachat-ios/TopViews/SettingsController.swift
  37. 2 1
      deltachat-ios/Utils.swift
  38. 52 32
      deltachat-ios/Wrapper.swift
  39. 23 0
      deltachat-ios/events.swift
  40. 266 0
      deltachat-ios/libraries/rpgp/librpgp.h

+ 1 - 1
.swiftformat

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

+ 23 - 0
Assets.xcassets/ic_close_18pt.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+    "images": [
+        {
+            "filename": "ic_close_18pt.png",
+            "idiom": "universal",
+            "scale": "1x"
+        },
+        {
+            "filename": "ic_close_18pt_2x.png",
+            "idiom": "universal",
+            "scale": "2x"
+        },
+        {
+            "filename": "ic_close_18pt_3x.png",
+            "idiom": "universal",
+            "scale": "3x"
+        }
+    ],
+    "info": {
+        "author": "xcode",
+        "version": 1
+    }
+}

BIN
Assets.xcassets/ic_close_18pt.imageset/ic_close_18pt.png


BIN
Assets.xcassets/ic_close_18pt.imageset/ic_close_18pt_2x.png


BIN
Assets.xcassets/ic_close_18pt.imageset/ic_close_18pt_3x.png


+ 23 - 0
Assets.xcassets/ic_close_36pt.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+    "images": [
+        {
+            "filename": "ic_close_36pt.png",
+            "idiom": "universal",
+            "scale": "1x"
+        },
+        {
+            "filename": "ic_close_36pt_2x.png",
+            "idiom": "universal",
+            "scale": "2x"
+        },
+        {
+            "filename": "ic_close_36pt_3x.png",
+            "idiom": "universal",
+            "scale": "3x"
+        }
+    ],
+    "info": {
+        "author": "xcode",
+        "version": 1
+    }
+}

BIN
Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt.png


BIN
Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_2x.png


BIN
Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_3x.png


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

@@ -67,7 +67,7 @@
 		78E45E3021D1774200D4B15E /* dc_move.c in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E2E21D1774200D4B15E /* dc_move.c */; };
 		78E45E3321D3CBC000D4B15E /* AppTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3221D3CBC000D4B15E /* AppTabBarController.swift */; };
 		78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3921D3CFBC00D4B15E /* SettingsController.swift */; };
-		78E45E3C21D3D03700D4B15E /* TableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3B21D3D03700D4B15E /* TableViewCell.swift */; };
+		78E45E3C21D3D03700D4B15E /* TextFieldTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3B21D3D03700D4B15E /* TextFieldTableViewCell.swift */; };
 		78E45E3E21D3D28C00D4B15E /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3D21D3D28C00D4B15E /* NavigationController.swift */; };
 		78E45E4021D3D70700D4B15E /* ContactListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E3F21D3D70700D4B15E /* ContactListController.swift */; };
 		78E45E4221D3DB4000D4B15E /* UIViewController+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E4121D3DB4000D4B15E /* UIViewController+Extension.swift */; };
@@ -89,10 +89,15 @@
 		7AE0A5491FC42F65005ECB4B /* NewChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7AE0A5481FC42F65005ECB4B /* NewChatViewController.swift */; };
 		8B6D425BC604F7C43B65D436 /* Pods_deltachat_ios.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6241BE1534A653E79AD5D01D /* Pods_deltachat_ios.framework */; };
 		AE0D26FD1FB1FE88002FAFCE /* ChatListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE0D26FC1FB1FE88002FAFCE /* ChatListController.swift */; };
+		AE38B31822672DFC00EC37A1 /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE38B31722672DFC00EC37A1 /* ActionCell.swift */; };
+		AE38B31A2267328200EC37A1 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE38B3192267328200EC37A1 /* Colors.swift */; };
 		AEACE2DD1FB323CA00DCDD78 /* ChatViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEACE2DC1FB323CA00DCDD78 /* ChatViewController.swift */; };
 		AEACE2DF1FB3246400DCDD78 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEACE2DE1FB3246400DCDD78 /* Message.swift */; };
 		AEACE2E31FB32B5C00DCDD78 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEACE2E21FB32B5C00DCDD78 /* Constants.swift */; };
 		AEACE2E51FB32E1900DCDD78 /* Utils.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEACE2E41FB32E1900DCDD78 /* Utils.swift */; };
+		AEE56D762253431E007DC082 /* AccountSetupController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE56D752253431E007DC082 /* AccountSetupController.swift */; };
+		AEE56D7D2253ADB4007DC082 /* HudHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE56D7C2253ADB4007DC082 /* HudHandler.swift */; };
+		AEE56D80225504DB007DC082 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE56D7F225504DB007DC082 /* Extensions.swift */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -196,7 +201,7 @@
 		78E45E2E21D1774200D4B15E /* dc_move.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = dc_move.c; path = "deltachat-ios/libraries/deltachat-core/src/dc_move.c"; sourceTree = "<group>"; };
 		78E45E3221D3CBC000D4B15E /* AppTabBarController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppTabBarController.swift; sourceTree = "<group>"; };
 		78E45E3921D3CFBC00D4B15E /* SettingsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsController.swift; sourceTree = "<group>"; };
-		78E45E3B21D3D03700D4B15E /* TableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewCell.swift; sourceTree = "<group>"; };
+		78E45E3B21D3D03700D4B15E /* TextFieldTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldTableViewCell.swift; sourceTree = "<group>"; };
 		78E45E3D21D3D28C00D4B15E /* NavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = "<group>"; };
 		78E45E3F21D3D70700D4B15E /* ContactListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListController.swift; sourceTree = "<group>"; };
 		78E45E4121D3DB4000D4B15E /* UIViewController+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIViewController+Extension.swift"; sourceTree = "<group>"; };
@@ -260,10 +265,16 @@
 		8DE110C607A0E4485C43B5FA /* Pods-deltachat-ios.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-ios.debug.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.debug.xcconfig"; sourceTree = "<group>"; };
 		A8615D4600859851E53CAA9C /* Pods-deltachat-ios.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-deltachat-ios.release.xcconfig"; path = "Pods/Target Support Files/Pods-deltachat-ios/Pods-deltachat-ios.release.xcconfig"; sourceTree = "<group>"; };
 		AE0D26FC1FB1FE88002FAFCE /* ChatListController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatListController.swift; sourceTree = "<group>"; };
+		AE38B31722672DFC00EC37A1 /* ActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCell.swift; sourceTree = "<group>"; };
+		AE38B3192267328200EC37A1 /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
+		AEA9CC2F22522DA20061D113 /* librpgp.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = librpgp.h; sourceTree = "<group>"; };
 		AEACE2DC1FB323CA00DCDD78 /* ChatViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatViewController.swift; sourceTree = "<group>"; };
 		AEACE2DE1FB3246400DCDD78 /* Message.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = "<group>"; };
 		AEACE2E21FB32B5C00DCDD78 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
 		AEACE2E41FB32E1900DCDD78 /* Utils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Utils.swift; sourceTree = "<group>"; };
+		AEE56D752253431E007DC082 /* AccountSetupController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountSetupController.swift; sourceTree = "<group>"; };
+		AEE56D7C2253ADB4007DC082 /* HudHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HudHandler.swift; sourceTree = "<group>"; };
+		AEE56D7F225504DB007DC082 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -288,6 +299,7 @@
 			children = (
 				78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */,
 				78E45E4121D3DB4000D4B15E /* UIViewController+Extension.swift */,
+				AEE56D7F225504DB007DC082 /* Extensions.swift */,
 			);
 			path = Extensions;
 			sourceTree = "<group>";
@@ -408,7 +420,9 @@
 				7A9FB4F81FB084E6001FEA36 /* Frameworks */,
 				7DB2DC4CCB84D323E5130F99 /* Pods */,
 			);
+			indentWidth = 2;
 			sourceTree = "<group>";
+			tabWidth = 2;
 		};
 		7A9FB1411FB061E2001FEA36 /* Products */ = {
 			isa = PBXGroup;
@@ -454,8 +468,12 @@
 				AEACE2E21FB32B5C00DCDD78 /* Constants.swift */,
 				78ED839321D5AF8A00243125 /* QrCodeView.swift */,
 				AEACE2E41FB32E1900DCDD78 /* Utils.swift */,
-				78E45E3B21D3D03700D4B15E /* TableViewCell.swift */,
+				78E45E3B21D3D03700D4B15E /* TextFieldTableViewCell.swift */,
 				78ED838221D5379000243125 /* TextFieldCell.swift */,
+				AEE56D752253431E007DC082 /* AccountSetupController.swift */,
+				AEE56D7C2253ADB4007DC082 /* HudHandler.swift */,
+				AE38B31722672DFC00EC37A1 /* ActionCell.swift */,
+				AE38B3192267328200EC37A1 /* Colors.swift */,
 			);
 			path = "deltachat-ios";
 			sourceTree = "<group>";
@@ -514,6 +532,7 @@
 				7070FB4920FF345B000DC258 /* dc_dehtml.c */,
 				7070FB5920FF345E000DC258 /* dc_e2ee.c */,
 				7070FB4520FF345B000DC258 /* dc_hash.c */,
+				AEA9CC2F22522DA20061D113 /* librpgp.h */,
 				7070FB5220FF345D000DC258 /* dc_imex.c */,
 				7070FB5020FF345C000DC258 /* dc_job.c */,
 				7070FB5720FF345E000DC258 /* dc_key.c */,
@@ -795,12 +814,14 @@
 				7AE0A5491FC42F65005ECB4B /* NewChatViewController.swift in Sources */,
 				78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */,
 				78ED838321D5379000243125 /* TextFieldCell.swift in Sources */,
-				78E45E3C21D3D03700D4B15E /* TableViewCell.swift in Sources */,
+				78E45E3C21D3D03700D4B15E /* TextFieldTableViewCell.swift in Sources */,
 				7070FB7220FF345F000DC258 /* dc_key.c in Sources */,
 				7070FB7420FF345F000DC258 /* dc_e2ee.c in Sources */,
 				AE0D26FD1FB1FE88002FAFCE /* ChatListController.swift in Sources */,
 				7070FB6920FF345F000DC258 /* dc_mimeparser.c in Sources */,
+				AEE56D80225504DB007DC082 /* Extensions.swift in Sources */,
 				7A0052C81FBE6CB40048C3BF /* NewContactController.swift in Sources */,
+				AEE56D762253431E007DC082 /* AccountSetupController.swift in Sources */,
 				7070FB6020FF345F000DC258 /* dc_hash.c in Sources */,
 				AEACE2DD1FB323CA00DCDD78 /* ChatViewController.swift in Sources */,
 				7070FB4020FF3421000DC258 /* dc_chat.c in Sources */,
@@ -809,6 +830,7 @@
 				78ED838F21D5927A00243125 /* ProfileViewController.swift in Sources */,
 				78E45E4221D3DB4000D4B15E /* UIViewController+Extension.swift in Sources */,
 				7A9FB1441FB061E2001FEA36 /* AppDelegate.swift in Sources */,
+				AEE56D7D2253ADB4007DC082 /* HudHandler.swift in Sources */,
 				7032FF8F2149C1DB00B7EC83 /* BaseController.swift in Sources */,
 				7070FB9320FF4118000DC258 /* dc_msg.c in Sources */,
 				7070FB6820FF345F000DC258 /* dc_array.c in Sources */,
@@ -819,6 +841,7 @@
 				789E879D21D6DF86003ED1C5 /* ProgressHud.swift in Sources */,
 				7070FB8C20FF4118000DC258 /* dc_contact.c in Sources */,
 				78E45E4C21D404AE00D4B15E /* CustomCell.swift in Sources */,
+				AE38B31A2267328200EC37A1 /* Colors.swift in Sources */,
 				7070FB7020FF345F000DC258 /* dc_lot.c in Sources */,
 				789E879621D6CB58003ED1C5 /* QrCodeReaderController.swift in Sources */,
 				7070FB5F20FF345F000DC258 /* dc_tools.c in Sources */,
@@ -832,6 +855,7 @@
 				7070FB5E20FF345F000DC258 /* dc_token.c in Sources */,
 				7070FB6C20FF345F000DC258 /* dc_keyring.c in Sources */,
 				7A451DB01FB1F84900177250 /* AppCoordinator.swift in Sources */,
+				AE38B31822672DFC00EC37A1 /* ActionCell.swift in Sources */,
 				785BE16821E247F1003BE98C /* MessageInfoViewController.swift in Sources */,
 				AEACE2E31FB32B5C00DCDD78 /* Constants.swift in Sources */,
 			);
@@ -977,6 +1001,7 @@
 					"$(inherited)",
 					"deltachat-ios/libraries/openssl/include",
 					/usr/local/include,
+					"deltachat-ios/libraries/rpgp",
 				);
 				INFOPLIST_FILE = "deltachat-ios/Info.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
@@ -1055,6 +1080,7 @@
 					"$(inherited)",
 					"deltachat-ios/libraries/openssl/include",
 					/usr/local/include,
+					"deltachat-ios/libraries/rpgp",
 				);
 				INFOPLIST_FILE = "deltachat-ios/Info.plist";
 				IPHONEOS_DEPLOYMENT_TARGET = 11.0;

+ 611 - 0
deltachat-ios/AccountSetupController.swift

@@ -0,0 +1,611 @@
+//
+//  AccountSetupController.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 02.04.19.
+//  Copyright © 2019 Jonas Reinsch. All rights reserved.
+//
+
+import SafariServices
+import UIKit
+
+class AccountSetupController: UITableViewController {
+  private var backupProgressObserver: Any?
+  private var configureProgressObserver: Any?
+  private var oauth2Observer: Any?
+
+  private lazy var hudHandler: HudHandler = {
+    let hudHandler = HudHandler(parentView: self.tableView)
+    return hudHandler
+  }()
+
+  private lazy var emailCell: TextFieldCell = {
+    let cell = TextFieldCell.makeEmailCell(delegate: self)
+    cell.textField.tag = 0
+    cell.textField.accessibilityIdentifier = "emailTextField" // will be used to eventually show oAuth-Dialogue when pressing return key
+    cell.setText(text: MRConfig.addr ?? nil)
+    return cell
+  }()
+
+  private lazy var passwordCell: TextFieldCell = {
+    let cell = TextFieldCell.makePasswordCell(delegate: self)
+    cell.textField.tag = 1
+    cell.accessibilityIdentifier = "passwordCell" // will be used to eventually show oAuth-Dialogue when selecting
+    cell.setText(text: MRConfig.mailPw ?? nil)
+    return cell
+  }()
+
+	private lazy var restoreCell: ActionCell = {
+		let cell = ActionCell(title: "Restore from backup")
+		cell.accessibilityIdentifier = "restoreCell"
+		return cell
+	}()
+
+  lazy var imapServerCell: TextFieldCell = {
+    let cell = TextFieldCell(description: "IMAP Server", placeholder: MRConfig.mailServer ?? MRConfig.configuredMailServer, delegate: self)
+    cell.accessibilityIdentifier = "IMAPServerCell"
+    cell.textField.tag = 2
+    return cell
+  }()
+
+  lazy var imapUserCell: TextFieldCell = {
+    let cell = TextFieldCell(description: "IMAP User", placeholder: MRConfig.mailUser ?? MRConfig.configuredMailUser, delegate: self)
+    cell.accessibilityIdentifier = "IMAPUserCell"
+    cell.textField.tag = 3
+    return cell
+  }()
+
+  lazy var imapPortCell: TextFieldCell = {
+    let cell = TextFieldCell(description: "IMAP Port", placeholder: MRConfig.mailPort ?? MRConfig.configuredMailPort, delegate: self)
+    cell.accessibilityIdentifier = "IMAPPortCell"
+    cell.textField.tag = 4
+    return cell
+  }()
+
+  lazy var imapSecurityCell: TextFieldCell = {
+    let text = "\(MRConfig.getImapSecurity())"
+    let cell = TextFieldCell(description: "IMAP Security", placeholder: text, delegate: self)
+    cell.accessibilityIdentifier = "IMAPSecurityCell"
+    cell.textField.tag = 5
+    cell.textField.keyboardType = UIKeyboardType.numberPad
+    return cell
+  }()
+
+  lazy var smtpServerCell: TextFieldCell = {
+    let cell = TextFieldCell(description: "SMTP Server", placeholder: MRConfig.sendServer ?? MRConfig.configuredSendServer, delegate: self)
+    cell.accessibilityIdentifier = "SMTPServerCell"
+    cell.textField.tag = 6
+    return cell
+  }()
+
+  lazy var smtpUserCell: TextFieldCell = {
+    let cell = TextFieldCell(description: "SMTP User", placeholder: MRConfig.sendUser ?? MRConfig.configuredSendUser, delegate: self)
+    cell.accessibilityIdentifier = "SMTPUserCell"
+    cell.textField.tag = 7
+    return cell
+  }()
+
+  lazy var smtpPortCell: TextFieldCell = {
+    let cell = TextFieldCell(description: "SMTP Port", placeholder: MRConfig.sendPort ?? MRConfig.configuredSendPort, delegate: self)
+    cell.accessibilityIdentifier = "SMTPPortCell"
+    cell.textField.tag = 8
+    return cell
+  }()
+
+  lazy var smtpPasswordCell: TextFieldCell = {
+    let cell = TextFieldCell(description: "SMTP Password", placeholder: "*************", delegate: self)
+    cell.accessibilityIdentifier = "SMTPPasswordCell"
+    cell.textField.tag = 9
+    return cell
+  }()
+
+  lazy var smtpSecurityCell: TextFieldCell = {
+    let text = "\(MRConfig.getSmtpSecurity())"
+    let cell = TextFieldCell(description: "SMTP Security", placeholder: text, delegate: self)
+    cell.accessibilityIdentifier = "SMTPSecurityCell"
+    cell.textField.tag = 10
+    cell.textField.keyboardType = UIKeyboardType.numberPad
+    return cell
+  }()
+
+  // this loginButton can be enabled and disabled
+  let loginButton: UIBarButtonItem = UIBarButtonItem(title: "Login", style: .done, target: self, action: #selector(loginButtonPressed))
+
+  private lazy var basicSectionCells: [UITableViewCell] = [emailCell, passwordCell]
+	private lazy var restoreCells: [UITableViewCell] = [restoreCell]
+  private lazy var advancedSectionCells: [UITableViewCell] = [
+    imapServerCell,
+    imapUserCell,
+    imapPortCell,
+    imapSecurityCell,
+    smtpServerCell,
+    smtpUserCell,
+    smtpPortCell,
+    smtpPasswordCell,
+    smtpSecurityCell,
+  ]
+
+  private var advancedSectionShowing: Bool = false
+
+  init() {
+    super.init(style: .grouped)
+  }
+
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  override func viewDidLoad() {
+    super.viewDidLoad()
+    title = "Login to your server"
+    // navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Close", style: .plain, target: self, action: #selector(closeButtonPressed))
+    navigationItem.rightBarButtonItem = loginButton
+  }
+
+  override func viewDidAppear(_ animated: Bool) {
+    super.viewDidAppear(animated)
+    addProgressHudEventListener()
+    // loginButton.isEnabled = false
+	}
+
+  override func viewDidDisappear(_: Bool) {
+    let nc = NotificationCenter.default
+    if let backupProgressObserver = self.backupProgressObserver {
+      nc.removeObserver(backupProgressObserver)
+    }
+    if let configureProgressObserver = self.configureProgressObserver {
+      nc.removeObserver(configureProgressObserver)
+    }
+    if let oauth2Observer = self.oauth2Observer {
+      nc.removeObserver(oauth2Observer)
+    }
+  }
+
+  // MARK: - Table view data source
+
+  override func numberOfSections(in _: UITableView) -> Int {
+    // #warning Incomplete implementation, return the number of sections
+    return 3
+  }
+
+  override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
+    // #warning Incomplete implementation, return the number of rows
+    if section == 0 {
+			return basicSectionCells.count
+		} else if section == 1 {
+			return restoreCells.count
+    } else {
+      return advancedSectionShowing ? advancedSectionCells.count : 0
+    }
+  }
+
+  override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
+    if section == 2 {
+      return "Advanced"
+    } else {
+      return nil
+    }
+  }
+
+  override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
+    if section == 2 {
+      // Advanced Header
+      let advancedView = AdvancedSectionHeader()
+      advancedView.handleTap = toggleAdvancedSection
+      // set tapHandler
+      return advancedView
+
+    } else {
+      return nil
+    }
+  }
+
+  override func tableView(_: UITableView, heightForHeaderInSection _: Int) -> CGFloat {
+    return 36.0
+  }
+
+  override func tableView(_: UITableView, titleForFooterInSection section: Int) -> String? {
+    if section == 0 {
+      return "There are no Delta Chat servers, your data stays on your device!"
+    } else if section == 2{
+      return "For known email providers additional settings are setup automatically. Sometimes IMAP needs to be enabled in the web frontend. Consult your email provider or friends for help"
+		} else {
+			return nil
+		}
+
+  }
+
+  override func tableView(_: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    let section = indexPath.section
+    let row = indexPath.row
+
+    if section == 0 {
+      // basicSection
+      return basicSectionCells[row]
+		} else if section == 1 {
+			return restoreCells[row]
+    } else {
+      // advancedSection
+      return advancedSectionCells[row]
+    }
+  }
+
+  override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+
+		guard let tappedCell = tableView.cellForRow(at: indexPath) else { return }
+		// handle tap on password -> show oAuthDialogue
+    if let textFieldCell = tappedCell as? TextFieldCell {
+      if textFieldCell.accessibilityIdentifier == "passwordCell" {
+        if let emailAdress = textFieldCell.getText() {
+          _ = showOAuthAlertIfNeeded(emailAddress: emailAdress, handleCancel: nil)
+        }
+      }
+    }
+
+		if tappedCell.accessibilityIdentifier == "restoreCell" {
+				self.restoreBackup()
+		}
+  }
+
+  private func toggleAdvancedSection(button: UILabel) {
+    let willShow = !advancedSectionShowing
+
+    // extract indexPaths from advancedCells
+    let advancedIndexPaths: [IndexPath] = advancedSectionCells.indices.map { IndexPath(row: $0, section: 2) }
+
+    // advancedSectionCells.indices.map({indexPaths.append(IndexPath(row: $0, section: 1))}
+
+    // set flag before delete/insert operation, because cellForRowAt will be triggered and uses this flag
+    advancedSectionShowing = willShow
+
+    button.text = willShow ? "Hide" : "Show"
+
+    if willShow {
+      tableView.insertRows(at: advancedIndexPaths, with: .fade)
+    } else {
+      tableView.deleteRows(at: advancedIndexPaths, with: .fade)
+    }
+  }
+
+  @objc private func loginButtonPressed() {
+    guard let emailAddress = emailCell.getText() else {
+      return // handle case when either email or pw fields are empty
+    }
+
+    let oAuthStarted = showOAuthAlertIfNeeded(emailAddress: emailAddress, handleCancel: loginButtonPressed) // if canceled we will run this method again but this time oAuthStarted will be false
+
+    if oAuthStarted {
+      // the loginFlow will be handled by oAuth2
+      return
+    }
+
+    let password = passwordCell.getText() ?? "" // empty passwords are ok -> for oauth there is no password needed
+    login(emailAddress: emailAddress, password: password)
+  }
+
+  private func login(emailAddress: String, password: String, skipAdvanceSetup: Bool = false) {
+    MRConfig.addr = emailAddress
+    MRConfig.mailPw = password
+    if !skipAdvanceSetup {
+      evaluluateAdvancedSetup() // this will set MRConfig related to advanced fields
+    }
+    dc_configure(mailboxPointer)
+    hudHandler.showBackupHud("Configuring account")
+  }
+
+  @objc func closeButtonPressed() {
+    dismiss(animated: true, completion: nil)
+  }
+
+  // returns true if needed
+  private func showOAuthAlertIfNeeded(emailAddress: String, handleCancel: (() -> Void)?) -> Bool {
+    if MRConfig.getAuthFlags() == 4 {
+      // user has previously denied oAuth2-setup
+      return false
+    }
+
+    guard let oAuth2UrlPointer = dc_get_oauth2_url(mailboxPointer, emailAddress, "chat.delta:/auth") else {
+      return false
+    }
+
+    let oAuth2Url = String(cString: oAuth2UrlPointer)
+
+    if let url = URL(string: oAuth2Url) {
+      let title = "Continue with simplified setup"
+      // swiftlint:disable all
+      let message = "The entered e-mail address supports a simplified setup (oAuth2).\n\nIn the next step, please allow Delta Chat to act as your Chat with E-Mail app.\n\nThere are no Delta Chat servers, your data stays on your device."
+
+      let oAuthAlertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
+      let confirm = UIAlertAction(title: "Confirm", style: .default, handler: {
+        [unowned self] _ in
+        let nc = NotificationCenter.default
+        self.oauth2Observer = nc.addObserver(self, selector: #selector(self.oauthLoginApproved), name: NSNotification.Name("oauthLoginApproved"), object: nil)
+        self.launchOAuthBrowserWindow(url: url)
+      })
+      let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: {
+        _ in
+        MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_NORMAL))
+        handleCancel?()
+      })
+      oAuthAlertController.addAction(confirm)
+      oAuthAlertController.addAction(cancel)
+
+      present(oAuthAlertController, animated: true, completion: nil)
+      return true
+    } else {
+      return false
+    }
+  }
+
+  @objc func oauthLoginApproved(notification: Notification) {
+    guard let userInfo = notification.userInfo, let token = userInfo["token"] as? String, let emailAddress = emailCell.getText() else {
+      return
+    }
+    passwordCell.setText(text: token)
+    MRConfig.setAuthFlags(flags: Int(DC_LP_AUTH_OAUTH2))
+    login(emailAddress: emailAddress, password: token, skipAdvanceSetup: true)
+  }
+
+  private func launchOAuthBrowserWindow(url: URL) {
+    UIApplication.shared.open(url) // this opens safari as seperate app
+  }
+
+  private func addProgressHudEventListener() {
+    let nc = NotificationCenter.default
+    backupProgressObserver = nc.addObserver(
+      forName: dcNotificationBackupProgress,
+      object: nil,
+      queue: nil
+    ) {
+      notification in
+      if let ui = notification.userInfo {
+        if ui["error"] as! Bool {
+          self.hudHandler.setHudError(ui["errorMessage"] as? String)
+        } else if ui["done"] as! Bool {
+          self.hudHandler.setHudDone(callback: self.handleLoginSuccess)
+        } else {
+          self.hudHandler.setHudProgress(ui["progress"] as! Int)
+        }
+      }
+    }
+    configureProgressObserver = nc.addObserver(
+      forName: dcNotificationConfigureProgress,
+      object: nil,
+      queue: nil
+    ) {
+      notification in
+      if let ui = notification.userInfo {
+        if ui["error"] as! Bool {
+          self.hudHandler.setHudError(ui["errorMessage"] as? String)
+        } else if ui["done"] as! Bool {
+          self.hudHandler.setHudDone(callback: self.handleLoginSuccess)
+        } else {
+          self.hudHandler.setHudProgress(ui["progress"] as! Int)
+        }
+      }
+    }
+  }
+
+  private func evaluluateAdvancedSetup() {
+    for cell in advancedSectionCells {
+      if let textFieldCell = cell as? TextFieldCell {
+        switch cell.accessibilityIdentifier {
+        case "IMAPServerCell":
+          MRConfig.mailServer = textFieldCell.getText() ?? nil
+        case "IMAPUserCell":
+          MRConfig.mailUser = textFieldCell.getText() ?? nil
+        case "IMAPPortCell":
+          MRConfig.mailPort = textFieldCell.getText() ?? nil
+        case "IMAPSecurityCell":
+          let flag = 0
+          MRConfig.setImapSecurity(imapFlags: flag)
+        case "SMTPServerCell":
+          MRConfig.sendServer = textFieldCell.getText() ?? nil
+        case "SMTPSUserCell":
+          MRConfig.sendUser = textFieldCell.getText() ?? nil
+        case "SMTPPortCell":
+          MRConfig.sendPort = textFieldCell.getText() ?? nil
+        case "SMTPPasswordCell":
+          MRConfig.sendPw = textFieldCell.getText() ?? nil
+        case "SMTPSecurityCell":
+          let flag = 0
+          MRConfig.setSmtpSecurity(smptpFlags: flag)
+        default:
+          logger.info("unknown identifier", cell.accessibilityIdentifier ?? "")
+        }
+      }
+    }
+  }
+
+	private func restoreBackup() {
+		logger.info("restoring backup")
+		if MRConfig.configured {
+			return
+		}
+		let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
+		if !documents.isEmpty {
+			logger.info("looking for backup in: \(documents[0])")
+
+			if let file = dc_imex_has_backup(mailboxPointer, documents[0]) {
+				logger.info("restoring backup: \(String(cString: file))")
+
+				hudHandler.showBackupHud("Restoring Backup")
+				dc_imex(mailboxPointer, DC_IMEX_IMPORT_BACKUP, file, nil)
+
+				return
+			}
+
+			let alert = UIAlertController(title: "Can not restore", message: "No Backup found", preferredStyle: .alert)
+			alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: { _ in
+
+			}))
+			present(alert, animated: true, completion: nil)
+			return
+		}
+
+		logger.error("no documents directory found")
+	}
+
+  private func handleLoginSuccess() {
+    // used when login hud successfully went trough
+    dismiss(animated: true, completion: nil)
+  }
+}
+
+extension AccountSetupController: UITextFieldDelegate {
+  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
+    let currentTag = textField.tag
+
+    if textField.accessibilityIdentifier == "emailTextField", showOAuthAlertIfNeeded(emailAddress: textField.text ?? "", handleCancel: {
+      // special case: email field should check for potential oAuth
+
+      // this will activate passwordTextField if oAuth-Dialogue was canceled
+      self.passwordCell.textField.becomeFirstResponder()
+    }) {
+      // all the action is defined in if condition
+    } else {
+      if let nextField = tableView.viewWithTag(currentTag + 1) as? UITextField {
+        if nextField.tag > 1, !advancedSectionShowing {
+          // gets here when trying to activate a collapsed cell
+          return false
+        }
+        nextField.becomeFirstResponder()
+      }
+    }
+
+    return false
+  }
+}
+
+class AdvancedSectionHeader: UIView {
+  var handleTap: ((UILabel) -> Void)?
+
+  private var label: UILabel = {
+    let label = UILabel()
+    label.text = "ADVANCED"
+    label.font = UIFont.systemFont(ofSize: 15)
+    label.textColor = UIColor.darkGray
+    return label
+  }()
+
+  /*
+   why UILabel, why no UIButton? For unknown reasons UIButton's target function was not triggered when one of the textfields in the tableview was active -> used label as workaround
+   */
+  private lazy var toggleButton: UILabel = {
+    let label = UILabel()
+    label.text = "Show"
+    label.font = UIFont.systemFont(ofSize: 15, weight: .medium)
+    label.textColor = UIColor.systemBlue
+    return label
+  }()
+
+//
+  //	private var toggleButton:UIButton = {
+  //		let button = UIButton(type: .system)
+  //		button.setTitle("Show", for: .normal)
+  //		button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside )
+  //		//button.target(forAction: #selector(buttonTapped(_:)), withSender: self)
+  //		return button
+  //	}()
+
+  init() {
+    super.init(frame: .zero) // will be constraint from tableViewDelegate
+    setupSubviews()
+    let tap = UITapGestureRecognizer(target: self, action: #selector(viewTapped)) // use this if the whole header is supposed to be clickable
+    addGestureRecognizer(tap)
+  }
+
+  required init?(coder _: NSCoder) {
+    fatalError("init(coder:) has not been implemented")
+  }
+
+  func setupSubviews() {
+    addSubview(label)
+    label.translatesAutoresizingMaskIntoConstraints = false
+    label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 15).isActive = true
+    label.centerYAnchor.constraint(equalTo: centerYAnchor, constant: 0).isActive = true
+    addSubview(toggleButton)
+    toggleButton.translatesAutoresizingMaskIntoConstraints = false
+
+    toggleButton.leadingAnchor.constraint(equalTo: trailingAnchor, constant: -60).isActive = true // since button will change title it should be left aligned
+    toggleButton.centerYAnchor.constraint(equalTo: label.centerYAnchor, constant: 0).isActive = true
+  }
+
+  @objc func buttonTapped(_: UIButton) {
+    // handleTap?(button)
+  }
+
+  @objc func viewTapped() {
+    handleTap?(toggleButton)
+  }
+}
+
+/*
+ class InputTableViewCell: UITableViewCell {
+ lazy var inputField: UITextField = {
+ let textField = UITextField()
+ return textField
+ }()
+
+ init() {
+ super.init(style: .default, reuseIdentifier: nil)
+ setupView()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ contentView.addSubview(inputField)
+ inputField.translatesAutoresizingMaskIntoConstraints = false
+ inputField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0).isActive = true
+ inputField.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true
+ inputField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true
+ inputField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 100).isActive = true
+ inputField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
+ }
+ public func getText() -> String? {
+ return inputField.text
+ }
+ }
+
+ class PasswordInputCell: UITableViewCell {
+ lazy var inputField: UITextField = {
+ let textField = UITextField()
+ textField.isSecureTextEntry = true
+ return textField
+ }()
+
+ // TODO: to add Eye-icon -> uncomment -> add to inputField.rightView
+ /*
+  lazy var makeVisibleIcon: UIImageView = {
+  let view = UIImageView(image: )
+  return view
+  }()
+  */
+ init() {
+ super.init(style: .default, reuseIdentifier: nil)
+ setupView()
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ private func setupView() {
+ contentView.addSubview(inputField)
+ inputField.translatesAutoresizingMaskIntoConstraints = false
+ inputField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0).isActive = true
+ inputField.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true
+ inputField.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5).isActive = true
+ inputField.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 100).isActive = true
+ inputField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
+ }
+
+ public func getText() -> String? {
+ return inputField.text
+ }
+ }
+
+ */

+ 61 - 0
deltachat-ios/ActionCell.swift

@@ -0,0 +1,61 @@
+//
+//  ActionCell.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 17.04.19.
+//  Copyright © 2019 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+
+// a cell with a centered label in system blue
+
+class ActionCell: UITableViewCell {
+
+	var actionTitle: String? {
+		didSet {
+			actionLabel.text = actionTitle
+		}
+	}
+
+	private lazy var actionLabel:UILabel = {
+		let label = UILabel()
+		label.text = actionTitle
+		label.textColor = UIColor.systemBlue
+		return label
+	}()
+
+	// use this constructor if cell won't be reused
+	init(title: String) {
+		actionTitle = title
+		super.init(style: .default, reuseIdentifier: nil)
+		setupSubviews()
+	}
+
+	override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+		super.init(style: style, reuseIdentifier: reuseIdentifier)
+		setupSubviews()
+	}
+
+	required init?(coder aDecoder: NSCoder) {
+		fatalError("init(coder:) has not been implemented")
+	}
+
+	override func awakeFromNib() {
+			super.awakeFromNib()
+			// Initialization code
+	}
+
+	override func setSelected(_ selected: Bool, animated: Bool) {
+			// no selection style ...
+	}
+
+	private func setupSubviews() {
+		contentView.addSubview(actionLabel)
+		actionLabel.translatesAutoresizingMaskIntoConstraints = false
+		actionLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor, constant: 0).isActive = true
+		actionLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor, constant: 0).isActive = true
+	}
+
+}

+ 17 - 2
deltachat-ios/AppCoordinator.swift

@@ -15,15 +15,30 @@ protocol Coordinator {
 class AppCoordinator: Coordinator {
   let baseController = BaseController()
 
+  private var appTabBarController: AppTabBarController = AppTabBarController()
+
   func setupViewControllers(window: UIWindow) {
-    window.rootViewController = AppTabBarController()
+    window.rootViewController = appTabBarController
     window.makeKeyAndVisible()
   }
 
+  func presentAccountSetup(animated: Bool) {
+    let accountSetupController = AccountSetupController()
+    let accountSetupNavigationController = UINavigationController(rootViewController: accountSetupController)
+    appTabBarController.present(accountSetupNavigationController, animated: animated, completion: nil)
+  }
+
   func setupInnerViewControllers() {
     let chatListController = ChatListController()
     let chatNavigationController = UINavigationController(rootViewController: chatListController)
-
     baseController.present(chatNavigationController, animated: false, completion: nil)
   }
+
+  /*
+   func setupAccountSetup() {
+     let accountSetupController = AccountSetupController()
+     let accountSetupNavigationController = UINavigationController(rootViewController: accountSetupController)
+     baseController.present(accountSetupNavigationController, animated: false, completion: nil)
+   }
+   */
 }

+ 21 - 13
deltachat-ios/AppDelegate.swift

@@ -47,6 +47,14 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     return []
   }
 
+  func application(_: UIApplication, open url: URL, options _: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
+    // gets here when app returns from oAuth2-Setup process - the url contains the provided token
+    if let params = url.queryParameters, let token = params["code"] {
+      NotificationCenter.default.post(name: NSNotification.Name("oauthLoginApproved"), object: nil, userInfo: ["token": token])
+    }
+    return true
+  }
+
   func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
     DBDebugToolkit.setup()
     DBDebugToolkit.setupCrashReporting()
@@ -62,15 +70,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
     guard let window = window else {
       fatalError("window was nil in app delegate")
     }
+    // setup deltachat core context
+    //       - second param remains nil (user data for more than one mailbox)
+    open()
+    let isConfigured = dc_is_configured(mailboxPointer) != 0
     AppDelegate.appCoordinator.setupViewControllers(window: window)
-
     UIApplication.shared.setMinimumBackgroundFetchInterval(UIApplication.backgroundFetchIntervalMinimum)
-
     start()
-    open()
-
     registerForPushNotifications()
-
+    if !isConfigured {
+      AppDelegate.appCoordinator.presentAccountSetup(animated: false)
+    }
     return true
   }
 
@@ -131,6 +141,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
   func open() {
     logger.info("open: \(dbfile())")
 
+    if mailboxPointer == nil {
+      mailboxPointer = dc_context_new(callback_ios, nil, "iOS")
+      guard mailboxPointer != nil else {
+        fatalError("Error: dc_context_new returned nil")
+      }
+    }
     _ = dc_open(mailboxPointer, dbfile(), nil)
   }
 
@@ -157,14 +173,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterD
       return
     }
 
-    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")
-      }
-    }
-
     state = .running
 
     DispatchQueue.global(qos: .background).async {

+ 23 - 0
deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_18pt.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_18pt_2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_18pt_3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

BIN
deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/ic_close_18pt.png


BIN
deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/ic_close_18pt_2x.png


BIN
deltachat-ios/Assets.xcassets/ic_close_18pt-1.imageset/ic_close_18pt_3x.png


+ 20 - 0
deltachat-ios/Assets.xcassets/ic_close_18pt.imageset/Contents.json

@@ -0,0 +1,20 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 23 - 0
deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_18pt.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_18pt_2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_18pt_3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

BIN
deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/ic_close_18pt.png


BIN
deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/ic_close_18pt_2x.png


BIN
deltachat-ios/Assets.xcassets/ic_close_18pt_2x.imageset/ic_close_18pt_3x.png


+ 20 - 0
deltachat-ios/Assets.xcassets/ic_close_18pt_3x.imageset/Contents.json

@@ -0,0 +1,20 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 23 - 0
deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/Contents.json

@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_36pt.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_36pt_2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "ic_close_36pt_3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

BIN
deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt.png


BIN
deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_2x.png


BIN
deltachat-ios/Assets.xcassets/ic_close_36pt.imageset/ic_close_36pt_3x.png


+ 1 - 1
deltachat-ios/ChatViewController.swift

@@ -95,7 +95,7 @@ class ChatViewController: MessagesViewController {
       ids = Utils.copyAndFreeArrayWithLen(inputArray: cMessageIds, len: count)
     }
 
-    let markIds: [UInt32] = ids.map { return UInt32($0) }
+    let markIds: [UInt32] = ids.map { UInt32($0) }
     dc_markseen_msgs(mailboxPointer, UnsafePointer(markIds), Int32(ids.count))
 
     return ids.map {

+ 15 - 0
deltachat-ios/Colors.swift

@@ -0,0 +1,15 @@
+//
+//  Colors.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 17.04.19.
+//  Copyright © 2019 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+extension UIColor {
+	static var systemBlue: UIColor {
+		return UIButton(type: .system).tintColor
+	}
+}

+ 73 - 0
deltachat-ios/Extensions/Extensions.swift

@@ -0,0 +1,73 @@
+//
+//  String+Extension.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 03.04.19.
+//  Copyright © 2019 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+extension String {
+  func containsCharacters() -> Bool {
+    return !trimmingCharacters(in: [" "]).isEmpty
+  }
+}
+
+
+
+extension URL {
+  public var queryParameters: [String: String]? {
+    guard
+      let components = URLComponents(url: self, resolvingAgainstBaseURL: true),
+      let queryItems = components.queryItems else { return nil }
+    return queryItems.reduce(into: [String: String]()) { result, item in
+      result[item.name] = item.value
+    }
+  }
+}
+
+extension Dictionary {
+  func percentEscaped() -> String {
+    return map { key, value in
+      let escapedKey = "\(key)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
+      let escapedValue = "\(value)".addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed) ?? ""
+      return escapedKey + "=" + escapedValue
+    }
+    .joined(separator: "&")
+  }
+}
+
+extension CharacterSet {
+  static let urlQueryValueAllowed: CharacterSet = {
+    let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
+    let subDelimitersToEncode = "!$&'()*+,;="
+
+    var allowed = CharacterSet.urlQueryAllowed
+    allowed.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")
+    return allowed
+  }()
+}
+
+extension URLSession {
+  func synchronousDataTask(request: URLRequest) -> (Data?, URLResponse?, Error?) {
+    var data: Data?
+    var response: URLResponse?
+    var error: Error?
+
+    let semaphore = DispatchSemaphore(value: 0)
+
+    let task = dataTask(with: request) {
+      data = $0
+      response = $1
+      error = $2
+
+      semaphore.signal()
+    }
+    task.resume()
+
+    _ = semaphore.wait(timeout: .distantFuture)
+
+    return (data, response, error)
+  }
+}

+ 74 - 0
deltachat-ios/HudHandler.swift

@@ -0,0 +1,74 @@
+//
+//  HudHandler.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 02.04.19.
+//  Copyright © 2019 Jonas Reinsch. All rights reserved.
+//
+
+import JGProgressHUD
+import UIKit
+
+class HudHandler {
+  var backupHud: JGProgressHUD?
+  unowned var view: UIView
+
+  init(parentView: UIView) {
+    view = parentView
+  }
+
+  func setHudProgress(_ progress: Int) {
+    if let hud = self.backupHud {
+      hud.progress = Float(progress) / 1000.0
+      hud.detailTextLabel.text = "\(progress / 10)% Complete"
+    }
+  }
+
+  func showBackupHud(_ text: String) {
+    DispatchQueue.main.async {
+      let hud = JGProgressHUD(style: .dark)
+      hud.vibrancyEnabled = true
+      hud.indicatorView = JGProgressHUDPieIndicatorView()
+      hud.detailTextLabel.text = "0% Complete"
+      hud.textLabel.text = text
+      hud.show(in: self.view)
+      self.backupHud = hud
+    }
+  }
+
+  func setHudError(_ message: String?) {
+    if let hud = self.backupHud {
+      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+        UIView.animate(
+          withDuration: 0.1, animations: {
+            hud.textLabel.text = message ?? "Error"
+            hud.detailTextLabel.text = nil
+            hud.indicatorView = JGProgressHUDErrorIndicatorView()
+          }
+        )
+        hud.dismiss(afterDelay: 5.0)
+      }
+    }
+  }
+
+  func setHudDone(callback: (() -> Void)?) {
+    let delay = 1.0
+
+    if let hud = self.backupHud {
+      DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(500)) {
+        UIView.animate(
+          withDuration: 0.1, animations: {
+            hud.textLabel.text = "Success"
+            hud.detailTextLabel.text = nil
+            hud.indicatorView = JGProgressHUDSuccessIndicatorView()
+          }
+        )
+      }
+
+      DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
+        callback?()
+        hud.dismiss()
+      }
+    }
+  }
+}

+ 9 - 0
deltachat-ios/Info.plist

@@ -55,5 +55,14 @@
 		<string>UIInterfaceOrientationLandscapeLeft</string>
 		<string>UIInterfaceOrientationLandscapeRight</string>
 	</array>
+	<key>CFBundleURLTypes</key>
+	<array>
+		<dict>
+			<key>CFBundleURLSchemes</key>
+			<array>
+				<string>chat.delta</string>
+			</array>
+		</dict>
+	</array>
 </dict>
 </plist>

+ 67 - 44
deltachat-ios/TextFieldCell.swift

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

+ 0 - 0
deltachat-ios/TableViewCell.swift → deltachat-ios/TextFieldTableViewCell.swift


+ 29 - 177
deltachat-ios/TopViews/SettingsController.swift

@@ -16,16 +16,16 @@ internal final class SettingsViewController: QuickTableViewController {
   let documentInteractionController = UIDocumentInteractionController()
   var backupProgressObserver: Any?
   var configureProgressObserver: Any?
-  var backupHud: JGProgressHUD?
 
-  // MARK: - View lifecycle
+  private lazy var hudHandler: HudHandler = {
+    let hudHandler = HudHandler(parentView: self.tableView)
+    return hudHandler
+  }()
 
   override func viewDidLoad() {
     super.viewDidLoad()
     title = "Settings"
-
     documentInteractionController.delegate = self as? UIDocumentInteractionControllerDelegate
-
     setTable()
   }
 
@@ -36,15 +36,14 @@ internal final class SettingsViewController: QuickTableViewController {
       forName: dcNotificationBackupProgress,
       object: nil,
       queue: nil
-    ) {
-      notification in
+    ) { notification in
       if let ui = notification.userInfo {
         if ui["error"] as! Bool {
-          self.setHudError(ui["errorMessage"] as? String)
+          self.hudHandler.setHudError(ui["errorMessage"] as? String)
         } else if ui["done"] as! Bool {
-          self.setHudDone()
+          self.hudHandler.setHudDone(callback: nil)
         } else {
-          self.setHudProgress(ui["progress"] as! Int)
+          self.hudHandler.setHudProgress(ui["progress"] as! Int)
         }
       }
     }
@@ -52,15 +51,14 @@ internal final class SettingsViewController: QuickTableViewController {
       forName: dcNotificationConfigureProgress,
       object: nil,
       queue: nil
-    ) {
-      notification in
+    ) { notification in
       if let ui = notification.userInfo {
         if ui["error"] as! Bool {
-          self.setHudError(ui["errorMessage"] as? String)
+          self.hudHandler.setHudError(ui["errorMessage"] as? String)
         } else if ui["done"] as! Bool {
-          self.setHudDone()
+          self.hudHandler.setHudDone(callback: nil)
         } else {
-          self.setHudProgress(ui["progress"] as! Int)
+          self.hudHandler.setHudProgress(ui["progress"] as! Int)
         }
       }
     }
@@ -81,62 +79,6 @@ internal final class SettingsViewController: QuickTableViewController {
     }
   }
 
-  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 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()
-
-      hud.detailTextLabel.text = "0% Complete"
-      hud.textLabel.text = text
-      hud.show(in: self.view)
-
-      self.backupHud = hud
-    }
-  }
-
   override func viewDidDisappear(_ animated: Bool) {
     super.viewDidDisappear(animated)
 
@@ -150,50 +92,21 @@ internal final class SettingsViewController: QuickTableViewController {
   }
 
   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) }),
+      TapActionRow(text: "Create backup", action: { [weak self] in self?.createBackup($0) }),
     ]
 
-    let deleteRow = TapActionRow(title: "Delete Account", action: { [weak self] in self?.deleteAccount($0) })
-
-    if MRConfig.configured {
-      backupRows.removeLast()
-    }
+    let deleteRow = TapActionRow(text: "Delete Account", action: { [weak self] in self?.deleteAccount($0) })
 
     tableContents = [
-      Section(
-        title: "Basics",
-        rows: basicsRows
-      ),
-
       Section(
         title: "User Details",
         rows: [
           NavigationRow(text: "Display Name", detailText: .value1(MRConfig.displayname ?? ""), action: editCell()),
           NavigationRow(text: "Status", detailText: .value1(MRConfig.selfstatus ?? ""), action: editCell()),
+          TapActionRow(text: "Configure my Account", action: { [weak self] in self?.presentAccountSetup($0) }),
         ]
       ),
-
-      Section(
-        title: "Advanced",
-        rows: [
-          NavigationRow(text: "IMAP Server", detailText: .value1(MRConfig.mailServer ?? MRConfig.configuredMailServer), action: editCell()),
-          NavigationRow(text: "IMAP User", detailText: .value1(MRConfig.mailUser ?? MRConfig.configuredMailUser), action: editCell()),
-          NavigationRow(text: "IMAP Port", detailText: .value1(MRConfig.mailPort ?? MRConfig.configuredMailPort), action: editCell()),
-
-          NavigationRow(text: "SMTP Server", detailText: .value1(MRConfig.sendServer ?? MRConfig.configuredSendServer), action: editCell()),
-          NavigationRow(text: "SMTP User", detailText: .value1(MRConfig.sendUser ?? MRConfig.configuredSendUser), action: editCell()),
-          NavigationRow(text: "SMTP Port", detailText: .value1(MRConfig.sendPort ?? MRConfig.configuredSendPort), action: editCell()),
-          NavigationRow(text: "SMTP Password", detailText: .value1("********"), action: editCell())
-        ]
-      ),
-
       Section(
         title: "Flags",
         rows: [
@@ -213,13 +126,11 @@ internal final class SettingsViewController: QuickTableViewController {
       ),
 
       Section(title: "Danger", rows: [
-        deleteRow
+        deleteRow,
       ]),
     ]
   }
 
-  // MARK: - Actions
-
   // FIXME: simplify this method
   // swiftlint:disable cyclomatic_complexity
   private func editCell() -> (Row) -> Void {
@@ -259,6 +170,7 @@ internal final class SettingsViewController: QuickTableViewController {
         default:
           logger.info("unknown title", title)
         }
+        dc_configure(mailboxPointer)
         return
       }
 
@@ -275,41 +187,18 @@ internal final class SettingsViewController: QuickTableViewController {
         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 {
+          dc_configure(mailboxPointer)
           self?.setTable()
           self?.tableView.reloadData()
         }
@@ -321,23 +210,6 @@ internal final class SettingsViewController: QuickTableViewController {
 
       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)
@@ -353,7 +225,7 @@ internal final class SettingsViewController: QuickTableViewController {
     let documents = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
     if !documents.isEmpty {
       logger.info("create backup in \(documents)")
-      showBackupHud("Creating Backup")
+      hudHandler.showBackupHud("Creating Backup")
       DispatchQueue.main.async {
         dc_imex(mailboxPointer, DC_IMEX_EXPORT_BACKUP, documents[0], nil)
       }
@@ -362,43 +234,17 @@ internal final class SettingsViewController: QuickTableViewController {
     }
   }
 
-  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))")
-
-        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
-    }
-
-    logger.error("no documents directory found")
-  }
 
   private func configure(_: Row) {
-    showBackupHud("Configuring account")
+    hudHandler.showBackupHud("Configuring account")
     dc_configure(mailboxPointer)
   }
 
   private func deleteAccount(_: Row) {
     logger.info("deleting account")
-    let appDelegate = UIApplication.shared.delegate as! AppDelegate
+    guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
+      return
+    }
 
     let dbfile = appDelegate.dbfile()
     let dburl = URL(fileURLWithPath: dbfile, isDirectory: false)
@@ -426,4 +272,10 @@ internal final class SettingsViewController: QuickTableViewController {
 
     present(alert, animated: true, completion: nil)
   }
+
+  private func presentAccountSetup(_: Row) {
+		if let nav = self.navigationController {
+			nav.pushViewController(AccountSetupController(), animated: true)
+		}	
+  }
 }

+ 2 - 1
deltachat-ios/Utils.swift

@@ -51,7 +51,8 @@ struct Utils {
     let lenArray = dc_array_get_cnt(inputArray)
     if lenArray <= skipEnd || lenArray == 0 {
       dc_array_unref(inputArray)
-      return [] }
+      return []
+    }
 
     let start = lenArray - 1 - skipEnd
     let end = max(0, start - len)

+ 52 - 32
deltachat-ios/Wrapper.swift

@@ -644,47 +644,67 @@ class MRConfig {
       return getOptStr("send_port")
     }
   }
-    
-      /**
+
+  /**
    * IMAP-/SMTP-flags as a combination of DC_LP flags, guessed if left out
    */
-    
 
   private class var serverFlags: Int {
     set {
       setOptStr("server_flags", "\(newValue)")
     }
     get {
-        if let str = getOptStr("server_flags") {
-            return Int(str) ?? 0
-        } else {
-            return 0
-        }
+      if let str = getOptStr("server_flags") {
+        return Int(str) ?? 0
+      } else {
+        return 0
+      }
     }
   }
-    
-    
-    class func setImapSecurity(imapFlags flags: Int) {
-        var sf = serverFlags
-        sf = sf & ~0x700 // TODO: should be DC_LP_IMAP_SOCKET_FLAGS - could not be found
-        sf = sf | flags
-        serverFlags = sf
-    }
-    
-    class func setSmtpSecurity(smptpFlags flags: Int) {
-        var sf = serverFlags
-        sf = sf & ~0x70000 // TODO: should be DC_LP_SMTP_SOCKET_FLAGS - could not be found
-        sf = sf | flags
-        serverFlags = sf
-    }
-    
-    class func setAuthFlags(flags: Int) {
-        var sf = serverFlags
-        sf = sf & ~0x6 // TODO: should be DC_LP_AUTH_FLAGS - could not be found
-        sf = sf | flags
-        serverFlags = sf
-    }
-    
+
+  class func setImapSecurity(imapFlags flags: Int) {
+    var sf = serverFlags
+    sf = sf & ~0x700 // TODO: should be DC_LP_IMAP_SOCKET_FLAGS - could not be found
+    sf = sf | flags
+    serverFlags = sf
+  }
+
+  class func setSmtpSecurity(smptpFlags flags: Int) {
+    var sf = serverFlags
+    sf = sf & ~0x70000 // TODO: should be DC_LP_SMTP_SOCKET_FLAGS - could not be found
+    sf = sf | flags
+    serverFlags = sf
+  }
+
+  class func setAuthFlags(flags: Int) {
+    var sf = serverFlags
+    sf = sf & ~0x6 // TODO: should be DC_LP_AUTH_FLAGS - could not be found
+    sf = sf | flags
+    serverFlags = sf
+  }
+
+  // returns one of DC_LP_IMAP_SOCKET_STARTTLS, DC_LP_IMAP_SOCKET_SSL,
+  class func getImapSecurity() -> Int {
+    var sf = serverFlags
+    sf = sf & 0x700
+    return sf
+  }
+
+  // returns one of DC_LP_SMTP_SOCKET_STARTTLS, DC_LP_SMTP_SOCKET_SSL,
+  class func getSmtpSecurity() -> Int {
+    var sf = serverFlags
+    sf = sf & 0x70000
+    return sf
+  }
+
+  // returns on of DC_LP_AUTH_OAUTH2 or 0
+  class func getAuthFlags() -> Int {
+    var sf = serverFlags
+    sf = sf & 0x6
+    serverFlags = sf
+    return sf
+  }
+
   /**
    * Own name to use when sending messages. MUAs are allowed to spread this way eg. using CC, defaults to empty
    */
@@ -806,7 +826,7 @@ class MRConfig {
       return getBool("show_emails")
     }
   }
-  
+
   /**
    * 1=save mime headers and make dc_get_mime_headers() work for subsequent calls, 0=do not save mime headers (default)
    */

+ 23 - 0
deltachat-ios/events.swift

@@ -21,6 +21,29 @@ let dcNotificationViewChat = Notification.Name(rawValue: "MrEventViewChat")
 
 public func callbackSwift(event: CInt, data1: CUnsignedLong, data2: CUnsignedLong, data1String: UnsafePointer<Int8>, data2String: UnsafePointer<Int8>) -> UnsafePointer<Int8>? {
   switch event {
+  case DC_EVENT_HTTP_POST:
+
+    let urlString = String(cString: data1String)
+    logger.info("network: http post: \(urlString)")
+
+    let base = String(urlString.split(separator: "?")[0])
+
+    guard let baseUrl = URL(string: base), let url = URL(string: urlString) else {
+      return nil
+    }
+    var request = URLRequest(url: baseUrl)
+    request.httpMethod = "POST"
+
+    if let params = url.queryParameters {
+      request.httpBody = params.percentEscaped().data(using: .utf8)
+    }
+
+    let (data, _, _) = URLSession.shared.synchronousDataTask(request: request) // returns (data, response, error)
+    guard let receivedData = data, let dataString = String(bytes: receivedData, encoding: .utf8) else {
+      return nil
+    }
+    let p = UnsafePointer(strdup(dataString))
+    return p
   case DC_EVENT_HTTP_GET:
     let urlString = String(cString: data1String)
     logger.info("network: http get: \(urlString)")

+ 266 - 0
deltachat-ios/libraries/rpgp/librpgp.h

@@ -0,0 +1,266 @@
+/* librpgp Header Version 0.1.0 */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+/**
+ * A PGP message
+ * https://tools.ietf.org/html/rfc4880.html#section-11.3
+ */
+typedef struct rpgp_Message rpgp_Message;
+
+typedef struct rpgp_PublicOrSecret rpgp_PublicOrSecret;
+
+/**
+ * Represents a Public PGP key, which is signed and either received or ready to be transferred.
+ */
+typedef struct rpgp_SignedPublicKey rpgp_SignedPublicKey;
+
+/**
+ * Represents a secret signed PGP key.
+ */
+typedef struct rpgp_SignedSecretKey rpgp_SignedSecretKey;
+
+typedef rpgp_SignedSecretKey rpgp_signed_secret_key;
+
+/**
+ * Represents a vector, that can be passed to C land.
+ * Has to be deallocated using [rpgp_cvec_drop], otherwise leaks memory.
+ */
+typedef struct {
+  uint8_t *data;
+  size_t len;
+} rpgp_cvec;
+
+typedef rpgp_Message rpgp_message;
+
+typedef rpgp_SignedPublicKey rpgp_signed_public_key;
+
+typedef rpgp_PublicOrSecret rpgp_public_or_secret_key;
+
+/**
+ * Message decryption result.
+ */
+typedef struct {
+  /**
+   * A pointer to the decrypted message.
+   */
+  rpgp_message *message_ptr;
+  /**
+   * Pointer to a list of fingerprints which verified the signature.
+   */
+  char **valid_ids_ptr;
+  size_t valid_ids_len;
+} rpgp_message_decrypt_result;
+
+/**
+ * Generates a new RSA key.
+ */
+rpgp_signed_secret_key *rpgp_create_rsa_skey(uint32_t bits, const char *user_id);
+
+/**
+ * Generates a new x25519 key.
+ */
+rpgp_signed_secret_key *rpgp_create_x25519_skey(const char *user_id);
+
+/**
+ * Get a pointer to the data of the given [cvec].
+ */
+const uint8_t *rpgp_cvec_data(rpgp_cvec *cvec_ptr);
+
+/**
+ * Free the given [cvec].
+ */
+void rpgp_cvec_drop(rpgp_cvec *cvec_ptr);
+
+/**
+ * Get the length of the data of the given [cvec].
+ */
+size_t rpgp_cvec_len(rpgp_cvec *cvec_ptr);
+
+rpgp_message *rpgp_encrypt_bytes_to_keys(const uint8_t *bytes_ptr,
+                                         size_t bytes_len,
+                                         const rpgp_signed_public_key *const *pkeys_ptr,
+                                         size_t pkeys_len);
+
+rpgp_message *rpgp_encrypt_bytes_with_password(const uint8_t *bytes_ptr,
+                                               size_t bytes_len,
+                                               const char *password_ptr);
+
+/**
+ * Calculate the SHA256 hash of the given bytes.
+ */
+rpgp_cvec *rpgp_hash_sha256(const uint8_t *bytes_ptr, size_t bytes_len);
+
+/**
+ * Frees the memory of the passed in key, making the pointer invalid after this method was called.
+ */
+void rpgp_key_drop(rpgp_public_or_secret_key *key_ptr);
+
+/**
+ * Returns the Fingerprint for the passed in key. The caller is responsible to call [rpgp_cvec_drop] with the returned memory, to free it.
+ */
+rpgp_cvec *rpgp_key_fingerprint(rpgp_public_or_secret_key *key_ptr);
+
+/**
+ * Creates an in-memory representation of a PGP key, based on the armor file given.
+ * The returned pointer should be stored, and reused when calling methods "on" this key.
+ * When done with it [rpgp_key_drop] should be called, to free the memory.
+ */
+rpgp_public_or_secret_key *rpgp_key_from_armor(const uint8_t *raw, size_t len);
+
+/**
+ * Creates an in-memory representation of a PGP key, based on the serialized bytes given.
+ */
+rpgp_public_or_secret_key *rpgp_key_from_bytes(const uint8_t *raw, size_t len);
+
+/**
+ * Returns the KeyID for the passed in key. The caller is responsible to call [rpgp_string_drop] with the returned memory, to free it.
+ */
+char *rpgp_key_id(rpgp_public_or_secret_key *key_ptr);
+
+/**
+ * Returns `true` if this key is a public key, false otherwise.
+ */
+bool rpgp_key_is_public(rpgp_public_or_secret_key *key_ptr);
+
+/**
+ * Returns `true` if this key is a secret key, false otherwise.
+ */
+bool rpgp_key_is_secret(rpgp_public_or_secret_key *key_ptr);
+
+/**
+ * Calculate the number of bytes in the last error's error message **not**
+ * including any trailing `null` characters.
+ */
+int rpgp_last_error_length(void);
+
+/**
+ * Write the most recent error message into a caller-provided buffer as a UTF-8
+ * string, returning the number of bytes written.
+ * # Note
+ * This writes a **UTF-8** string into the buffer. Windows users may need to
+ * convert it to a UTF-16 "unicode" afterwards.
+ * If there are no recent errors then this returns `0` (because we wrote 0
+ * bytes). `-1` is returned if there are any errors, for example when passed a
+ * null pointer or a buffer of insufficient size.
+ */
+char *rpgp_last_error_message(void);
+
+/**
+ * Free a [message_decrypt_result].
+ */
+void rpgp_message_decrypt_result_drop(rpgp_message_decrypt_result *res_ptr);
+
+/**
+ * Decrypt the passed in message, without attempting to use a password.
+ */
+rpgp_message_decrypt_result *rpgp_msg_decrypt_no_pw(const rpgp_message *msg_ptr,
+                                                    const rpgp_signed_secret_key *const *skeys_ptr,
+                                                    size_t skeys_len,
+                                                    const rpgp_signed_public_key *const *pkeys_ptr,
+                                                    size_t pkeys_len);
+
+/**
+ * Decrypt the passed in message, using a password.
+ */
+rpgp_message *rpgp_msg_decrypt_with_password(const rpgp_message *msg_ptr, const char *password_ptr);
+
+/**
+ * Free a [message], that was created by rpgp.
+ */
+void rpgp_msg_drop(rpgp_message *msg_ptr);
+
+/**
+ * Parse an armored message.
+ */
+rpgp_message *rpgp_msg_from_armor(const uint8_t *msg_ptr, size_t msg_len);
+
+/**
+ * Parse a message in bytes format.
+ */
+rpgp_message *rpgp_msg_from_bytes(const uint8_t *msg_ptr, size_t msg_len);
+
+/**
+ * Get the fingerprint of a given encrypted message, by index, in hexformat.
+ */
+char *rpgp_msg_recipients_get(rpgp_message *msg_ptr, uint32_t i);
+
+/**
+ * Get the number of fingerprints of a given encrypted message.
+ */
+uint32_t rpgp_msg_recipients_len(rpgp_message *msg_ptr);
+
+/**
+ * Encodes the message into its ascii armored representation.
+ */
+rpgp_cvec *rpgp_msg_to_armored(const rpgp_message *msg_ptr);
+
+/**
+ * Encodes the message into its ascii armored representation, returning a string.
+ */
+char *rpgp_msg_to_armored_str(const rpgp_message *msg_ptr);
+
+/**
+ * Returns the underlying data of the given message.
+ * Fails when the message is encrypted. Decompresses compressed messages.
+ */
+rpgp_cvec *rpgp_msg_to_bytes(const rpgp_message *msg_ptr);
+
+/**
+ * Free the given [signed_public_key].
+ */
+void rpgp_pkey_drop(rpgp_signed_public_key *pkey_ptr);
+
+/**
+ * Parse a serialized public key, into the native rPGP memory representation.
+ */
+rpgp_signed_public_key *rpgp_pkey_from_bytes(const uint8_t *raw, size_t len);
+
+/**
+ * Get the key id of the given [signed_public_key].
+ */
+char *rpgp_pkey_key_id(rpgp_signed_public_key *pkey_ptr);
+
+/**
+ * Serialize the [signed_public_key] to bytes.
+ */
+rpgp_cvec *rpgp_pkey_to_bytes(rpgp_signed_public_key *pkey_ptr);
+
+rpgp_message *rpgp_sign_encrypt_bytes_to_keys(const uint8_t *bytes_ptr,
+                                              size_t bytes_len,
+                                              const rpgp_signed_public_key *const *pkeys_ptr,
+                                              size_t pkeys_len,
+                                              const rpgp_signed_secret_key *skey_ptr);
+
+/**
+ * Free the memory of a secret key.
+ */
+void rpgp_skey_drop(rpgp_signed_secret_key *skey_ptr);
+
+/**
+ * Creates an in-memory representation of a Secret PGP key, based on the serialized bytes given.
+ */
+rpgp_signed_secret_key *rpgp_skey_from_bytes(const uint8_t *raw, size_t len);
+
+/**
+ * Returns the KeyID for the passed in key.
+ */
+char *rpgp_skey_key_id(rpgp_signed_secret_key *skey_ptr);
+
+/**
+ * Get the signed public key matching the given private key. Only works for non password protected keys.
+ */
+rpgp_signed_public_key *rpgp_skey_public_key(rpgp_signed_secret_key *skey_ptr);
+
+/**
+ * Serialize a secret key into its byte representation.
+ */
+rpgp_cvec *rpgp_skey_to_bytes(rpgp_signed_secret_key *skey_ptr);
+
+/**
+ * Free string, that was created by rpgp.
+ */
+void rpgp_string_drop(char *p);