Bladeren bron

wip - next

dignifiedquire 6 jaren geleden
bovenliggende
commit
749af43bc4
56 gewijzigde bestanden met toevoegingen van 1299 en 385 verwijderingen
  1. 32 0
      deltachat-ios.xcodeproj/project.pbxproj
  2. 15 12
      deltachat-ios/AppCoordinator.swift
  3. 35 12
      deltachat-ios/AppDelegate.swift
  4. 50 0
      deltachat-ios/AppTabBarController.swift
  5. 23 0
      deltachat-ios/Assets.xcassets/camera.imageset/Contents.json
  6. BIN
      deltachat-ios/Assets.xcassets/camera.imageset/camera.png
  7. BIN
      deltachat-ios/Assets.xcassets/camera.imageset/camera@2x.png
  8. BIN
      deltachat-ios/Assets.xcassets/camera.imageset/camera@3x.png
  9. 23 0
      deltachat-ios/Assets.xcassets/chat.imageset/Contents.json
  10. BIN
      deltachat-ios/Assets.xcassets/chat.imageset/chat.png
  11. BIN
      deltachat-ios/Assets.xcassets/chat.imageset/chat@2x.png
  12. BIN
      deltachat-ios/Assets.xcassets/chat.imageset/chat@3x.png
  13. 23 0
      deltachat-ios/Assets.xcassets/contact_card.imageset/Contents.json
  14. BIN
      deltachat-ios/Assets.xcassets/contact_card.imageset/contact_card.png
  15. BIN
      deltachat-ios/Assets.xcassets/contact_card.imageset/contact_card@2x.png
  16. BIN
      deltachat-ios/Assets.xcassets/contact_card.imageset/contact_card@3x.png
  17. 23 0
      deltachat-ios/Assets.xcassets/contacts.imageset/Contents.json
  18. BIN
      deltachat-ios/Assets.xcassets/contacts.imageset/contacts.png
  19. BIN
      deltachat-ios/Assets.xcassets/contacts.imageset/contacts@2x.png
  20. BIN
      deltachat-ios/Assets.xcassets/contacts.imageset/contacts@3x.png
  21. 23 0
      deltachat-ios/Assets.xcassets/create_new.imageset/Contents.json
  22. BIN
      deltachat-ios/Assets.xcassets/create_new.imageset/create_new.png
  23. BIN
      deltachat-ios/Assets.xcassets/create_new.imageset/create_new@2x.png
  24. BIN
      deltachat-ios/Assets.xcassets/create_new.imageset/create_new@3x.png
  25. 23 0
      deltachat-ios/Assets.xcassets/paper_plane.imageset/Contents.json
  26. BIN
      deltachat-ios/Assets.xcassets/paper_plane.imageset/paper_plane.png
  27. BIN
      deltachat-ios/Assets.xcassets/paper_plane.imageset/paper_plane@2x.png
  28. BIN
      deltachat-ios/Assets.xcassets/paper_plane.imageset/paper_plane@3x.png
  29. 23 0
      deltachat-ios/Assets.xcassets/settings.imageset/Contents.json
  30. BIN
      deltachat-ios/Assets.xcassets/settings.imageset/settings.png
  31. BIN
      deltachat-ios/Assets.xcassets/settings.imageset/settings@2x.png
  32. BIN
      deltachat-ios/Assets.xcassets/settings.imageset/settings@3x.png
  33. 23 0
      deltachat-ios/Assets.xcassets/splash-logo.imageset/Contents.json
  34. BIN
      deltachat-ios/Assets.xcassets/splash-logo.imageset/delta-chat@1x.png
  35. BIN
      deltachat-ios/Assets.xcassets/splash-logo.imageset/delta-chat@2x.png
  36. BIN
      deltachat-ios/Assets.xcassets/splash-logo.imageset/delta-chat@3x.png
  37. 24 4
      deltachat-ios/Base.lproj/LaunchScreen.storyboard
  38. 3 2
      deltachat-ios/BaseController.swift
  39. 21 50
      deltachat-ios/ChatListController.swift
  40. 355 226
      deltachat-ios/ChatViewController.swift
  41. 4 0
      deltachat-ios/Constants.swift
  42. 12 2
      deltachat-ios/ContactCell.swift
  43. 65 0
      deltachat-ios/ContactListController.swift
  44. 13 13
      deltachat-ios/ContactProfileViewController.swift
  45. 21 43
      deltachat-ios/ContactViewController.swift
  46. 1 1
      deltachat-ios/CredentialsController.swift
  47. 47 0
      deltachat-ios/CustomCell.swift
  48. 51 0
      deltachat-ios/NavigationController.swift
  49. 2 4
      deltachat-ios/NewChatViewController.swift
  50. 1 2
      deltachat-ios/NewGroupViewController.swift
  51. 157 0
      deltachat-ios/SettingsController.swift
  52. 46 0
      deltachat-ios/TableViewCell.swift
  53. 25 0
      deltachat-ios/UIImage+Extension.swift
  54. 52 0
      deltachat-ios/UIViewController+Extension.swift
  55. 21 14
      deltachat-ios/Utils.swift
  56. 62 0
      deltachat-ios/Wrapper.swift

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

@@ -55,6 +55,14 @@
 		78E45E2B21D176FB00D4B15E /* dc_jobthread.c in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E2A21D176FB00D4B15E /* dc_jobthread.c */; };
 		78E45E2F21D1774200D4B15E /* dc_context.h in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E2D21D1774200D4B15E /* dc_context.h */; };
 		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 */; };
+		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 */; };
+		78E45E4421D3F14A00D4B15E /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */; };
+		78E45E4C21D404AE00D4B15E /* CustomCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78E45E4B21D404AE00D4B15E /* CustomCell.swift */; };
 		7A0052A11FBC50C40048C3BF /* CredentialsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0052A01FBC50C40048C3BF /* CredentialsController.swift */; };
 		7A0052C81FBE6CB40048C3BF /* NewContactController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7A0052C71FBE6CB40048C3BF /* NewContactController.swift */; };
 		7A451D941FB1B1DB00177250 /* wrapper.c in Sources */ = {isa = PBXBuildFile; fileRef = 7A451D921FB1B1DB00177250 /* wrapper.c */; };
@@ -174,6 +182,14 @@
 		78E45E2C21D1774200D4B15E /* dc_context.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; name = dc_context.c; path = "deltachat-ios/libraries/deltachat-core/src/dc_context.c"; sourceTree = "<group>"; };
 		78E45E2D21D1774200D4B15E /* dc_context.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = dc_context.h; path = "deltachat-ios/libraries/deltachat-core/src/dc_context.h"; sourceTree = "<group>"; };
 		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>"; };
+		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>"; };
+		78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = "<group>"; };
+		78E45E4B21D404AE00D4B15E /* CustomCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCell.swift; sourceTree = "<group>"; };
 		7A0052A01FBC50C40048C3BF /* CredentialsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CredentialsController.swift; sourceTree = "<group>"; };
 		7A0052C71FBE6CB40048C3BF /* NewContactController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewContactController.swift; sourceTree = "<group>"; };
 		7A451D921FB1B1DB00177250 /* wrapper.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = wrapper.c; sourceTree = "<group>"; };
@@ -369,7 +385,9 @@
 				7A9FB15B1FB07364001FEA36 /* libraries */,
 				7A9FB1431FB061E2001FEA36 /* AppDelegate.swift */,
 				7A451DAF1FB1F84900177250 /* AppCoordinator.swift */,
+				78E45E4B21D404AE00D4B15E /* CustomCell.swift */,
 				7A9FB14A1FB061E2001FEA36 /* Assets.xcassets */,
+				78E45E4321D3F14A00D4B15E /* UIImage+Extension.swift */,
 				7A9FB14C1FB061E2001FEA36 /* LaunchScreen.storyboard */,
 				7A9FB14F1FB061E2001FEA36 /* Info.plist */,
 				7A9FB1561FB06540001FEA36 /* deltachat-ios-Bridging-Header.h */,
@@ -379,18 +397,24 @@
 				70B8882D2091B8550074812E /* ContactCell.swift */,
 				7032FF8E2149C1DB00B7EC83 /* BaseController.swift */,
 				AE0D26FC1FB1FE88002FAFCE /* ChatListController.swift */,
+				78E45E3221D3CBC000D4B15E /* AppTabBarController.swift */,
+				78E45E3D21D3D28C00D4B15E /* NavigationController.swift */,
 				7A0052A01FBC50C40048C3BF /* CredentialsController.swift */,
+				78E45E3921D3CFBC00D4B15E /* SettingsController.swift */,
 				7092474020B3869500AF8799 /* ContactProfileViewController.swift */,
 				7A0052C71FBE6CB40048C3BF /* NewContactController.swift */,
 				AEACE2DC1FB323CA00DCDD78 /* ChatViewController.swift */,
+				78E45E4121D3DB4000D4B15E /* UIViewController+Extension.swift */,
 				7AE0A5481FC42F65005ECB4B /* NewChatViewController.swift */,
 				7070FB3C20FDD9FE000DC258 /* NewGroupViewController.swift */,
+				78E45E3F21D3D70700D4B15E /* ContactListController.swift */,
 				70B08FCC21073B910097D3EA /* NewGroupMemberChoiceController.swift */,
 				7070FB9A2101ECBB000DC258 /* GroupNameController.swift */,
 				AEACE2E01FB3271700DCDD78 /* SampleData.swift */,
 				AEACE2E21FB32B5C00DCDD78 /* Constants.swift */,
 				AEACE2E41FB32E1900DCDD78 /* Utils.swift */,
 				AEACE2E81FB34D9100DCDD78 /* ContactViewController.swift */,
+				78E45E3B21D3D03700D4B15E /* TableViewCell.swift */,
 			);
 			path = "deltachat-ios";
 			sourceTree = "<group>";
@@ -636,6 +660,7 @@
 				7070FB6220FF345F000DC258 /* dc_configure.c in Sources */,
 				7A7923731FB0A2C800BC2DE5 /* signature.c in Sources */,
 				7070FB4120FF3421000DC258 /* dc_smtp.c in Sources */,
+				78E45E3321D3CBC000D4B15E /* AppTabBarController.swift in Sources */,
 				7070FB6E20FF345F000DC258 /* dc_openssl.c in Sources */,
 				7070FB6720FF345F000DC258 /* dc_simplify.c in Sources */,
 				7070FB7120FF345F000DC258 /* dc_receive_imf.c in Sources */,
@@ -651,15 +676,20 @@
 				7070FB9220FF4118000DC258 /* dc_imap.c in Sources */,
 				7070FB3D20FDD9FE000DC258 /* NewGroupViewController.swift in Sources */,
 				7070FB7720FF345F000DC258 /* dc_qr.c in Sources */,
