Browse Source

Merge pull request #86 from deltachat/ContactProfile

Contact profile
nayooti 6 years ago
parent
commit
3172e67c31

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

@@ -92,6 +92,8 @@
 		AE25F09022807AD800CDEA66 /* GroupNameCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE25F08F22807AD800CDEA66 /* GroupNameCell.swift */; };
 		AE38B31822672DFC00EC37A1 /* ActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE38B31722672DFC00EC37A1 /* ActionCell.swift */; };
 		AE38B31A2267328200EC37A1 /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE38B3192267328200EC37A1 /* Colors.swift */; };
+		AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */; };
+		AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */; };
 		AE728F15229D5C390047565B /* PhotoPickerAlertAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */; };
 		AE8519EA2272FDCA00ED86F0 /* DeviceContactsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */; };
 		AE851A04227AECDE00ED86F0 /* deltachat_iosTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851A03227AECDE00ED86F0 /* deltachat_iosTests.swift */; };
@@ -99,7 +101,7 @@
 		AE851AC7227C776400ED86F0 /* Location.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851AC6227C776400ED86F0 /* Location.swift */; };
 		AE851AC9227C77CF00ED86F0 /* Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851AC8227C77CF00ED86F0 /* Media.swift */; };
 		AE851ACE227CA54400ED86F0 /* InitialsLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851ACD227CA54300ED86F0 /* InitialsLabel.swift */; };
-		AE851AD0227DF50900ED86F0 /* SingleChatDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851ACF227DF50900ED86F0 /* SingleChatDetailViewController.swift */; };
+		AE851AD0227DF50900ED86F0 /* GroupChatDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851ACF227DF50900ED86F0 /* GroupChatDetailViewController.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 */; };
@@ -289,6 +291,8 @@
 		AE25F08F22807AD800CDEA66 /* GroupNameCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupNameCell.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>"; };
+		AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailHeader.swift; sourceTree = "<group>"; };
+		AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupViewController.swift; sourceTree = "<group>"; };
 		AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerAlertAction.swift; sourceTree = "<group>"; };
 		AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContactsHandler.swift; sourceTree = "<group>"; };
 		AE851A01227AECDE00ED86F0 /* deltachat-iosTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "deltachat-iosTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -298,7 +302,7 @@
 		AE851AC6227C776400ED86F0 /* Location.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Location.swift; sourceTree = "<group>"; };
 		AE851AC8227C77CF00ED86F0 /* Media.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Media.swift; sourceTree = "<group>"; };
 		AE851ACD227CA54300ED86F0 /* InitialsLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InitialsLabel.swift; sourceTree = "<group>"; };
-		AE851ACF227DF50900ED86F0 /* SingleChatDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleChatDetailViewController.swift; sourceTree = "<group>"; };
+		AE851ACF227DF50900ED86F0 /* GroupChatDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupChatDetailViewController.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>"; };
@@ -618,10 +622,11 @@
 				78ED838E21D5927A00243125 /* ProfileViewController.swift */,
 				78E45E3921D3CFBC00D4B15E /* SettingsController.swift */,
 				7070FB9A2101ECBB000DC258 /* GroupNameController.swift */,
-				AE851ACF227DF50900ED86F0 /* SingleChatDetailViewController.swift */,
+				AE851ACF227DF50900ED86F0 /* GroupChatDetailViewController.swift */,
 				AEE6EC3E2282C59C00EDC689 /* GroupMembersViewController.swift */,
 				AEE6EC402282DF5700EDC689 /* MailboxViewController.swift */,
 				AEE6EC472283045D00EDC689 /* EditSettingsController.swift */,
+				AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */,
 			);
 			path = Controller;
 			sourceTree = "<group>";
@@ -662,6 +667,7 @@
 				AE25F08F22807AD800CDEA66 /* GroupNameCell.swift */,
 				AEE6EC372281AF2D00EDC689 /* InitialsBadge.swift */,
 				AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */,
+				AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -972,10 +978,12 @@
 				AEACE2DF1FB3246400DCDD78 /* Message.swift in Sources */,
 				7070FB9B2101ECBB000DC258 /* GroupNameController.swift in Sources */,
 				7070FB6D20FF345F000DC258 /* dc_imex.c in Sources */,
+				AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */,
 				7070FB6620FF345F000DC258 /* dc_param.c in Sources */,
 				7070FB9220FF4118000DC258 /* dc_imap.c in Sources */,
 				7070FB7720FF345F000DC258 /* dc_qr.c in Sources */,
 				78E45E4421D3F14A00D4B15E /* UIImage+Extension.swift in Sources */,
+				AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */,
 				70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */,
 				78E45E3E21D3D28C00D4B15E /* NavigationController.swift in Sources */,
 				78E45E4021D3D70700D4B15E /* ContactListController.swift in Sources */,
@@ -1035,7 +1043,7 @@
 				7092474120B3869500AF8799 /* ContactDetailViewController.swift in Sources */,
 				7070FB5E20FF345F000DC258 /* dc_token.c in Sources */,
 				AE18F292228C17BC0007B1BE /* PortSettingsController.swift in Sources */,
-				AE851AD0227DF50900ED86F0 /* SingleChatDetailViewController.swift in Sources */,
+				AE851AD0227DF50900ED86F0 /* GroupChatDetailViewController.swift in Sources */,
 				7070FB6C20FF345F000DC258 /* dc_keyring.c in Sources */,
 				7A451DB01FB1F84900177250 /* AppCoordinator.swift in Sources */,
 				AE38B31822672DFC00EC37A1 /* ActionCell.swift in Sources */,

+ 1 - 6
deltachat-ios/Controller/ChatViewController.swift

@@ -45,7 +45,6 @@ class ChatViewController: MessagesViewController {
 		return messageInputBar
 	}
 
-
 	init(chatId: Int, title: String? = nil) {
 		self.chatId = chatId
 		super.init(nibName: nil, bundle: nil)
@@ -387,11 +386,7 @@ class ChatViewController: MessagesViewController {
 		}
 	}
 
-	/*
-	let rightItems = [
-	let sendButtonImage = UIImage(named: "paper_plane")?.withRenderingMode(.alwaysTemplate)
-	]
-	*/
+
 	@objc private func chatProfilePressed() {
 		coordinator?.showChatDetail(chatId: chatId)
 	}

+ 146 - 118
deltachat-ios/Controller/ContactDetailViewController.swift

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

+ 18 - 1
deltachat-ios/Controller/ContactListController.swift

@@ -87,6 +87,23 @@ class ContactListController: UITableViewController {
 
   override func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
     let contactId = contactIds[indexPath.row]
-    coordinator?.showContactDetail(contactId: contactId)
+		let chatId = dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId))
+
+		coordinator?.showChat(chatId: Int(chatId))
+		// coordinator?.showContactDetail(contactId: contactId)
   }
+
+	override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
+		let row = indexPath.row
+
+		let contactId = contactIds[row]
+
+		// assigning swipe by delete to chats
+		let edit = UITableViewRowAction(style: .default, title: "Edit") {
+			[unowned self] _, indexPath in
+			self.coordinator?.showContactDetail(contactId: contactId)
+		}
+		edit.backgroundColor = DCColors.primary
+		return [edit]
+	}
 }

+ 77 - 0
deltachat-ios/Controller/EditGroupViewController.swift