+				78E45E4421D3F14A00D4B15E /* UIImage+Extension.swift in Sources */,
 				7A7923701FB0A2C800BC2DE5 /* packet-parse.c in Sources */,
 				70B08FCD21073B910097D3EA /* NewGroupMemberChoiceController.swift in Sources */,
+				78E45E3E21D3D28C00D4B15E /* NavigationController.swift in Sources */,
 				7A7923751FB0A2C800BC2DE5 /* validate.c in Sources */,
 				AEACE2E11FB3271700DCDD78 /* SampleData.swift in Sources */,
+				78E45E4021D3D70700D4B15E /* ContactListController.swift in Sources */,
 				7070FB6A20FF345F000DC258 /* dc_saxparser.c in Sources */,
 				7070FB8A20FF4118000DC258 /* dc_log.c in Sources */,
 				7070FB9020FF4118000DC258 /* dc_stock.c in Sources */,
 				7070FB8F20FF4118000DC258 /* dc_loginparam.c in Sources */,
 				7AE0A5491FC42F65005ECB4B /* NewChatViewController.swift in Sources */,
+				78E45E3A21D3CFBC00D4B15E /* SettingsController.swift in Sources */,
+				78E45E3C21D3D03700D4B15E /* TableViewCell.swift in Sources */,
 				7070FB7220FF345F000DC258 /* dc_key.c in Sources */,
 				7070FB7420FF345F000DC258 /* dc_e2ee.c in Sources */,
 				AE0D26FD1FB1FE88002FAFCE /* ChatListController.swift in Sources */,
@@ -670,6 +700,7 @@
 				7070FB4020FF3421000DC258 /* dc_chat.c in Sources */,
 				7070FB7320FF345F000DC258 /* dc_aheader.c in Sources */,
 				7070FB6120FF345F000DC258 /* dc_securejoin.c in Sources */,
+				78E45E4221D3DB4000D4B15E /* UIViewController+Extension.swift in Sources */,
 				7A7923741FB0A2C800BC2DE5 /* symmetric.c in Sources */,
 				7A9FB1441FB061E2001FEA36 /* AppDelegate.swift in Sources */,
 				7032FF8F2149C1DB00B7EC83 /* BaseController.swift in Sources */,
@@ -682,6 +713,7 @@
 				7070FB7620FF345F000DC258 /* dc_pgp.c in Sources */,
 				7070FB5D20FF345F000DC258 /* dc_mimefactory.c in Sources */,
 				7070FB8C20FF4118000DC258 /* dc_contact.c in Sources */,
+				78E45E4C21D404AE00D4B15E /* CustomCell.swift in Sources */,
 				7070FB7020FF345F000DC258 /* dc_lot.c in Sources */,
 				7A79236F1FB0A2C800BC2DE5 /* openssl_crypto.c in Sources */,
 				7A79236C1FB0A2C800BC2DE5 /* crypto.c in Sources */,

+ 15 - 12
deltachat-ios/AppCoordinator.swift

@@ -16,22 +16,25 @@ class AppCoordinator: Coordinator {
     let baseController = BaseController()
 
     func setupViewControllers(window: UIWindow) {
-        window.rootViewController = baseController
-        window.makeKeyAndVisible()
-        window.backgroundColor = UIColor.white
-        
         let ud = UserDefaults.standard
-        
         if ud.bool(forKey: Constants.Keys.deltachatUserProvidedCredentialsKey) {
             initCore(withCredentials: false)
-            setupInnerViewControllers()
-        } else {
-//            let email = "alice@librechat.net"
-//            let password = "foobar"
-//            initCore(email: email, password: password)
-            
-            displayCredentialsController()
         }
+        
+        window.rootViewController = AppTabBarController()
+        window.makeKeyAndVisible()
+        // window.backgroundColor = UIColor.white
+        
+        
+        
+//            setupInnerViewControllers()
+//        } else {
+////            let email = "alice@librechat.net"
+////            let password = "foobar"
+////            initCore(email: email, password: password)
+//
+//            displayCredentialsController()
+//        }
     }
     
     func displayCredentialsController(message: String? = nil, isCancellable:Bool = false) {

+ 35 - 12
deltachat-ios/AppDelegate.swift

@@ -67,36 +67,59 @@ public func callbackSwift(event: CInt, data1: CUnsignedLong, data2: CUnsignedLon
             DispatchQueue.main.async {
                 nc.post(name:Notification.Name(rawValue:"ProgressUpdated"),
                         object: nil,
-                        userInfo: ["message":"Progress updated", "date":Date()])
+                        userInfo: ["message":"Progress updated", "date": Date()])
             }
         }
         return nil
     case DC_EVENT_IS_OFFLINE:
         return nil
-    case DC_EVENT_MSGS_CHANGED:
+    case DC_EVENT_MSGS_CHANGED, DC_EVENT_MSG_READ, DC_EVENT_MSG_DELIVERED:
         // TODO: reload all views
         // e.g. when message appears that is not new, i.e. no need
         // to set badge / notification
-        
+        print("change", event)
         let nc = NotificationCenter.default
         
         DispatchQueue.main.async {
             nc.post(name:dc_notificationChanged,
                     object: nil,
-                    userInfo: ["message":"Messages Changed!", "date":Date()])
+                    userInfo: [
+                        "message_id": Int(data2),
+                        "chat_id": Int(data1),
+                        "date": Date()
+                    ])
         }
 
     case DC_EVENT_INCOMING_MSG:
         // TODO: reload all views + set notification / badge
         // mrmailbox_get_fresh_msgs
         let nc = NotificationCenter.default
+        
+        // let msg = MRMessage.init(id: Int(data2))
+        // TODO: default summary
+        // if let summary = msg.summary(chars: 32) {
+        // TODO: dispatch user notification
         DispatchQueue.main.async {
-            nc.post(name:dc_notificationIncoming,
-                    object: nil,
-                    userInfo: ["message":"Incoming Message!", "date":Date()])
+             nc.post(name:dc_notificationIncoming,
+                     object: nil,
+                     userInfo: [
+                        "message_id": Int(data2),
+                        "chat_id": Int(data1),
+                        "date": Date()
+                    ])
         }
-    default:
+    case DC_EVENT_IMAP_CONNECTED:
+        print("imap connected", data2String)
+    case DC_EVENT_SMTP_CONNECTED:
+        print("smtp connected", data2String)
+    case DC_EVENT_GET_STRING:
         break
+    case DC_EVENT_SMTP_MESSAGE_SENT:
+        print("smtp message sent", data2String)
+    case DC_EVENT_MSG_DELIVERED:
+        print("message delivered", data1, data2)
+    default:
+        print("unknown event", event, data1String, data2String)
     }
     return nil
 }
@@ -137,7 +160,7 @@ func initCore(withCredentials: Bool, advancedMode:Bool = false, model:Credential
         fatalError("Error: dc_context_new returned nil")
     }
     