@@ -0,0 +1,77 @@
+//
+//  EditGroupViewController.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 29.05.19.
+//  Copyright © 2019 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+class EditGroupViewController: UITableViewController {
+
+	weak var coordinator: EditGroupCoordinator?
+
+	private let chat: MRChat
+
+	lazy var groupNameCell: GroupLabelCell = {
+		let cell = GroupLabelCell(style: .default, reuseIdentifier: nil)
+		cell.onTextChanged = groupNameEdited(_:)
+		return cell
+	}()
+
+	lazy var doneButton: UIBarButtonItem = {
+		let button = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(saveContactButtonPressed))
+		button.isEnabled = false
+		return button
+	}()
+
+	lazy var cancelButton: UIBarButtonItem = {
+		let button = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
+		return button
+	}()
+
+	init(chat: MRChat) {
+		self.chat = chat
+		super.init(style: .grouped)
+		groupNameCell.inputField.text = chat.name
+		groupNameCell.groupBadge.setText(chat.name)
+		groupNameCell.groupBadge.setColor(chat.color)
+	}
+
+	required init?(coder aDecoder: NSCoder) {
+		fatalError("init(coder:) has not been implemented")
+	}
+
+	override func viewDidLoad() {
+		super.viewDidLoad()
+		navigationItem.rightBarButtonItem = doneButton
+		navigationItem.leftBarButtonItem = cancelButton
+	}
+
+	override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+		return groupNameCell
+	}
+
+	override func numberOfSections(in tableView: UITableView) -> Int {
+		return 1
+	}
+
+	override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+		return 1
+	}
+
+	@objc func saveContactButtonPressed() {
+		let newName = groupNameCell.getGroupName()
+		dc_set_chat_name(mailboxPointer, UInt32(chat.id), newName)
+		coordinator?.navigateBack()
+	}
+
+	@objc func cancelButtonPressed() {
+		coordinator?.navigateBack()
+	}
+
+	private func groupNameEdited(_ newName: String) {
+		doneButton.isEnabled = true
+	}
+}

+ 230 - 0
deltachat-ios/Controller/GroupChatDetailViewController.swift

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

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

@@ -69,7 +69,7 @@ class GroupNameController: UITableViewController {
 
     if section == 0 {
       let cell = tableView.dequeueReusableCell(withIdentifier: "groupLabelCell", for: indexPath) as! GroupLabelCell
-      cell.groupNameUpdated = updateGroupName
+      cell.onTextChanged = updateGroupName
 
       return cell
 

+ 44 - 25
deltachat-ios/Controller/NewContactController.swift

@@ -8,7 +8,44 @@
 
 import UIKit
 
+class EditContactController: NewContactController {
+
+	// for editing existing contacts (only
+	// the name may be edited, therefore disable
+	// the email field)
+	init(contactIdForUpdate: Int) {
+		super.init()
+		title = "Edit Contact"
+
+		let contact = MRContact(id: contactIdForUpdate)
+		nameCell.textField.text = contact.name
+		emailCell.textField.text = contact.email
+		emailCell.textField.isEnabled = false
+		emailCell.contentView.alpha = 0.3
+
+		model.name = contact.name
+		model.email = contact.email
+
+		if contactIsValid() {
+			doneButton?.isEnabled = true
+		}
+	}
+
+	required init?(coder _: NSCoder) {
+		fatalError("init(coder:) has not been implemented")
+	}
+
+	@objc override func saveContactButtonPressed() {
+		dc_create_contact(mailboxPointer, model.name, model.email)
+		coordinator?.navigateBack()
+	}
+
+}
+
 class NewContactController: UITableViewController {
+
+	weak var coordinator: EditContactCoordinatorProtocol?
+
   let emailCell = TextFieldCell.makeEmailCell()
   let nameCell = TextFieldCell.makeNameCell()
   var doneButton: UIBarButtonItem?
@@ -30,26 +67,7 @@ class NewContactController: UITableViewController {
 
   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"
 
-    let contact = MRContact(id: contactIdForUpdate)
-    nameCell.textField.text = contact.name
-    emailCell.textField.text = contact.email
-    emailCell.textField.isEnabled = false
-    emailCell.contentView.alpha = 0.3
-
-    model.name = contact.name
-    model.email = contact.email
-
-    if contactIsValid() {
-      doneButton?.isEnabled = true
-    }
-  }
 
   // for creating a new contact
   init() {
@@ -65,11 +83,11 @@ class NewContactController: UITableViewController {
     nameCell.textField.returnKeyType = .done
 
     title = "New Contact"
-    doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(NewContactController.saveContactButtonPressed))
+    doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(saveContactButtonPressed))
     doneButton?.isEnabled = false
     navigationItem.rightBarButtonItem = doneButton
 
-    cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(NewContactController.cancelButtonPressed))
+    cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelButtonPressed))
     navigationItem.leftBarButtonItem = cancelButton
 
     emailCell.textField.addTarget(self, action: #selector(NewContactController.emailTextChanged), for: UIControl.Event.editingChanged)
@@ -100,12 +118,13 @@ class NewContactController: UITableViewController {
   }
 
   @objc func saveContactButtonPressed() {
-    dc_create_contact(mailboxPointer, model.name, model.email)
-    navigationController?.popViewController(animated: true)
-  }
+		let contactId = dc_create_contact(mailboxPointer, model.name, model.email)
+		let chatId = Int(dc_create_chat_by_contact_id(mailboxPointer, UInt32(contactId)))
+		coordinator?.showChat(chatId: chatId)
+	}
 
   @objc func cancelButtonPressed() {
-    navigationController?.popViewController(animated: true)
+		coordinator?.navigateBack()
   }
 
   required init?(coder _: NSCoder) {

+ 0 - 353
deltachat-ios/Controller/SingleChatDetailViewController.swift

@@ -1,353 +0,0 @@
-//
-//  ChatDetailViewController.swift
-//  deltachat-ios
-//
-//  Created by Bastian van de Wetering on 04.05.19.
-//  Copyright © 2019 Jonas Reinsch. All rights reserved.
-//
-
-import UIKit
-
-class ChatDetailViewController: UIViewController {
-  weak var coordinator: ChatDetailCoordinator?
-
-  fileprivate var chat: MRChat
-
-  var chatDetailTable: UITableView = {
-    let table = UITableView(frame: .zero, style: .grouped)
-    table.bounces = false
-    table.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
-    table.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
-    table.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
-
-    return table
-  }()
-
-  init(chatId: Int) {
-    chat = MRChat(id: chatId)
-    super.init(nibName: nil, bundle: nil)
-    setupSubviews()
-  }
-
-  override func viewWillAppear(_: Bool) {
-    chatDetailTable.reloadData() // to display updates
-  }
-
-  required init?(coder _: NSCoder) {
-    fatalError("init(coder:) has not been implemented")
-  }
-
-  private func setupSubviews() {
-    view.addSubview(chatDetailTable)
-    chatDetailTable.translatesAutoresizingMaskIntoConstraints = false
-
-    chatDetailTable.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
-    chatDetailTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
-    chatDetailTable.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
-    chatDetailTable.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
-  }
-
-  @objc func editButtonPressed() {
-    // will be overwritten
-  }
-
-  func showNotificationSetup() {
-    let notificationSetupAlert = UIAlertController(title: "Notifications Setup is not implemented yet", message: "But you get an idea where this is going", preferredStyle: .actionSheet)
-    let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
-    notificationSetupAlert.addAction(cancelAction)
-    present(notificationSetupAlert, animated: true, completion: nil)
-  }
-}
-
-class SingleChatDetailViewController: ChatDetailViewController {
-  var contact: MRContact? {
-    if let id = chat.contactIds.first {
-      return MRContact(id: id)
-    }
-    return nil
-  }
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    title = "Info"
-    chatDetailTable.delegate = self
-    chatDetailTable.dataSource = self
-  }
-
-  @objc override func editButtonPressed() {
-    if let id = chat.contactIds.first {
-      coordinator?.showSingleChatEdit(contactId: id)
-    }
-  }
-}
-
-extension SingleChatDetailViewController: UITableViewDelegate, UITableViewDataSource {
-  func numberOfSections(in _: UITableView) -> Int {
-    return 2
-  }
-
-  func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int {
-    return 1
-  }
-
-  func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
-    if section == 0 {
-      guard let contact = contact else {
-        return nil
-      }
-      let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.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 = chat.profileImage {
-        contactCell.setImage(img)
-      } else {
-        contactCell.setBackupImage(name: contact.name, color: contact.color)
-      }
-      contactCell.setVerified(isVerified: chat.isVerified)
-      return contactCell
-    } else {
-      return nil
-    }
-  }
-
-  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let section = indexPath.section
-
-    if section == 0 {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
-      cell.textLabel?.text = "Notifications"
-      cell.selectionStyle = .none
-      return cell
-    } else if section == 1 {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionCell
-      if let contact = contact {
-        cell.actionTitle = contact.isBlocked ? "Unblock Contact" : "Block Contact"
-        cell.actionColor = contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red // SystemColor.red.uiColor
-      }
-      return cell
-    }
-    return UITableViewCell(frame: .zero)
-  }
-
-  func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-    let section = indexPath.section
-
-    if section == 0 {
-      showNotificationSetup()
-    } else if section == 1 {
-      if let contact = contact {
-        contact.isBlocked ? contact.unblock() : contact.block()
-        tableView.reloadData()
-      }
-    }
-  }
-}
-
-class GroupChatDetailViewController: ChatDetailViewController {
-  private var currentUser: MRContact? {
-    return groupMembers.filter { $0.email == MRConfig.addr }.first
-  }
-
-  private let editGroupCell = GroupLabelCell()
-
-  private var editingGroupName: Bool = false
-
-  private lazy var editBarButtonItem: UIBarButtonItem = {
-    UIBarButtonItem(title: editingGroupName ? "Done" : "Edit", style: .plain, target: self, action: #selector(editButtonPressed))
-  }()
-
-  private var groupMembers: [MRContact] = []
-
-  private let staticCellCountMemberSection = 1 //
-
-  override func viewDidLoad() {
-    super.viewDidLoad()
-    title = "Group Info"
-    chatDetailTable.delegate = self
-    chatDetailTable.dataSource = self
-    navigationItem.rightBarButtonItem = editBarButtonItem
-  }
-
-  override func viewWillAppear(_: Bool) {
-    updateGroupMembers()
-    editBarButtonItem.isEnabled = currentUser != nil
-  }
-
-  private func updateGroupMembers() {
-    let ids = chat.contactIds
-    groupMembers = ids.map { MRContact(id: $0) }
-    chatDetailTable.reloadData()
-  }
-
-  @objc override func editButtonPressed() {
-    if editingGroupName {
-      let newName = editGroupCell.getGroupName()
-      dc_set_chat_name(mailboxPointer, UInt32(chat.id), newName)
-      chat = MRChat(id: chat.id) // reload
-    }
-
-    editingGroupName = !editingGroupName
-    editBarButtonItem.title = editingGroupName ? "Save" : "Edit"
-    chatDetailTable.reloadData()
-  }
-
-  private func leaveGroup() {
-    if let userId = currentUser?.id {
-      dc_remove_contact_from_chat(mailboxPointer, UInt32(chat.id), UInt32(userId))
-      editBarButtonItem.isEnabled = false
-      updateGroupMembers()
-    }
-  }
-}
-
-extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSource {
-  func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
-    if section == 1 {
-      return "Members:"
-    }
-    return nil
-  }
-
-  func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
-    if section == 0 {
-      let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
-
-      if editingGroupName {
-        editGroupCell.groupBadge.setColor(chat.color)
-        editGroupCell.backgroundColor = bg
-        editGroupCell.inputField.text = chat.name
-        editGroupCell.groupBadge.setText(chat.name)
-        return editGroupCell
-      } else {
-        let contactCell = ContactCell()
-        contactCell.backgroundColor = bg
-        contactCell.nameLabel.text = chat.name
-        contactCell.emailLabel.text = chat.subtitle
-        contactCell.darkMode = false
-        contactCell.selectionStyle = .none
-        if let img = chat.profileImage {
-          contactCell.setImage(img)
-        } else {
-          contactCell.setBackupImage(name: chat.name, color: chat.color)
-        }
-        contactCell.setVerified(isVerified: chat.isVerified)
-        return contactCell
-      }
-    } else {
-      return nil
-    }
-  }
-
-  func numberOfSections(in _: UITableView) -> Int {
-    /*
-     section 0: config
-     section 1: members
-     section 2: leave group (optional - if user already left group this option will be hidden)
-     */
-
-    if currentUser == nil {
-      return 2
-    }
-    return 3
-  }
-
-  func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-    if section == 0 {
-      return 1
-    } else if section == 1 {
-      return groupMembers.count + staticCellCountMemberSection // first cell is addGroupMemberCell
-    } else if section == 2 {
-      return 1
-    } else {
-      return 0
-    }
-  }
-
-  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-    let section = indexPath.section
-    let row = indexPath.row
-
-    if section == 0 {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
-      cell.textLabel?.text = "Notifications"
-      cell.selectionStyle = .none
-      return cell
-    } else if section == 1 {
-      if row == 0 {
-        let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionCell
-        cell.actionTitle = "Add Members"
-        return cell
-      } else {
-        let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as! ContactCell
-        let contact = groupMembers[row - staticCellCountMemberSection]
-        cell.nameLabel.text = contact.name
-        cell.emailLabel.text = contact.email
-        cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-        cell.setColor(contact.color)
-        return cell
-      }
-    } else if section == 2 {
-      let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as! ActionCell
-      cell.actionTitle = "Leave Group"
-      cell.actionColor = UIColor.red
-      return cell
-    }
-
-    return UITableViewCell(frame: .zero)
-  }
-
-  func tableView(_: UITableView, didSelectRowAt indexPath: IndexPath) {
-    let section = indexPath.section
-    let row = indexPath.row
-    if section == 0 {
-      showNotificationSetup()
-    } else if section == 1 {
-      if row == 0 {
-        coordinator?.showAddGroupMember(chatId: chat.id)
-      }
-      // ignore for now - in Telegram tapping a contactCell leads into ContactDetail
-    } else if section == 2 {
-      leaveGroup()
-    }
-  }
-
-  func tableView(_: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
-    let section = indexPath.section
-    let row = indexPath.row
-
-    if let currentUser = currentUser {
-      if section == 1, row > 0, groupMembers[row - staticCellCountMemberSection].id != currentUser.id {
-        return true
-      }
-    }
-    return false
-  }
-
-  func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
-    let section = indexPath.section
-    let row = indexPath.row
-
-    // assigning swipe by delete to members (except for current user)
-    if section == 1, row >= staticCellCountMemberSection, groupMembers[row - staticCellCountMemberSection].id != currentUser?.id {
-      let delete = UITableViewRowAction(style: .destructive, title: "Delete") { [unowned self] _, indexPath in
-
-        let memberId = self.groupMembers[row - self.staticCellCountMemberSection].id
-        let success = dc_remove_contact_from_chat(mailboxPointer, UInt32(self.chat.id), UInt32(memberId))
-        if success == 1 {
-          self.groupMembers.remove(at: row - self.staticCellCountMemberSection)
-          tableView.deleteRows(at: [indexPath], with: .fade)
-          tableView.reloadData()
-        }
-      }
-      delete.backgroundColor = UIColor.red
-      return [delete]
-    } else {
-      return nil
-    }
-  }
-}