-    DispatchQueue.global().async {
+    DispatchQueue.global(qos: .background).async {
         while true {
             dc_perform_imap_jobs(mailboxPointer)
             dc_perform_imap_fetch(mailboxPointer)
@@ -145,21 +168,21 @@ func initCore(withCredentials: Bool, advancedMode:Bool = false, model:Credential
         }
     }
     
-    DispatchQueue.global().async {
+    DispatchQueue.global(qos: .utility).async {
         while true {
             dc_perform_smtp_jobs(mailboxPointer)
             dc_perform_smtp_idle(mailboxPointer)
         }
     }
     
-    DispatchQueue.global().async {
+    DispatchQueue.global(qos: .background).async {
         while true {
             dc_perform_sentbox_fetch(mailboxPointer)
             dc_perform_sentbox_idle(mailboxPointer)
         }
     }
     
-    DispatchQueue.global().async {
+    DispatchQueue.global(qos: .background).async {
         while true {
             dc_perform_mvbox_fetch(mailboxPointer)
             dc_perform_mvbox_idle(mailboxPointer)

+ 50 - 0
deltachat-ios/AppTabBarController.swift

@@ -0,0 +1,50 @@
+//
+//  AppTabBarController.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+class AppTabBarController: UITabBarController {
+
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        
+        let contactListController = ContactListController()
+        let contactNavigationController = NavigationController(rootViewController: contactListController)
+        let contactImage = UIImage(named: "contacts")
+        contactNavigationController.tabBarItem = UITabBarItem.init(title: "Contacts", image: contactImage, tag: 0)
+
+        let chatListController = ChatListController()
+        let chatNavigationController = NavigationController(rootViewController: chatListController)
+        let chatImage = UIImage(named: "chat")
+        chatNavigationController.tabBarItem = UITabBarItem.init(title: "Chats", image: chatImage, tag: 1)
+        
+        let settingsController = SettingsViewController()
+        let settingsNavigationController = NavigationController(rootViewController: settingsController)
+        let settingsImage = UIImage(named: "settings")
+        settingsNavigationController.tabBarItem = UITabBarItem.init(title: "Settings", image: settingsImage, tag: 2)
+        
+        let tabBarList = [contactNavigationController, chatNavigationController, settingsNavigationController]
+        
+        viewControllers = tabBarList
+        self.selectedIndex = 1
+        
+        tabBar.tintColor = Constants.primaryColor
+    }
+    
+
+    /*
+    // MARK: - Navigation
+
+    // In a storyboard-based application, you will often want to do a little preparation before navigation
+    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
+        // Get the new view controller using segue.destination.
+        // Pass the selected object to the new view controller.
+    }
+    */
+
+}

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

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

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


BIN
deltachat-ios/Assets.xcassets/camera.imageset/camera@2x.png


BIN
deltachat-ios/Assets.xcassets/camera.imageset/camera@3x.png


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

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

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


BIN
deltachat-ios/Assets.xcassets/chat.imageset/chat@2x.png


BIN
deltachat-ios/Assets.xcassets/chat.imageset/chat@3x.png


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

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

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


BIN
deltachat-ios/Assets.xcassets/contact_card.imageset/contact_card@2x.png


BIN
deltachat-ios/Assets.xcassets/contact_card.imageset/contact_card@3x.png


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

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

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


BIN
deltachat-ios/Assets.xcassets/contacts.imageset/contacts@2x.png


BIN
deltachat-ios/Assets.xcassets/contacts.imageset/contacts@3x.png


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

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

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


BIN
deltachat-ios/Assets.xcassets/create_new.imageset/create_new@2x.png


BIN
deltachat-ios/Assets.xcassets/create_new.imageset/create_new@3x.png


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

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

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


BIN
deltachat-ios/Assets.xcassets/paper_plane.imageset/paper_plane@2x.png


BIN
deltachat-ios/Assets.xcassets/paper_plane.imageset/paper_plane@3x.png


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

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

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


BIN
deltachat-ios/Assets.xcassets/settings.imageset/settings@2x.png


BIN
deltachat-ios/Assets.xcassets/settings.imageset/settings@3x.png


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

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

BIN
deltachat-ios/Assets.xcassets/splash-logo.imageset/delta-chat@1x.png


BIN
deltachat-ios/Assets.xcassets/splash-logo.imageset/delta-chat@2x.png


BIN
deltachat-ios/Assets.xcassets/splash-logo.imageset/delta-chat@3x.png


+ 24 - 4
deltachat-ios/Base.lproj/LaunchScreen.storyboard

@@ -1,7 +1,11 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
     <dependencies>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
         <capability name="Safe area layout guides" minToolsVersion="9.0"/>
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
@@ -13,7 +17,20 @@
                     <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
-                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <subviews>
+                            <imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="splash-logo" translatesAutoresizingMaskIntoConstraints="NO" id="Y54-lv-TD5">
+                                <rect key="frame" x="132" y="231" width="111" height="205"/>
+                                <constraints>
+                                    <constraint firstAttribute="width" constant="111" id="P5k-Xd-i89"/>
+                                </constraints>
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="0.31764705882352939" green="0.28627450980392155" blue="1" alpha="1" colorSpace="custom" customColorSpace="displayP3"/>
+                        <constraints>
+                            <constraint firstItem="Y54-lv-TD5" firstAttribute="top" secondItem="6Tk-OE-BBY" secondAttribute="top" constant="211" id="5Pj-Mr-aB4"/>
+                            <constraint firstItem="Y54-lv-TD5" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="NUE-Pa-dbQ"/>
+                            <constraint firstItem="Y54-lv-TD5" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="Ort-Ty-sUq"/>
+                        </constraints>
                         <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/>
                     </view>
                 </viewController>
@@ -22,4 +39,7 @@
             <point key="canvasLocation" x="53" y="375"/>
         </scene>
     </scenes>
+    <resources>
+        <image name="splash-logo" width="200" height="200"/>
+    </resources>
 </document>

+ 3 - 2
deltachat-ios/BaseController.swift

@@ -71,7 +71,7 @@ class BaseController: UIViewController {
     }
     
     override func viewWillAppear(_ animated: Bool) {
-        
+        super.viewWillAppear(animated)
         let nc = NotificationCenter.default
         progressChangedObserver = nc.addObserver(forName:Notification.Name(rawValue:"ProgressUpdated"),
                                             object:nil, queue:nil) {
@@ -81,7 +81,8 @@ class BaseController: UIViewController {
         }
     }
     
-    override func viewWillDisappear(_ animated: Bool) {
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
         let nc = NotificationCenter.default
         if let progressChangedObserver = self.progressChangedObserver {
             nc.removeObserver(progressChangedObserver)

+ 21 - 50
deltachat-ios/ChatListController.swift

@@ -20,7 +20,7 @@ class ChatListController: UIViewController {
     var msgChangedObserver: Any?
     var incomingMsgObserver: Any?
     
-    var dotsButton: UIBarButtonItem!
+    var newButton: UIBarButtonItem!
     
     func getChatList() {
         guard let chatlistPointer = dc_get_chatlist(mailboxPointer, 0, nil, 0) else {
@@ -36,9 +36,20 @@ class ChatListController: UIViewController {
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = true
+        }
+        
         getChatList()
     }
     
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+    }
+    
     override func viewDidAppear(_ animated: Bool) {
         super.viewDidAppear(animated)
         let nc = NotificationCenter.default
@@ -57,8 +68,8 @@ class ChatListController: UIViewController {
         }
     }
     
-    override func viewWillDisappear(_ animated: Bool) {
-        super.viewWillDisappear(animated)
+    override func viewDidDisappear(_ animated: Bool) {
+        super.viewDidDisappear(animated)
         
         let nc = NotificationCenter.default
         if let msgChangedObserver = self.msgChangedObserver {
@@ -71,8 +82,8 @@ class ChatListController: UIViewController {
     
     override func viewDidLoad() {
         super.viewDidLoad()
-        title = "Delta Chat"
-        navigationController?.navigationBar.prefersLargeTitles = false
+        title = "Chats"
+        navigationController?.navigationBar.prefersLargeTitles = true
         view.addSubview(chatTable)
         chatTable.translatesAutoresizingMaskIntoConstraints = false
         chatTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
@@ -82,51 +93,12 @@ class ChatListController: UIViewController {
         chatTable.dataSource = chatTableDataSource
         chatTableDelegate.chatPresenter = self
         chatTable.delegate = chatTableDelegate
-        let dotsImage:UIImage = #imageLiteral(resourceName: "ic_more_vert")
-        dotsButton = UIBarButtonItem(image: dotsImage, landscapeImagePhone: nil, style: .plain, target: self, action: #selector(didPressDotsButton))
+        let newImage:UIImage = UIImage(named: "create_new")!
+        newButton = UIBarButtonItem(image: newImage, landscapeImagePhone: nil, style: .plain, target: self, action: #selector(didPressNewChat))
     
-        navigationItem.rightBarButtonItem = dotsButton
+        navigationItem.rightBarButtonItem = newButton
     }
     
-    @objc func didPressDotsButton() {
-        let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
-        actionSheet.addAction(UIAlertAction(title: "New chat",
-                                            style: .default,
-                                            handler: {
-                                                [unowned self]
-                                                a in
-                                                self.didPressNewChat()
-        }))
-        /* actionSheet.addAction(UIAlertAction(title: "New group",
-                                            style: .default,
-                                            handler: {a in print("New group")}))
-actionSheet.addAction(UIAlertAction(title: "Scan QR code",
-                                            style: .default,
-                                            handler: {a in print("Scan QR code")}))
-        actionSheet.addAction(UIAlertAction(title: "Show QR code",
-                                            style: .default,
-                                            handler: {a in print("Show QR code")}))
-        actionSheet.addAction(UIAlertAction(title: "Contact requests",
-                                            style: .default,
-                                            handler: {a in print("Contact requests")}))*/
-        actionSheet.addAction(UIAlertAction(title: "Settings",
-                                            style: .default,
-                                            handler: {a in
-                                                AppDelegate.appCoordinator.displayCredentialsController(isCancellable: true)
-                                                
-        }))
-        actionSheet.addAction(UIAlertAction(title: "Cancel",
-                                            style: .cancel,
-                                            handler: {a in print("Cancel")}))
-        
-        if let popoverController = actionSheet.popoverPresentationController {
-            popoverController.barButtonItem = dotsButton
-        }
-        
-        present(actionSheet, animated: true, completion: nil)
-        
-        
-    }
     
     @objc func didPressNewChat() {
         let ncv = NewChatViewController()
@@ -194,9 +166,8 @@ class ChatTableDataSource: NSObject, UITableViewDataSource  {
         let summary = chatList.summary(index: row)
         
         cell.nameLabel.text = chat.name
-        cell.initialsLabel.text = Utils.getInitials(inputName: chat.name)
-        let contactColor = Utils.color(row: row, colors: Constants.chatColors)
-        cell.setColor(contactColor)
+        cell.setBackupImage(name: chat.name, color: chat.color)
+
         let result1 = summary.text1 ?? ""
         let result2 = summary.text2 ?? ""
         let result:String

+ 355 - 226
deltachat-ios/ChatViewController.swift

@@ -12,16 +12,24 @@ import MapKit
 import MessageInputBar
 
 class ChatViewController: MessagesViewController {
+    let outgoingAvatarOverlap: CGFloat = 17.5
+
     let chatId: Int
+    let refreshControl = UIRefreshControl()
     var messageIds:[Int] = []
+    var messageList: [Message] = []
 
     var msgChangedObserver: Any?
     var incomingMsgObserver: Any?
     
+    override var preferredStatusBarStyle: UIStatusBarStyle {
+        return .lightContent
+    }
+    
     init(chatId: Int) {
         self.chatId = chatId
         super.init(nibName: nil, bundle: nil)
-        self.getMessageIds()
+        // self.getMessageIds()
         
         /*
         let chat = MRChat(id: chatId)
@@ -33,6 +41,49 @@ class ChatViewController: MessagesViewController {
 
     }
     
+    @objc
+    func loadMoreMessages() {
+        DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 1) {
+            self.getMessageIds()
+            DispatchQueue.main.async {
+                self.messageList = self.messageIds.map(self.idToMessage)
+                self.messagesCollectionView.reloadDataAndKeepOffset()
+                self.refreshControl.endRefreshing()
+            }
+        }
+    }
+    
+    func loadFirstMessages() {
+        DispatchQueue.global(qos: .userInitiated).async {
+            self.getMessageIds()
+            DispatchQueue.main.async {
+                self.messageList = self.messageIds.map(self.idToMessage)
+                self.messagesCollectionView.reloadData()
+                self.refreshControl.endRefreshing()
+            }
+        }
+    }
+    
+    private func idToMessage(messageId: Int) -> Message {
+        let message = MRMessage(id: messageId)
+        let contact = MRContact(id: message.fromContactId)
+        
+        let sender = Sender(id: "\(contact.id)", displayName: contact.name)
+        if let image = message.image {
+            return Message(image: image, sender: sender, messageId: "\(messageId)", date: Date(timeIntervalSince1970: Double(message.timestamp)))
+        } else {
+            return Message(text: message.text ?? "- empty -", sender: sender, messageId: "\(messageId)", date: Date(timeIntervalSince1970: Double(message.timestamp)))
+        }
+    }
+    
+    private func messageToMRMessage(message: Message) -> MRMessage? {
+        if let id = Int(message.messageId) {
+            return MRMessage(id: id)
+        }
+        
+        return nil
+    }
+    
     var textDraft:String? {
         // FIXME: need to free pointer
         if let draft = dc_get_draft(mailboxPointer, UInt32(chatId)) {
@@ -54,14 +105,6 @@ class ChatViewController: MessagesViewController {
         fatalError("init(coder:) has not been implemented")
     }
     
-    var messageList: [Message] = [] {
-        didSet {
-            DispatchQueue.main.async {
-                self.messagesCollectionView.reloadData()
-            }
-        }
-    }
-    
     override func viewWillAppear(_ animated: Bool) {
         super.viewWillAppear(animated)
         
@@ -70,18 +113,23 @@ class ChatViewController: MessagesViewController {
                                             object:nil, queue: OperationQueue.main) {
                                                 notification in
                                                 print("----------- MrEventMsgsChanged notification received --------")
-                                                self.getMessageIds()
-                                                self.messagesCollectionView.reloadData()
-                                                self.messagesCollectionView.scrollToBottom()
+                                                if let ui = notification.userInfo {
+                                                    if self.chatId == ui["chat_id"] as! Int {
+                                                        self.updateMessage(ui["message_id"] as! Int)
+                                                    }
+                                                }
         }
         
         incomingMsgObserver = nc.addObserver(forName:dc_notificationIncoming,
                                              object:nil, queue: OperationQueue.main) {
                                                 notification in
                                                 print("----------- MrEventIncomingMsg received --------")
-                                                self.getMessageIds()
-                                                self.messagesCollectionView.reloadData()
-                                                self.messagesCollectionView.scrollToBottom()
+                                                if let ui = notification.userInfo {
+                                                    if self.chatId == ui["chat_id"] as! Int {
+                                                        let id = ui["message_id"] as! Int
+                                                        self.insertMessage(self.idToMessage(messageId: id))
+                                                    }
+                                                }
         }
     }
     
@@ -96,7 +144,7 @@ class ChatViewController: MessagesViewController {
         }
     }
     
-    override func viewWillDisappear(_ animated: Bool) {
+    override func viewDidDisappear(_ animated: Bool) {
         super.viewDidDisappear(animated)
         
         setTextDraft()
@@ -111,175 +159,143 @@ class ChatViewController: MessagesViewController {
     
     override func viewDidLoad() {
         super.viewDidLoad()
-        
+
         let chat = MRChat(id: self.chatId)
-        title = chat.name
+        
+        configureMessageCollectionView()
+        configureMessageInputBar()
+        updateTitleView(title: chat.name, subtitle: nil)
         
         messageInputBar.inputTextView.text = textDraft
         messageInputBar.inputTextView.becomeFirstResponder()
         
-        DispatchQueue.global(qos: .userInitiated).async {
-            SampleData.shared.getMessages(count: 10) { messages in
-                DispatchQueue.main.async {
-                    self.messageList = messages
-                }
-            }
-        }
-        
+        loadFirstMessages()
+    }
+    
+    func configureMessageCollectionView() {
         messagesCollectionView.messagesDataSource = self
-        messagesCollectionView.messagesLayoutDelegate = self
-        messagesCollectionView.messagesDisplayDelegate = self
         messagesCollectionView.messageCellDelegate = self
+       
         
-        messageInputBar.delegate = self
-        messageInputBar.sendButton.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-        // scrollsToBottomOnFirstLayout = true //default false
         scrollsToBottomOnKeyboardBeginsEditing = true // default false
+        maintainPositionOnKeyboardFrameChanged = true // default false
         
-        /*navigationItem.rightBarButtonItem = UIBarButtonItem(image: UIImage(named: "ic_keyboard"),
-                                                            style: .plain,
-                                                            target: self,
-                                                            action: #selector(handleKeyboardButton))*/
+        messagesCollectionView.addSubview(refreshControl)
+        refreshControl.addTarget(self, action: #selector(loadMoreMessages), for: .valueChanged)
         
-        let photoButton = UIBarButtonItem(barButtonSystemItem: .camera, target: self, action: #selector(didPressPhotoButton))
-        navigationItem.rightBarButtonItem = photoButton
-    }
-    
-
-    
-    @objc func handleKeyboardButton() {
         
-        messageInputBar.inputTextView.resignFirstResponder()
-        let actionSheetController = UIAlertController(title: "Change Keyboard Style", message: nil, preferredStyle: .actionSheet)
-        let actions = [
-            UIAlertAction(title: "Slack", style: .default, handler: { _ in
-                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1, execute: {
-                    self.slack()
-                })
-            }),
-            UIAlertAction(title: "iMessage", style: .default, handler: { _ in
-                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1, execute: {
-                    self.iMessage()
-                })
-            }),
-            UIAlertAction(title: "Default", style: .default, handler: { _ in
-                DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1, execute: {
-                    self.defaultStyle()
-                })
-            }),
-            UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
-        ]
-        actions.forEach { actionSheetController.addAction($0) }
-        actionSheetController.view.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-        present(actionSheetController, animated: true, completion: nil)
-    }
-    
-    // MARK: - Keyboard Style
-    
-    func slack() {
-        defaultStyle()
-        messageInputBar.isTranslucent = false
-        messageInputBar.inputTextView.backgroundColor = .clear
-        messageInputBar.inputTextView.layer.borderWidth = 0
-        let items = [
-            makeButton(named: "ic_camera").onTextViewDidChange { button, textView in
-                button.isEnabled = textView.text.isEmpty
-            },
-            makeButton(named: "ic_at").onSelected {
-                $0.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-                print("@ Selected")
-            },
-            makeButton(named: "ic_hashtag").onSelected {
-                $0.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-                print("# Selected")
-            },
-            .flexibleSpace,
-            makeButton(named: "ic_library").onTextViewDidChange { button, textView in
-                button.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-                button.isEnabled = textView.text.isEmpty
-            },
-            messageInputBar.sendButton
-                .configure {
-                    $0.layer.cornerRadius = 8
-                    $0.layer.borderWidth = 1.5
-                    $0.layer.borderColor = $0.titleColor(for: .disabled)?.cgColor
-                    $0.setTitleColor(.white, for: .normal)
-                    $0.setTitleColor(.white, for: .highlighted)
-                    $0.setSize(CGSize(width: 52, height: 30), animated: true)
-                }.onDisabled {
-                    $0.layer.borderColor = $0.titleColor(for: .disabled)?.cgColor
-                    $0.backgroundColor = .white
-                }.onEnabled {
-                    $0.backgroundColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-                    $0.layer.borderColor = UIColor.clear.cgColor
-                }.onSelected {
-                    // We use a transform becuase changing the size would cause the other views to relayout
-                    $0.transform = CGAffineTransform(scaleX: 1.2, y: 1.2)
-                }.onDeselected {
-                    $0.transform = CGAffineTransform.identity
-            }
-        ]
-        items.forEach { $0.tintColor = .lightGray }
+        let layout = messagesCollectionView.collectionViewLayout as? MessagesCollectionViewFlowLayout
+        layout?.sectionInset = UIEdgeInsets(top: 1, left: 8, bottom: 1, right: 8)
         
-        // We can change the container insets if we want
-        messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
-        messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 5)
+        // Hide the outgoing avatar and adjust the label alignment to line up with the messages
+        layout?.setMessageOutgoingAvatarSize(.zero)
+        layout?.setMessageOutgoingMessageTopLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
+        layout?.setMessageOutgoingMessageBottomLabelAlignment(LabelAlignment(textAlignment: .right, textInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8)))
         
-        // Since we moved the send button to the bottom stack lets set the right stack width to 0
-        messageInputBar.setRightStackViewWidthConstant(to: 0, animated: true)
+        // Set outgoing avatar to overlap with the message bubble
+        layout?.setMessageIncomingMessageTopLabelAlignment(LabelAlignment(textAlignment: .left, textInsets: UIEdgeInsets(top: 0, left: 18, bottom: outgoingAvatarOverlap, right: 0)))
+        layout?.setMessageIncomingAvatarSize(CGSize(width: 30, height: 30))
+        layout?.setMessageIncomingMessagePadding(UIEdgeInsets(top: -outgoingAvatarOverlap, left: -18, bottom: outgoingAvatarOverlap, right: 18))
+        
+        layout?.setMessageIncomingAccessoryViewSize(CGSize(width: 30, height: 30))
+        layout?.setMessageIncomingAccessoryViewPadding(HorizontalEdgeInsets(left: 8, right: 0))
+        layout?.setMessageOutgoingAccessoryViewSize(CGSize(width: 30, height: 30))
+        layout?.setMessageOutgoingAccessoryViewPadding(HorizontalEdgeInsets(left: 0, right: 8))
+        
+        messagesCollectionView.messagesLayoutDelegate = self
+        messagesCollectionView.messagesDisplayDelegate = self
         
-        // Finally set the items
-        messageInputBar.setStackViewItems(items, forStack: .bottom, animated: true)
     }
     
-    func iMessage() {
-        defaultStyle()
-        messageInputBar.isTranslucent = false
+    func configureMessageInputBar() {
+        messageInputBar.delegate = self
+        messageInputBar.inputTextView.tintColor = Constants.primaryColor
+        messageInputBar.sendButton.tintColor = Constants.primaryColor
+        
+        messageInputBar.isTranslucent = true
         messageInputBar.separatorLine.isHidden = true
+        messageInputBar.inputTextView.tintColor = Constants.primaryColor
+        
+        messageInputBar.delegate = self
+        scrollsToBottomOnKeyboardBeginsEditing = true
+        
         messageInputBar.inputTextView.backgroundColor = UIColor(red: 245/255, green: 245/255, blue: 245/255, alpha: 1)
         messageInputBar.inputTextView.placeholderTextColor = UIColor(red: 0.6, green: 0.6, blue: 0.6, alpha: 1)
-        messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 36)
-        messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 36)
+        messageInputBar.inputTextView.textContainerInset = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 38)
+        messageInputBar.inputTextView.placeholderLabelInsets = UIEdgeInsets(top: 8, left: 20, bottom: 8, right: 38)
         messageInputBar.inputTextView.layer.borderColor = UIColor(red: 200/255, green: 200/255, blue: 200/255, alpha: 1).cgColor
         messageInputBar.inputTextView.layer.borderWidth = 1.0
         messageInputBar.inputTextView.layer.cornerRadius = 16.0
         messageInputBar.inputTextView.layer.masksToBounds = true
         messageInputBar.inputTextView.scrollIndicatorInsets = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0)
-        messageInputBar.setRightStackViewWidthConstant(to: 36, animated: true)
-        messageInputBar.setStackViewItems([messageInputBar.sendButton], forStack: .right, animated: true)
-        messageInputBar.sendButton.imageView?.backgroundColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-        messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 2, left: 2, bottom: 2, right: 2)
-        messageInputBar.sendButton.setSize(CGSize(width: 36, height: 36), animated: true)
-        messageInputBar.sendButton.image = #imageLiteral(resourceName: "ic_up")
-        messageInputBar.sendButton.title = nil
-        messageInputBar.sendButton.imageView?.layer.cornerRadius = 16
-        messageInputBar.sendButton.backgroundColor = .clear
-        messageInputBar.textViewPadding.right = -38
+        configureInputBarItems()
     }
     
-    func defaultStyle() {
-        let newMessageInputBar = MessageInputBar()
-        newMessageInputBar.sendButton.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-        newMessageInputBar.delegate = self
-        messageInputBar = newMessageInputBar
-        reloadInputViews()
+    private func configureInputBarItems() {
+        messageInputBar.setLeftStackViewWidthConstant(to: 44, animated: false)
+        messageInputBar.setRightStackViewWidthConstant(to: 36, animated: false)
+        
+        let sendButtonImage = UIImage(named: "paper_plane")?.withRenderingMode(.alwaysTemplate)
+        messageInputBar.sendButton.image = sendButtonImage
+        messageInputBar.sendButton.tintColor = UIColor(white: 1, alpha: 1)
+        messageInputBar.sendButton.backgroundColor = UIColor(white: 0.9, alpha: 1)
+        messageInputBar.sendButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 0, bottom: 6, right: 0)
+        messageInputBar.sendButton.setSize(CGSize(width: 34, height: 34), animated: false)
+
+        messageInputBar.sendButton.title = nil
+        messageInputBar.sendButton.layer.cornerRadius = 18
+        
+        messageInputBar.textViewPadding.right = -40
+        
+        let leftItems = [
+            InputBarButtonItem()
+                .configure {
+                    $0.spacing = .fixed(0)
+                    $0.image = UIImage(named: "camera")?.withRenderingMode(.alwaysTemplate)
+                    $0.setSize(CGSize(width: 36, height: 36), animated: false)
+                    $0.tintColor = UIColor(white: 0.8, alpha: 1)
+                }.onSelected {
+                    $0.tintColor = Constants.primaryColor
+                }.onDeselected {
+                    $0.tintColor = UIColor(white: 0.8, alpha: 1)
+                }.onTouchUpInside {  _ in
+                    self.didPressPhotoButton()
+                }
+        ]
+        messageInputBar.setStackViewItems(leftItems, forStack: .left, animated: false)
+        
+        // This just adds some more flare
+        messageInputBar.sendButton
+            .onEnabled { item in
+                UIView.animate(withDuration: 0.3, animations: {
+                    item.backgroundColor = Constants.primaryColor
+                })
+            }.onDisabled { item in
+                UIView.animate(withDuration: 0.3, animations: {
+                    item.backgroundColor = UIColor(white: 0.9, alpha: 1)
+                })
+        }
     }
     
-    // MARK: - Helpers
+    // MARK: - UICollectionViewDataSource
     
-    func makeButton(named: String) -> InputBarButtonItem {
-        return InputBarButtonItem()
-            .configure {
-                $0.spacing = .fixed(10)
-                $0.image = UIImage(named: named)?.withRenderingMode(.alwaysTemplate)
-                $0.setSize(CGSize(width: 30, height: 30), animated: true)
-            }.onSelected {
-                $0.tintColor = UIColor(red: 69/255, green: 193/255, blue: 89/255, alpha: 1)
-            }.onDeselected {
-                $0.tintColor = UIColor.lightGray
-            }.onTouchUpInside { _ in
-                print("Item Tapped")
+    public override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
+        
+        guard let messagesDataSource = messagesCollectionView.messagesDataSource else {
+            fatalError("Ouch. nil data source for messages")
         }
+        
+        //        guard !isSectionReservedForTypingBubble(indexPath.section) else {
+        //            return super.collectionView(collectionView, cellForItemAt: indexPath)
+        //        }
+        
+        let message = messagesDataSource.messageForItem(at: indexPath, in: messagesCollectionView)
+        if case .custom = message.kind {
+            let cell = messagesCollectionView.dequeueReusableCell(CustomCell.self, for: indexPath)
+            cell.configure(with: message, at: indexPath, and: messagesCollectionView)
+            return cell
+        }
+        return super.collectionView(collectionView, cellForItemAt: indexPath)
     }
 }
 
@@ -287,7 +303,7 @@ class ChatViewController: MessagesViewController {
 
 extension ChatViewController: MessagesDataSource {
     func numberOfSections(in messagesCollectionView: MessagesCollectionView) -> Int {
-        return 1
+        return messageList.count
     }
     
     func currentSender() -> Sender {
@@ -296,22 +312,8 @@ extension ChatViewController: MessagesDataSource {
         return currentSender
     }
     
-    func numberOfItems(inSection section: Int, in messagesCollectionView: MessagesCollectionView) -> Int {
-        return self.messageIds.count
-    }
-    
     func messageForItem(at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageType {
-        let row = indexPath.row
-        let messageId = messageIds[row]
-        let message = MRMessage(id: messageId)
-        let contact = MRContact(id: message.fromContactId)
-        
-        let sender = Sender(id: "\(contact.id)", displayName: contact.name)
-        if let image = message.image {
-            return Message(image: image, sender: sender, messageId: "\(messageId)", date: Date(timeIntervalSince1970: Double(message.timestamp)))
-        } else {
-            return Message(text: message.text ?? "- empty -", sender: sender, messageId: "\(messageId)", date: Date(timeIntervalSince1970: Double(message.timestamp)))
-        }
+        return messageList[indexPath.section]
     }
     
     func avatar(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> Avatar {
@@ -319,73 +321,185 @@ extension ChatViewController: MessagesDataSource {
     }
     
     func cellTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-        let name = message.sender.displayName
-        return NSAttributedString(string: name, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
+        if isTimeLabelVisible(at: indexPath) {
+            return NSAttributedString(string: MessageKitDateFormatter.shared.string(from: message.sentDate), attributes: [NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 10), NSAttributedString.Key.foregroundColor: UIColor.darkGray])
+        }
+        
+        return nil
     }
     
-    func cellBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
-        let formatter = DateFormatter()
-        formatter.dateStyle = .medium
-        let dateString = formatter.string(from: message.sentDate)
-        return NSAttributedString(string: dateString, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption2)])
+    func messageTopLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+        if !isPreviousMessageSameSender(at: indexPath) {
+            let name = message.sender.displayName
+            return NSAttributedString(string: name, attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
+        }
+        return nil
     }
+
     
+    func isTimeLabelVisible(at indexPath: IndexPath) -> Bool {
+        // TODO: better heuristic when to show the time label
+        return indexPath.section % 3 == 0 && !isPreviousMessageSameSender(at: indexPath)
+    }
+    
+    func isPreviousMessageSameSender(at indexPath: IndexPath) -> Bool {
+        guard indexPath.section - 1 >= 0 else { return false }
+        return messageList[indexPath.section].sender == messageList[indexPath.section - 1].sender
+    }
+    
+    func isNextMessageSameSender(at indexPath: IndexPath) -> Bool {
+        guard indexPath.section + 1 < messageList.count else { return false }
+        return messageList[indexPath.section].sender == messageList[indexPath.section + 1].sender
+    }
+    
+    func messageBottomLabelAttributedText(for message: MessageType, at indexPath: IndexPath) -> NSAttributedString? {
+
+        guard indexPath.section < messageList.count else { return nil }
+        if let m = messageToMRMessage(message: messageList[indexPath.section]) {
+            print("state", m.stateOutDescription(), !isNextMessageSameSender(at: indexPath) && isFromCurrentSender(message: message))
+            if !isNextMessageSameSender(at: indexPath) && isFromCurrentSender(message: message) {
+                return NSAttributedString(string: m.stateOutDescription(), attributes: [NSAttributedString.Key.font: UIFont.preferredFont(forTextStyle: .caption1)])
+            }
+        }
+        return nil
+    }
+    
+    func updateMessage(_ messageId: Int) {
+        let messageIdStr = String(messageId)
+        if let index = messageList.firstIndex(where:{$0.messageId == messageIdStr}) {
+            messageList[index] = idToMessage(messageId: messageId)
+            // Reload section to update header/footer labels
+            messagesCollectionView.performBatchUpdates({
+                messagesCollectionView.reloadSections([index])
+                if index > 0 {
+                    messagesCollectionView.reloadSections([index - 1])
+                }
+                if index < messageList.count - 1 {
+                    messagesCollectionView.reloadSections([index + 1])
+                }
+            }, completion: { [weak self] _ in
+                if self?.isLastSectionVisible() == true {
+                    self?.messagesCollectionView.scrollToBottom(animated: true)
+                }
+            })
+        } else {
+            insertMessage(idToMessage(messageId: messageId))
+        }
+    }
+    
+    func insertMessage(_ message: Message) {
+        messageList.append(message)
+        // Reload last section to update header/footer labels and insert a new one
+        messagesCollectionView.performBatchUpdates({
+            messagesCollectionView.insertSections([messageList.count - 1])
+            if messageList.count >= 2 {
+                messagesCollectionView.reloadSections([messageList.count - 2])
+            }
+        }, completion: { [weak self] _ in
+            if self?.isLastSectionVisible() == true {
+                self?.messagesCollectionView.scrollToBottom(animated: true)
+            }
+        })
+    }
+    
+    func isLastSectionVisible() -> Bool {
+        guard !messageList.isEmpty else { return false }
+        
+        let lastIndexPath = IndexPath(item: 0, section: messageList.count - 1)
+        return messagesCollectionView.indexPathsForVisibleItems.contains(lastIndexPath)
+    }
 }
 
 // MARK: - MessagesDisplayDelegate
 
 extension ChatViewController: MessagesDisplayDelegate {
     
-    func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
-        return isFromCurrentSender(message: message) ? Constants.Color.bubble : UIColor(red: 230/255, green: 230/255, blue: 230/255, alpha: 1)
-    }
+    // MARK: - Text Messages
     
     func textColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
         return .darkText
     }
     
+    // MARK: - All Messages
+    
+    func backgroundColor(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIColor {
+        return isFromCurrentSender(message: message) ? Constants.messagePrimaryColor : Constants.messageSecondaryColor
+    }
+    
     func messageStyle(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> MessageStyle {
-        let corner: MessageStyle.TailCorner = isFromCurrentSender(message: message) ? .bottomRight : .bottomLeft
-        return .bubbleTail(corner, .curved)
-        //        let configurationClosure = { (view: MessageContainerView) in}
-        //        return .custom(configurationClosure)
+        
+        var corners: UIRectCorner = []
+        
+        if isFromCurrentSender(message: message) {
+            corners.formUnion(.topLeft)
+            corners.formUnion(.bottomLeft)
+            if !isPreviousMessageSameSender(at: indexPath) {
+                corners.formUnion(.topRight)
+            }
+            if !isNextMessageSameSender(at: indexPath) {
+                corners.formUnion(.bottomRight)
+            }
+        } else {
+            corners.formUnion(.topRight)
+            corners.formUnion(.bottomRight)
+            if !isPreviousMessageSameSender(at: indexPath) {
+                corners.formUnion(.topLeft)
+            }
+            if !isNextMessageSameSender(at: indexPath) {
+                corners.formUnion(.bottomLeft)
+            }
+        }
+        
+        return .custom { view in
+            let radius: CGFloat = 16
+            let path = UIBezierPath(roundedRect: view.bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
+            let mask = CAShapeLayer()
+            mask.path = path.cgPath
+            view.layer.mask = mask
+        }
     }
     
+    func configureAvatarView(_ avatarView: AvatarView, for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) {
+        
+        if let id = Int(messageList[indexPath.section].messageId) {
+            let message = MRMessage(id: id)
+            let contact = message.fromContact
+            let avatar = Avatar(image: contact.profileImage, initials: Utils.getInitials(inputName: contact.name))
+            avatarView.set(avatar: avatar)
+            avatarView.isHidden = isNextMessageSameSender(at: indexPath)
+        }
+    }
+    
+    func enabledDetectors(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> [DetectorType] {
+        return [.url, .date, .phoneNumber, .address]
+    }
 }
 
 // MARK: - MessagesLayoutDelegate
 
 extension ChatViewController: MessagesLayoutDelegate {
-    func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
-        return 40
+    
+    func cellTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        if isTimeLabelVisible(at: indexPath) {
+            return 18
+        }
+        return 0
     }
     
-    func messagePadding(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> UIEdgeInsets {
+    func messageTopLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
         if isFromCurrentSender(message: message) {
-            return UIEdgeInsets(top: 0, left: 30, bottom: 0, right: 4)
+            return !isPreviousMessageSameSender(at: indexPath) ? 20 : 0
         } else {
-            return UIEdgeInsets(top: 0, left: 4, bottom: 0, right: 30)
+            return !isPreviousMessageSameSender(at: indexPath) ? (20 + outgoingAvatarOverlap) : 0
         }
     }
     
-//    func cellTopLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
-//        if isFromCurrentSender(message: message) {
-//            return .messageTrailing(UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10))
-//        } else {
-//            return .messageLeading(UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0))
-//        }
-//    }
-//    
-//    func cellBottomLabelAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> LabelAlignment {
-//        if isFromCurrentSender(message: message) {
-//            return .messageLeading(UIEdgeInsets(top: 0, left: 10, bottom: 0, right: 0))
-//        } else {
-//            return .messageTrailing(UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 10))
-//        }
-//    }
-    
-    func avatarAlignment(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> AvatarPosition.Horizontal {
-        return AvatarPosition.Horizontal.cellLeading
+    func messageBottomLabelHeight(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        return (!isNextMessageSameSender(at: indexPath) && isFromCurrentSender(message: message)) ? 16 : 0
+    }
+
+    func heightForLocation(message: MessageType, at indexPath: IndexPath, with maxWidth: CGFloat, in messagesCollectionView: MessagesCollectionView) -> CGFloat {
+        return 40
     }
     
     func footerViewSize(for message: MessageType, at indexPath: IndexPath, in messagesCollectionView: MessagesCollectionView) -> CGSize {
@@ -394,13 +508,16 @@ extension ChatViewController: MessagesLayoutDelegate {
     }
     
     @objc func didPressPhotoButton() {
-        // TODO: don't panic in simulator, when the camera is not available
-        let imagePicker = UIImagePickerController()
-        imagePicker.sourceType = .camera
-        imagePicker.cameraDevice = .rear
-        imagePicker.delegate = self
-        imagePicker.allowsEditing = true
-        self.present(imagePicker, animated: true, completion: nil)
+        if UIImagePickerController.isSourceTypeAvailable(.camera) {
+            let imagePicker = UIImagePickerController()
+            imagePicker.sourceType = .camera
+            imagePicker.cameraDevice = .rear
+            imagePicker.delegate = self
+            imagePicker.allowsEditing = true
+            self.present(imagePicker, animated: true, completion: nil)
+        } else {
+            print("no camera available")
+        }
     }
     
     fileprivate func saveImage(image: UIImage) -> String? {
@@ -486,27 +603,43 @@ extension ChatViewController: MessageCellDelegate {
 extension ChatViewController: MessageLabelDelegate {
     
     func didSelectAddress(_ addressComponents: [String : String]) {
-        print("Address Selected: \(addressComponents)")
+        let mapAddress = Utils.formatAddressForQuery(address: addressComponents)
+        if let escapedMapAddress = mapAddress.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+            // Use query, to handle malformed addresses
+            if let url = URL(string: "http://maps.apple.com/?q=\(escapedMapAddress)") {
+                print("open address", url, addressComponents)
+                UIApplication.shared.open(url as URL)
+            }
+        }
     }
     
     func didSelectDate(_ date: Date) {
-        print("Date Selected: \(date)")
+        print("date open", date)
+        let interval = date.timeIntervalSinceReferenceDate
+        if let url = NSURL(string: "calshow:\(interval)") {
+            print("open", url)
+            UIApplication.shared.open(url as URL)
+        }
     }
     
     func didSelectPhoneNumber(_ phoneNumber: String) {
-        print("Phone Number Selected: \(phoneNumber)")
+        print("phone open", phoneNumber)
+        if let escapedPhoneNumber = phoneNumber.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) {
+            if let url = NSURL(string: "tel:\(escapedPhoneNumber)") {
+                UIApplication.shared.open(url as URL)
+            }
+        }
     }
     
     func didSelectURL(_ url: URL) {
-        print("URL Selected: \(url)")
+        UIApplication.shared.open(url)
     }
     
 }
-/*
-// MARK: - LocationMessageDisplayDelegate
 
+// MARK: - LocationMessageDisplayDelegate
+/*
 extension ChatViewController: LocationMessageDisplayDelegate {
-    
     func annotationViewForLocation(message: MessageType, at indexPath: IndexPath, in messageCollectionView: MessagesCollectionView) -> MKAnnotationView? {
         let annotationView = MKAnnotationView(annotation: nil, reuseIdentifier: nil)
         let pinImage = #imageLiteral(resourceName: "ic_block_36pt").withRenderingMode(.alwaysTemplate)
@@ -525,24 +658,20 @@ extension ChatViewController: LocationMessageDisplayDelegate {
             }, completion: nil)
         }
     }
-    
 }
 */
 
+
 // MARK: - MessageInputBarDelegate
  
 
 extension ChatViewController: MessageInputBarDelegate {
     
     func messageInputBar(_ inputBar: MessageInputBar, didPressSendButtonWith text: String) {
-//        messageList.append(Message(text: text, sender: currentSender(), messageId: UUID().uuidString, date: Date()))
         DispatchQueue.global().async {
             dc_send_text_msg(mailboxPointer, UInt32(self.chatId), text)
         }
         print(text)
         inputBar.inputTextView.text = String()
-//        messagesCollectionView.reloadData()
-//        messagesCollectionView.scrollToBottom()
     }
 }
-

+ 4 - 0
deltachat-ios/Constants.swift

@@ -20,5 +20,9 @@ struct Constants {
         static let deltachatImapEmailKey = "__DELTACHAT_IMAP_EMAIL_KEY__"
         static let deltachatImapPasswordKey = "__DELTACHAT_IMAP_PASSWORD_KEY__"
     }
+    
+    static let primaryColor = UIColor(red: 81/255, green: 73/255, blue: 255/255, alpha: 1)
+    static let messagePrimaryColor = UIColor(red: 234/255, green: 233/255, blue: 246/255, alpha: 1)
+    static let messageSecondaryColor = UIColor(red: 245/255, green: 245/255, blue: 245/255, alpha: 1)
 }
 

+ 12 - 2
deltachat-ios/ContactCell.swift

@@ -9,10 +9,10 @@
 import UIKit
 
 class ContactCell: UITableViewCell {
-    //Klasse initialisieren nachschauen
     let initialsLabel:UILabel = UILabel()
     let nameLabel = UILabel()
     let emailLabel = UILabel()
+    
     var darkMode: Bool = false {
         didSet {
             if darkMode {
@@ -24,8 +24,8 @@ class ContactCell: UITableViewCell {
     }
     
     override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        //Init von der Superklasse aufrufen nachschauen
         super.init(style: style, reuseIdentifier: reuseIdentifier)
+   
         //configure and layout initialsLabel
         let initialsLabelSize:CGFloat = 60
         let initialsLabelCornerRadius = initialsLabelSize/2
@@ -59,6 +59,16 @@ class ContactCell: UITableViewCell {
         emailLabel.textColor = UIColor.gray
     }
     
+    func setImage(_ img: UIImage) {
+        let attachment = NSTextAttachment()
+        attachment.image = img
+        initialsLabel.attributedText = NSAttributedString(attachment: attachment)
+    }
+    
+    func setBackupImage(name: String, color: UIColor) {
+        initialsLabel.text = Utils.getInitials(inputName: name)
+        setColor(color)
+    }
     
     func setColor(_ color: UIColor) {
         self.initialsLabel.backgroundColor = color

+ 65 - 0
deltachat-ios/ContactListController.swift

@@ -0,0 +1,65 @@
+//
+//  ContactListController.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+class ContactListController: UITableViewController {
+
+    let contactCellReuseIdentifier = "xyz"
+    var contactIds: [Int] = Utils.getContactIds()
+    var contactIdsForGroup: Set<Int> = []
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        self.title = "Contacts"
+        navigationController?.navigationBar.prefersLargeTitles = true
+        
+        tableView.register(ContactCell.self, forCellReuseIdentifier: contactCellReuseIdentifier)
+    }
+    
+    
+    override func didReceiveMemoryWarning() {
+        super.didReceiveMemoryWarning()
+    }
+    
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
+    
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return contactIds.count
+    }
+    
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        guard let cell:ContactCell = tableView.dequeueReusableCell(withIdentifier: contactCellReuseIdentifier, for: indexPath) as? ContactCell else {
+            fatalError("shouldn't happen")
+        }
+        
+        let row = indexPath.row
+        let contactRow = row
+        
+        let contact = MRContact(id: contactIds[contactRow])
+        cell.nameLabel.text = contact.name
+        cell.emailLabel.text = contact.email
+        
+        if let img = contact.profileImage {
+            cell.setImage(img)
+        } else {
+            cell.setBackupImage(name: contact.name, color: contact.color)
+        }
+        
+        return cell
+    }
+    
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        print("view contact", indexPath.row)
+        let contactId = contactIds[indexPath.row]
+        let contactProfileController = ContactProfileViewController(contactId: contactId)
+        navigationController?.pushViewController(contactProfileController, animated: true)
+    }
+}

+ 13 - 13
deltachat-ios/ContactProfileViewController.swift

@@ -9,18 +9,14 @@
 import UIKit
 
 class ContactProfileViewController: UITableViewController {
-    let contactId:Int
-    let contactColor:UIColor
-    var name:String {
-        return MRContact(id: contactId).name
-    }
-    var email:String {
-        return MRContact(id: contactId).email
+    let contactId: Int
+
+    var contact: MRContact {
+        return MRContact(id: contactId)
     }
     
-    init(contactId: Int, contactColor: UIColor) {
+    init(contactId: Int) {
         self.contactId = contactId
-        self.contactColor = contactColor
         super.init(style: .plain)
     }
     
@@ -60,12 +56,16 @@ class ContactProfileViewController: UITableViewController {
         let row = indexPath.row
         if row == 0 {
             let contactCell = ContactCell()
-            contactCell.nameLabel.text = name
-            contactCell.emailLabel.text = email
-            contactCell.initialsLabel.text = Utils.getInitials(inputName: name)
-            contactCell.setColor(self.contactColor)
+            contactCell.nameLabel.text = contact.name
+            contactCell.emailLabel.text = contact.email
             contactCell.darkMode = true
             contactCell.selectionStyle = .none
+            if let img = contact.profileImage {
+                contactCell.setImage(img)
+            } else {
+                contactCell.setBackupImage(name: contact.name, color: contact.color)
+            }
+            
             return contactCell
         }
         let cell = UITableViewCell(style: .default, reuseIdentifier: nil)

+ 21 - 43
deltachat-ios/ContactViewController.swift

@@ -8,28 +8,18 @@
 
 import UIKit
 
-class ContactViewController: UIViewController {
-    var coordinator: Coordinator
+class ContactViewController: UITableViewController {
     var contactIds: [Int] = []
     
-    let contactTable = UITableView()
-    let contactTableDataSource = ContactTableDataSource()
-    let contactTableDelegate = ContactTableDelegate()
-    
-    init(coordinator: Coordinator) {
-        self.coordinator = coordinator
-        super.init(nibName: nil, bundle: nil)
-    }
-    
     required init?(coder aDecoder: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
-    
-    override func viewWillAppear(_ animated: Bool) {
-        let c_contacts = dc_get_contacts(mailboxPointer, 0, nil)
-        self.contactIds = Utils.copyAndFreeArray(inputArray: c_contacts)
-        contactTableDataSource.contacts = self.contactIds
-        contactTable.reloadData()
+
+    override func viewDidAppear(_ animated: Bool) {
+        super.viewDidAppear(animated)
+
+        contactIds = Utils.getContactIds()
+        tableView.reloadData()
     }
     
     override func viewDidLoad() {
@@ -38,16 +28,6 @@ class ContactViewController: UIViewController {
         title = "Contacts"
         navigationController?.navigationBar.prefersLargeTitles = true
 
-        contactTable.dataSource = self.contactTableDataSource
-        contactTable.delegate = self.contactTableDelegate
-        
-        view.addSubview(contactTable)
-        contactTable.translatesAutoresizingMaskIntoConstraints = false
-        contactTable.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
-        contactTable.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
-        contactTable.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
-        contactTable.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
-        
         let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(ContactViewController.addContact))
         navigationItem.rightBarButtonItem = addButton
     }
@@ -62,33 +42,31 @@ class ContactViewController: UIViewController {
         super.didReceiveMemoryWarning()
         // Dispose of any resources that can be recreated.
     }
-}
 
-class ContactTableDataSource: NSObject, UITableViewDataSource {
-    var contacts: [Int] = []
-
-    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-        return contacts.count
+    
+    // MARK: - Table view data source
+    
+    override func numberOfSections(in tableView: UITableView) -> Int {
+        return 1
+    }
+    
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return contactIds.count
     }
     
-    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
         let cell:UITableViewCell
         if let c = tableView.dequeueReusableCell(withIdentifier: String(describing: UITableViewCell.self)) {
             cell = c
         } else {
             cell = UITableViewCell(style: .subtitle, reuseIdentifier: String(describing: UITableViewCell.self))
         }
-        let row = indexPath.row
-        let id = contacts[row]
-        let contact = MRContact(id: id)
-
+        
+        let contact = MRContact(id: contactIds[indexPath.row])
+        
         cell.textLabel?.text = contact.name
         cell.detailTextLabel?.text = contact.email
-
+        
         return cell
     }
 }
-
-class ContactTableDelegate: NSObject, UITableViewDelegate {
-    
-}

+ 1 - 1
deltachat-ios/CredentialsController.swift

@@ -234,7 +234,7 @@ class CredentialsController: UITableViewController {
     override func viewDidLoad() {
         super.viewDidLoad()
         
-        title = "Account"
+        title = "Settings"
         navigationController?.navigationBar.prefersLargeTitles = true
     }
     

+ 47 - 0
deltachat-ios/CustomCell.swift

@@ -0,0 +1,47 @@
+//
+//  CustomCell.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+import UIKit
+import MessageKit
+
+open class CustomCell: UICollectionViewCell {
+    
+    let label = UILabel()
+    
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
+        setupSubviews()
+    }
+    
+    public required init?(coder aDecoder: NSCoder) {
+        super.init(coder: aDecoder)
+        setupSubviews()
+    }
+    
+    open func setupSubviews() {
+        contentView.addSubview(label)
+        label.textAlignment = .center
+        label.font = UIFont.italicSystemFont(ofSize: 13)
+    }
+    
+    open override func layoutSubviews() {
+        super.layoutSubviews()
+        label.frame = contentView.bounds
+    }
+    
+    open func configure(with message: MessageType, at indexPath: IndexPath, and messagesCollectionView: MessagesCollectionView) {
+        // Do stuff
+        switch message.kind {
+        case .custom(let data):
+            guard let systemMessage = data as? String else { return }
+            label.text = systemMessage
+        default:
+            break
+        }
+    }
+    
+}

+ 51 - 0
deltachat-ios/NavigationController.swift

@@ -0,0 +1,51 @@
+//
+//  NavigationController.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+final class NavigationController: UINavigationController {
+    
+    override var preferredStatusBarStyle: UIStatusBarStyle {
+        return viewControllers.last?.preferredStatusBarStyle ?? .lightContent
+    }
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        navigationBar.isTranslucent = false
+        navigationBar.tintColor = .white
+        navigationBar.barTintColor = Constants.primaryColor
+        navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
+        if #available(iOS 11.0, *) {
+            navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
+        }
+        navigationBar.shadowImage = UIImage()
+        navigationBar.setBackgroundImage(UIImage(), for: .default)
+        view.backgroundColor = Constants.primaryColor
+    }
+    
+    func setAppearanceStyle(to style: UIStatusBarStyle) {
+        if style == .default {
+            navigationBar.shadowImage = UIImage()
+            navigationBar.barTintColor = Constants.primaryColor
+            navigationBar.tintColor = .white
+            navigationBar.titleTextAttributes = [.foregroundColor: UIColor.white]
+            if #available(iOS 11.0, *) {
+                navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
+            }
+        } else if style == .lightContent {
+            navigationBar.shadowImage = nil
+            navigationBar.barTintColor = .white
+            navigationBar.tintColor = UIColor(red: 0, green: 0.5, blue: 1, alpha: 1)
+            navigationBar.titleTextAttributes = [.foregroundColor: UIColor.black]
+            if #available(iOS 11.0, *) {
+                navigationBar.largeTitleTextAttributes = [.foregroundColor: UIColor.black]
+            }
+        }
+    }
+    
+}

+ 2 - 4
deltachat-ios/NewChatViewController.swift

@@ -94,8 +94,7 @@ class NewChatViewController: UITableViewController {
         cell.nameLabel.text = contact.name
         cell.emailLabel.text = contact.email
         cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-        let contactColor = Utils.contactColor(row: contactRow)
-        cell.setColor(contactColor)
+        cell.setColor(contact.color)
         
         cell.accessoryType = .detailDisclosureButton
         return cell
@@ -127,8 +126,7 @@ class NewChatViewController: UITableViewController {
             let contactId = contactIds[contactIndex]
             // let newContactController = NewContactController(contactIdForUpdate: contactId)
             // navigationController?.pushViewController(newContactController, animated: true)
-            let contactColor = Utils.contactColor(row: contactIndex)
-            let contactProfileController = ContactProfileViewController(contactId: contactId, contactColor: contactColor)
+            let contactProfileController = ContactProfileViewController(contactId: contactId)
             navigationController?.pushViewController(contactProfileController, animated: true)
         }
     }

+ 1 - 2
deltachat-ios/NewGroupViewController.swift

@@ -59,8 +59,7 @@ class NewGroupViewController: UITableViewController {
         cell.nameLabel.text = contact.name
         cell.emailLabel.text = contact.email
         cell.initialsLabel.text = Utils.getInitials(inputName: contact.name)
-        let contactColor = Utils.contactColor(row: contactRow)
-        cell.setColor(contactColor)
+        cell.setColor(contact.color)
         
         return cell
     }

+ 157 - 0
deltachat-ios/SettingsController.swift

@@ -0,0 +1,157 @@
+//
+//  SettingsController.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+import MessageKit
+import MessageInputBar
+
+final internal class SettingsViewController: UITableViewController {
+    
+    // MARK: - Properties
+    
+    override var preferredStatusBarStyle: UIStatusBarStyle {
+        return .lightContent
+    }
+    
+    let cells = ["Mock messages count", "Text Messages", "AttributedText Messages", "Photo Messages", "Video Messages", "Emoji Messages", "Location Messages", "Url Messages", "Phone Messages"]
+    
+    // MARK: - Picker
+    
+    var messagesPicker = UIPickerView()
+    
+    @objc func onDoneWithPickerView() {
+        let selectedMessagesCount = messagesPicker.selectedRow(inComponent: 0)
+        view.endEditing(false)
+        tableView.reloadData()
+    }
+    
+    @objc func dismissPickerView() {
+        view.endEditing(false)
+    }
+    
+    private func configurePickerView() {
+        messagesPicker.dataSource = self
+        messagesPicker.delegate = self
+        messagesPicker.backgroundColor = .white
+        
+        messagesPicker.selectRow(0, inComponent: 0, animated: false)
+    }
+    
+    // MARK: - Toolbar
+    
+    var messagesToolbar = UIToolbar()
+    
+    private func configureToolbar() {
+        let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self, action: #selector(onDoneWithPickerView))
+        let spaceButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
+        let cancelButton = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(dismissPickerView))
+        messagesToolbar.items = [cancelButton, spaceButton, doneButton]
+        messagesToolbar.sizeToFit()
+    }
+    
+    // MARK: - View lifecycle
+    
+    override func viewDidLoad() {
+        super.viewDidLoad()
+        title = "Settings"
+            
+        tableView.register(TextFieldTableViewCell.self, forCellReuseIdentifier: TextFieldTableViewCell.identifier)
+        tableView.tableFooterView = UIView()
+        configurePickerView()
+        configureToolbar()
+    }
+    
+    override func viewWillAppear(_ animated: Bool) {
+        super.viewWillAppear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = true
+        }
+    }
+    
+    override func viewWillDisappear(_ animated: Bool) {
+        super.viewWillDisappear(animated)
+        if #available(iOS 11.0, *) {
+            navigationController?.navigationBar.prefersLargeTitles = false
+        }
+    }
+    
+    // MARK: - TableViewDelegate & TableViewDataSource
+    
+    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+        return cells.count
+    }
+    
+    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+        let cellValue = cells[indexPath.row]
+        let cell = tableView.dequeueReusableCell(withIdentifier: "cell") ?? UITableViewCell()
+        cell.textLabel?.text = cells[indexPath.row]
+        
+        switch cellValue {
+        case "Mock messages count":
+            return configureTextFieldTableViewCell(at: indexPath)
+        default:
+            let switchView = UISwitch(frame: .zero)
+            switchView.isOn = UserDefaults.standard.bool(forKey: cellValue)
+            switchView.tag = indexPath.row
+            switchView.addTarget(self, action: #selector(self.switchChanged(_:)), for: .valueChanged)
+            cell.accessoryView = switchView
+        }
+        return cell
+    }
+    
+    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+        tableView.deselectRow(at: indexPath, animated: true)
+        
+        let cell = tableView.cellForRow(at: indexPath)
+        
+        cell?.contentView.subviews.forEach {
+            if $0 is UITextField {
+                $0.becomeFirstResponder()
+            }
+        }
+    }
+    
+    // MARK: - Helper
+    
+    private func configureTextFieldTableViewCell(at indexPath: IndexPath) -> TextFieldTableViewCell {
+        if let cell = tableView.dequeueReusableCell(withIdentifier: TextFieldTableViewCell.identifier, for: indexPath) as? TextFieldTableViewCell {
+            cell.mainLabel.text = "Mock messages count:"
+            
+            let messagesCount = 0
+            cell.textField.text = "\(messagesCount)"
+            
+            cell.textField.inputView = messagesPicker
+            cell.textField.inputAccessoryView = messagesToolbar
+            
+            return cell
+        }
+        return TextFieldTableViewCell()
+    }
+    
+    @objc func switchChanged(_ sender: UISwitch!) {
+        let cell = cells[sender.tag]
+        
+        UserDefaults.standard.set(sender.isOn, forKey: cell)
+    }
+}
+
+// MARK: - UIPickerViewDelegate, UIPickerViewDataSource
+extension SettingsViewController: UIPickerViewDelegate, UIPickerViewDataSource {
+    
+    func numberOfComponents(in pickerView: UIPickerView) -> Int {
+        return 1
+    }
+    
+    func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+        return 100
+    }
+    
+    func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
+        return "\(row)"
+    }
+}

+ 46 - 0
deltachat-ios/TableViewCell.swift

@@ -0,0 +1,46 @@
+//
+//  TableViewCell.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+internal class TextFieldTableViewCell: UITableViewCell {
+    
+    static let identifier = "TextFieldTableViewCellIdentifier"
+    
+    var mainLabel = UILabel()
+    var textField = UITextField()
+    
+    // MARK: - View lifecycle
+    
+    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
+        super.init(style: style, reuseIdentifier: reuseIdentifier)
+        
+        mainLabel.translatesAutoresizingMaskIntoConstraints = false
+        textField.translatesAutoresizingMaskIntoConstraints = false
+        
+        contentView.addSubview(mainLabel)
+        contentView.addSubview(textField)
+        
+        NSLayoutConstraint.activate([
+            mainLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 20),
+            mainLabel.widthAnchor.constraint(equalToConstant: 200),
+            mainLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+            
+            textField.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
+            
+            textField.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -20),
+            textField.widthAnchor.constraint(equalToConstant: 50)
+            ])
+        
+        textField.textAlignment = .right
+    }
+    
+    required init?(coder aDecoder: NSCoder) {
+        fatalError("init(coder:) has not been implemented")
+    }
+}

+ 25 - 0
deltachat-ios/UIImage+Extension.swift

@@ -0,0 +1,25 @@
+//
+//  UIImage+Extension.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+extension UIImage {
+    
+    func imageResize (sizeChange: CGSize)-> UIImage{
+        
+        let hasAlpha = true
+        let scale: CGFloat = 0.0 // Use scale factor of main screen
+        
+        UIGraphicsBeginImageContextWithOptions(sizeChange, !hasAlpha, scale)
+        self.draw(in: CGRect(origin: CGPoint.zero, size: sizeChange))
+        
+        let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
+        return scaledImage!
+    }
+    
+}

+ 52 - 0
deltachat-ios/UIViewController+Extension.swift

@@ -0,0 +1,52 @@
+//
+//  TableViewController.swift
+//  deltachat-ios
+//
+//  Created by Friedel Ziegelmayer on 26.12.18.
+//  Copyright © 2018 Jonas Reinsch. All rights reserved.
+//
+
+import UIKit
+
+
+extension UIViewController {
+    
+    func updateTitleView(title: String, subtitle: String?, baseColor: UIColor = .white) {
+        
+        let titleLabel = UILabel(frame: CGRect(x: 0, y: -2, width: 0, height: 0))
+        titleLabel.backgroundColor = UIColor.clear
+        titleLabel.textColor = baseColor
+        titleLabel.font = UIFont.systemFont(ofSize: 15)
+        titleLabel.text = title
+        titleLabel.textAlignment = .center
+        titleLabel.adjustsFontSizeToFitWidth = true
+        titleLabel.sizeToFit()
+        
+        let subtitleLabel = UILabel(frame: CGRect(x: 0, y: 18, width: 0, height: 0))
+        subtitleLabel.textColor = baseColor.withAlphaComponent(0.95)
+        subtitleLabel.font = UIFont.systemFont(ofSize: 12)
+        subtitleLabel.text = subtitle
+        subtitleLabel.textAlignment = .center
+        subtitleLabel.adjustsFontSizeToFitWidth = true
+        subtitleLabel.sizeToFit()
+        
+        let titleView = UIView(frame: CGRect(x: 0, y: 0, width: max(titleLabel.frame.size.width, subtitleLabel.frame.size.width), height: 30))
+        titleView.addSubview(titleLabel)
+        if subtitle != nil {
+            titleView.addSubview(subtitleLabel)
+        } else {
+            titleLabel.frame = titleView.frame
+        }
+        let widthDiff = subtitleLabel.frame.size.width - titleLabel.frame.size.width
+        if widthDiff < 0 {
+            let newX = widthDiff / 2
+            subtitleLabel.frame.origin.x = abs(newX)
+        } else {
+            let newX = widthDiff / 2
+            titleLabel.frame.origin.x = newX
+        }
+        
+        navigationItem.titleView = titleView
+    }
+    
+}

+ 21 - 14
deltachat-ios/Utils.swift

@@ -10,20 +10,6 @@ import Foundation
 import UIKit
 
 struct Utils {
-    static func colorIndex(row: Int, colors:[UIColor]) -> Int {
-        return row % colors.count
-    }
-    
-    static func color(row: Int, colors:[UIColor]) -> UIColor {
-        let index = colorIndex(row: row, colors: colors)
-        return colors[index]
-    }
-    
-    static func contactColor(row: Int) -> UIColor {
-        let contactColor = Utils.color(row: row, colors: Constants.chatColors)
-        return contactColor
-    }
-    
     static func getContactIds() -> [Int] {
         let c_contacts = dc_get_contacts(mailboxPointer, 0, nil)
         return Utils.copyAndFreeArray(inputArray: c_contacts)
@@ -54,6 +40,27 @@ struct Utils {
         let emailTest = NSPredicate(format:"SELF MATCHES[c] %@", emailRegEx)
         return emailTest.evaluate(with: email)
     }
+    
+    static func formatAddressForQuery(address: [String : String]) -> String {
+        // Open address in Apple Maps app.
+        var addressParts = [String]()
+        let addAddressPart: ((String?) -> Void) = { (part) in
+            guard let part = part else {
+                return
+            }
+            guard part.count > 0 else {
+                return
+            }
+            addressParts.append(part)
+        }
+        addAddressPart(address["Street"])
+        addAddressPart(address["Neighborhood"])
+        addAddressPart(address["City"])
+        addAddressPart(address["Region"])
+        addAddressPart(address["Postcode"])
+        addAddressPart(address["Country"])
+        return addressParts.joined(separator: ", ")
+    }
 }
 
 

+ 62 - 0
deltachat-ios/Wrapper.swift

@@ -30,6 +30,39 @@ class MRContact {
         return String(cString: contactPointer.pointee.addr)
     }
     
+    var isVerified: Bool {
+        return dc_contact_is_verified(contactPointer) == 1
+    }
+    
+    var isBlocked: Bool {
+        return dc_contact_is_blocked(contactPointer) == 1
+    }
+    
+    lazy var profileImage: UIImage? = { [unowned self] in
+        let file = dc_contact_get_profile_image(contactPointer)
+        if let cFile = file {
+            let filename = String(cString: cFile)
+            let path: URL = URL.init(fileURLWithPath: filename, isDirectory: false)
+            if path.isFileURL {
+                do {
+                    let data = try Data(contentsOf: path)
+                    let image = UIImage(data: data)
+                    return image
+                } catch {
+                    print("failed to load image", error, filename)
+                    return nil
+                }
+            }
+            return nil
+        }
+        
+        return nil
+    }()
+    
+    var color: UIColor {
+        return UIColor(netHex: Int(dc_contact_get_color(contactPointer)))
+    }
+    
     var id: Int {
         return Int(contactPointer.pointee.id)
     }
@@ -55,6 +88,10 @@ class MRMessage {
         return Int(messagePointer.pointee.from_id)
     }
     
+    lazy var fromContact: MRContact = {
+        return MRContact(id: fromContactId)
+    }()
+    
     var toContactId: Int {
         return Int(messagePointer.pointee.to_id)
     }
@@ -103,6 +140,21 @@ class MRMessage {
     var state: Int {
         return Int(messagePointer.pointee.state)
     }
+
+    func stateOutDescription() -> String {
+        switch Int32(state) {
+        case DC_STATE_OUT_DRAFT:
+                return "Draft"
+        case DC_STATE_OUT_PENDING:
+                return "Pending"
+        case DC_STATE_OUT_DELIVERED:
+            return "Sent"
+        case DC_STATE_OUT_MDN_RCVD:
+            return "Read"
+        default:
+            return "Unknown"
+        }
+    }
     
     var timestamp: Int64 {
         return Int64(messagePointer.pointee.timestamp)
@@ -112,6 +164,12 @@ class MRMessage {
         messagePointer = dc_get_msg(mailboxPointer, UInt32(id))
     }
     
+    func summary(chars: Int) -> String? {
+        guard let result = dc_msg_get_summarytext(messagePointer, Int32(chars)) else { return nil }
+
+        return String(cString: result)
+    }
+    
     deinit {
         dc_msg_unref(messagePointer)
     }
@@ -136,6 +194,10 @@ class MRChat {
         return Int(chatPointer.pointee.type)
     }
     
+    var color: UIColor {
+        return UIColor(netHex: Int(dc_chat_get_color(chatPointer)))
+    }
+
     init(id: Int) {
         chatPointer = dc_get_chat(mailboxPointer, UInt32(id))
     }