+ 96 - 12
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -102,7 +102,7 @@ class AppCoordinator: NSObject, Coordinator, UITabBarControllerDelegate {
 
 	func presentLoginController() {
 		let accountSetupController = AccountSetupController()
-		let accountSetupNavigationController = UINavigationController(rootViewController: accountSetupController)
+		let accountSetupNavigationController = NavigationController(rootViewController: accountSetupController)
 		rootViewController.present(accountSetupNavigationController, animated: false, completion: nil)
 	}
 }
@@ -129,11 +129,20 @@ class ContactListCoordinator: Coordinator {
 
 	func showContactDetail(contactId: Int) {
 		let contactDetailController = ContactDetailViewController(contactId: contactId)
+		contactDetailController.showChatCell = true
 		let coordinator = ContactDetailCoordinator(navigationController: navigationController)
 		childCoordinators.append(coordinator)
 		contactDetailController.coordinator = coordinator
 		navigationController.pushViewController(contactDetailController, animated: true)
 	}
+
+	func showChat(chatId: Int) {
+		let chatVC = ChatViewController(chatId: chatId)
+		let coordinator = ChatViewCoordinator(navigationController: navigationController)
+		childCoordinators.append(coordinator)
+		chatVC.coordinator = coordinator
+		navigationController.pushViewController(chatVC, animated: true)
+	}
 }
 
 // since mailbox and chatView -tab both use ChatViewController we want to be able to assign different functionality via coordinators -> therefore we override unneeded functions such as showChatDetail -> maybe find better solution in longterm
@@ -288,6 +297,9 @@ class NewChatCoordinator: Coordinator {
 
 	func showNewContactController() {
 		let newContactController = NewContactController()
+		let coordinator = EditContactCoordinator(navigationController: navigationController)
+		childCoordinators.append(coordinator)
+		newContactController.coordinator = coordinator
 		navigationController.pushViewController(newContactController, animated: true)
 	}
 
@@ -306,7 +318,7 @@ class NewChatCoordinator: Coordinator {
 	}
 }
 
-class ChatDetailCoordinator: Coordinator {
+class GroupChatDetailCoordinator: Coordinator {
 	let navigationController: UINavigationController
 
 	private var childCoordinators: [Coordinator] = []
@@ -316,14 +328,25 @@ class ChatDetailCoordinator: Coordinator {
 	}
 
 	func showSingleChatEdit(contactId: Int) {
-		let newContactController = NewContactController(contactIdForUpdate: contactId)
-		navigationController.pushViewController(newContactController, animated: true)
+		let editContactController = EditContactController(contactIdForUpdate: contactId)
+		let coordinator = EditContactCoordinator(navigationController: navigationController)
+		childCoordinators.append(coordinator)
+		editContactController.coordinator = coordinator
+		navigationController.pushViewController(editContactController, animated: true)
 	}
 
 	func showAddGroupMember(chatId: Int) {
 		let groupMemberViewController = AddGroupMembersViewController(chatId: chatId)
 		navigationController.pushViewController(groupMemberViewController, animated: true)
 	}
+
+	func showGroupChatEdit(chat: MRChat) {
+		let editGroupViewController = EditGroupViewController(chat: chat)
+		let coordinator = EditGroupCoordinator(navigationController: navigationController)
+		childCoordinators.append(coordinator)
+		editGroupViewController.coordinator = coordinator
+		navigationController.pushViewController(editGroupViewController, animated: true)
+	}
 }
 
 class ChatViewCoordinator: Coordinator {
@@ -337,23 +360,30 @@ class ChatViewCoordinator: Coordinator {
 
 	func showChatDetail(chatId: Int) {
 		let chat = MRChat(id: chatId)
-		let chatDetailViewController: ChatDetailViewController
 		switch chat.chatType {
 		case .SINGLE:
-			chatDetailViewController = SingleChatDetailViewController(chatId: chatId) // inherits from ChatDetailViewController
+			if let contactId = chat.contactIds.first {
+				let contactDetailController = ContactDetailViewController(contactId: contactId)
+				let coordinator = ContactDetailCoordinator(navigationController: navigationController)
+				childCoordinators.append(coordinator)
+				contactDetailController.coordinator = coordinator
+				navigationController.pushViewController(contactDetailController, animated: true)
+			}
 		case .GROUP, .VERYFIEDGROUP:
-			chatDetailViewController = GroupChatDetailViewController(chatId: chatId) // inherits from ChatDetailViewController
+			let groupChatDetailViewController = GroupChatDetailViewController(chatId: chatId) // inherits from ChatDetailViewController
+			let coordinator = GroupChatDetailCoordinator(navigationController: navigationController)
+			childCoordinators.append(coordinator)
+			groupChatDetailViewController.coordinator = coordinator
+			navigationController.pushViewController(groupChatDetailViewController, animated: true)
 		}
-		let coordinator = ChatDetailCoordinator(navigationController: navigationController)
-		childCoordinators.append(coordinator)
-		chatDetailViewController.coordinator = coordinator
-		navigationController.pushViewController(chatDetailViewController, animated: true)
 	}
 
 	func showContactDetail(of contactId: Int) {
 		let contactDetailController = ContactDetailViewController(contactId: contactId)
+		contactDetailController.showChatCell = true
 		//let nav = UINavigationController(rootViewController: contactDetailController)
 		let coordinator = ContactDetailCoordinator(navigationController: navigationController)
+		childCoordinators.append(coordinator)
 		contactDetailController.coordinator = coordinator
 		navigationController.pushViewController(contactDetailController, animated: true)
 		// navigationController.present(nav, animated: true, completion: nil)
@@ -397,7 +427,7 @@ class GroupNameCoordinator: Coordinator {
 	}
 }
 
-class ContactDetailCoordinator: Coordinator {
+class ContactDetailCoordinator: Coordinator, ContactDetailCoordinatorProtocol {
 	let navigationController: UINavigationController
 
 	private var childCoordinators: [Coordinator] = []
@@ -414,4 +444,58 @@ class ContactDetailCoordinator: Coordinator {
 		navigationController.popToRootViewController(animated: false)
 		navigationController.pushViewController(chatViewController, animated: true)
 	}
+
+	func showEditContact(contactId: Int) {
+		let editContactController = EditContactController(contactIdForUpdate: contactId)
+		let coordinator = EditContactCoordinator(navigationController: navigationController)
+		childCoordinators.append(coordinator)
+		editContactController.coordinator = coordinator
+		navigationController.pushViewController(editContactController, animated: true)
+	}
+}
+
+class EditGroupCoordinator: Coordinator {
+	let navigationController: UINavigationController
+
+	init(navigationController: UINavigationController) {
+		self.navigationController = navigationController
+	}
+
+	func navigateBack() {
+		navigationController.popViewController(animated: true)
+	}
+}
+
+class EditContactCoordinator: Coordinator, EditContactCoordinatorProtocol {
+
+	let navigationController: UINavigationController
+
+	var childCoordinators: [Coordinator] = []
+
+	init(navigationController: UINavigationController) {
+		self.navigationController = navigationController
+	}
+
+	func navigateBack() {
+		navigationController.popViewController(animated: true)
+	}
+
+	func showChat(chatId: Int) {
+		let chatViewController = ChatViewController(chatId: chatId)
+		let coordinator = ChatViewCoordinator(navigationController: navigationController)
+		childCoordinators.append(coordinator)
+		chatViewController.coordinator = coordinator
+		navigationController.popToRootViewController(animated: false)
+		navigationController.pushViewController(chatViewController, animated: true)
+	}
+}
+
+protocol ContactDetailCoordinatorProtocol: class {
+	func showEditContact(contactId: Int)
+	func showChat(chatId: Int)
+}
+
+protocol EditContactCoordinatorProtocol: class {
+	func navigateBack()
+	func showChat(chatId: Int)
 }

+ 28 - 0
deltachat-ios/View/ContactDetailHeader.swift

@@ -0,0 +1,28 @@
+//
+//  ContactDetailHeader.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 29.05.19.
+//  Copyright © 2019 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+class ContactDetailHeader: ContactCell {
+	init() {
+		super.init(style: .default, reuseIdentifier: nil)
+		let bg = UIColor(red: 248 / 255, green: 248 / 255, blue: 255 / 255, alpha: 1.0)
+		backgroundColor = bg
+		darkMode = false
+		selectionStyle = .none
+	}
+
+	required init?(coder _: NSCoder) {
+		fatalError("init(coder:) has not been implemented")
+	}
+
+	func updateDetails(title: String?, subtitle: String?) {
+		nameLabel.text = title
+		emailLabel.text = subtitle
+	}
+}

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

@@ -11,7 +11,7 @@ import UIKit
 class GroupLabelCell: UITableViewCell {
   var groupBadgeSize: CGFloat = 54
 
-  var groupNameUpdated: ((String) -> Void)? // use this callback to update editButton in navigationController
+  var onTextChanged: ((String) -> Void)? // use this callback to update editButton in navigationController
 
   lazy var groupBadge: InitialsBadge = {
     let badge = InitialsBadge(frame: .zero)
@@ -62,7 +62,7 @@ class GroupLabelCell: UITableViewCell {
   @objc func nameFieldChanged() {
     let groupName = inputField.text ?? ""
     groupBadge.setText(groupName)
-    groupNameUpdated?(groupName)
+    onTextChanged?(groupName)
   }
 
   func getGroupName() -> String {