Przeglądaj źródła

Merge pull request #508 from deltachat/sharedChats

Shared chats in profile
bjoern 5 lat temu
rodzic
commit
8a9f3ce42c

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

@@ -131,6 +131,8 @@
 		AE52EA19229EB53C00C586C9 /* ContactDetailHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */; };
 		AE52EA20229EB9F000C586C9 /* EditGroupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */; };
 		AE728F15229D5C390047565B /* PhotoPickerAlertAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */; };
+		AE77838D23E32ED20093EABD /* ContactDetailViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE77838C23E32ED20093EABD /* ContactDetailViewModel.swift */; };
+		AE77838F23E4276D0093EABD /* ContactCellViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE77838E23E4276D0093EABD /* ContactCellViewModel.swift */; };
 		AE8519EA2272FDCA00ED86F0 /* DeviceContactsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */; };
 		AE851A04227AECDE00ED86F0 /* deltachat_iosTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851A03227AECDE00ED86F0 /* deltachat_iosTests.swift */; };
 		AE851AC5227C755A00ED86F0 /* Protocols.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE851AC4227C755A00ED86F0 /* Protocols.swift */; };
@@ -356,6 +358,8 @@
 		AE52EA18229EB53C00C586C9 /* ContactDetailHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailHeader.swift; sourceTree = "<group>"; };
 		AE52EA1F229EB9F000C586C9 /* EditGroupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditGroupViewController.swift; sourceTree = "<group>"; };
 		AE728F14229D5C390047565B /* PhotoPickerAlertAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPickerAlertAction.swift; sourceTree = "<group>"; };
+		AE77838C23E32ED20093EABD /* ContactDetailViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactDetailViewModel.swift; sourceTree = "<group>"; };
+		AE77838E23E4276D0093EABD /* ContactCellViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactCellViewModel.swift; sourceTree = "<group>"; };
 		AE8519E92272FDCA00ED86F0 /* DeviceContactsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContactsHandler.swift; sourceTree = "<group>"; };
 		AE851A01227AECDE00ED86F0 /* deltachat-iosTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "deltachat-iosTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		AE851A03227AECDE00ED86F0 /* deltachat_iosTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = deltachat_iosTests.swift; sourceTree = "<group>"; };
@@ -614,6 +618,7 @@
 			children = (
 				AE1988AA23EB3C7600B4CD5F /* Assets */,
 				AE19887623EB2BDA00B4CD5F /* Assets */,
+				AE77838B23E32EAA0093EABD /* ViewModel */,
 				305961812346125000C80F33 /* Extensions */,
 				3059617E234610A800C80F33 /* MessageKit */,
 				7A9FB1431FB061E2001FEA36 /* AppDelegate.swift */,
@@ -680,6 +685,15 @@
 			path = Assets;
 			sourceTree = "<group>";
 		};
+		AE77838B23E32EAA0093EABD /* ViewModel */ = {
+			isa = PBXGroup;
+			children = (
+				AE77838C23E32ED20093EABD /* ContactDetailViewModel.swift */,
+				AE77838E23E4276D0093EABD /* ContactCellViewModel.swift */,
+			);
+			path = ViewModel;
+			sourceTree = "<group>";
+		};
 		AE851A02227AECDE00ED86F0 /* deltachat-iosTests */ = {
 			isa = PBXGroup;
 			children = (
@@ -1124,11 +1138,13 @@
 				AE851AC7227C776400ED86F0 /* Location.swift in Sources */,
 				7AE0A5491FC42F65005ECB4B /* NewChatViewController.swift in Sources */,
 				305961E52346125100C80F33 /* LabelAlignment.swift in Sources */,
+				AE77838F23E4276D0093EABD /* ContactCellViewModel.swift in Sources */,
 				305961E82346125100C80F33 /* Sender.swift in Sources */,
 				305961EE2346125100C80F33 /* AvatarPosition.swift in Sources */,
 				3015634423A003BA00E9DEF4 /* AudioRecorderController.swift in Sources */,
 				AE25F09022807AD800CDEA66 /* AvatarSelectionCell.swift in Sources */,
 				302B84C6239676F0001C261F /* AvatarHelper.swift in Sources */,
+				AE77838D23E32ED20093EABD /* ContactDetailViewModel.swift in Sources */,
 				305961E62346125100C80F33 /* LocationMessageSnapshotOptions.swift in Sources */,
 				AEE6EC3F2282C59C00EDC689 /* GroupMembersViewController.swift in Sources */,
 				B26B3BC7236DC3DC008ED35A /* SwitchCell.swift in Sources */,

+ 71 - 69
deltachat-ios/Controller/ContactDetailViewController.swift

@@ -3,25 +3,19 @@ import UIKit
 // this is also used as ChatDetail for SingleChats
 class ContactDetailViewController: UITableViewController {
     weak var coordinator: ContactDetailCoordinatorProtocol?
+    private let viewModel: ContactDetailViewModelProtocol
 
-    let sectionOptions = 0
-    let sectionOptionsRowStartChat = 1
-    let sectionOptionsRowCount = 1
-
-    let sectionBlockContact = 1
-    let sectionBlockContactRowCount = 1
-
-    let sectionCount = 2
-
-    var showStartChat = true
-
-    var optionCells: [UITableViewCell] = []
-
-    private let contactId: Int
-
-    private var contact: DcContact {
-        return DcContact(id: contactId)
-    }
+    private lazy var headerCell: ContactDetailHeader = {
+        let cell = ContactDetailHeader()
+        cell.updateDetails(title: viewModel.contact.displayName, subtitle: viewModel.contact.email)
+        if let img = viewModel.contact.profileImage {
+            cell.setImage(img)
+        } else {
+            cell.setBackupImage(name: viewModel.contact.displayName, color: viewModel.contact.color)
+        }
+        cell.setVerified(isVerified: viewModel.contact.isVerified)
+        return cell
+    }()
 
     private lazy var startChatCell: ActionCell = {
         let cell = ActionCell()
@@ -33,14 +27,14 @@ class ContactDetailViewController: UITableViewController {
 
     private lazy var blockContactCell: ActionCell = {
         let cell = ActionCell()
-        cell.actionTitle = contact.isBlocked ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
-        cell.actionColor = contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
+        cell.actionTitle = viewModel.contact.isBlocked ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
+        cell.actionColor = viewModel.contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
         cell.selectionStyle = .none
         return cell
     }()
 
-    init(contactId: Int) {
-        self.contactId = contactId
+    init(viewModel: ContactDetailViewModelProtocol) {
+        self.viewModel = viewModel
         super.init(style: .grouped)
     }
 
@@ -48,15 +42,14 @@ class ContactDetailViewController: UITableViewController {
         fatalError("init(coder:) has not been implemented")
     }
 
+    // MARK: - lifecycle
     override func viewDidLoad() {
         super.viewDidLoad()
+        configureTableView()
         navigationItem.rightBarButtonItem = UIBarButtonItem(
             title: String.localized("global_menu_edit_desktop"),
             style: .plain, target: self, action: #selector(editButtonPressed))
         self.title = String.localized("tab_contact")
-        if showStartChat {
-            optionCells.append(startChatCell)
-        }
     }
 
     override func viewWillAppear(_ animated: Bool) {
@@ -64,37 +57,69 @@ class ContactDetailViewController: UITableViewController {
         tableView.reloadData()
     }
 
+    // MARK: - setup and configuration
+    private func configureTableView() {
+        tableView.register(ActionCell.self, forCellReuseIdentifier: ActionCell.reuseIdentifier)
+        tableView.register(ContactCell.self, forCellReuseIdentifier: ContactCell.reuseIdentifier)
+        headerCell.frame = CGRect(0, 0, tableView.frame.width, ContactCell.cellHeight)
+        tableView.tableHeaderView = headerCell
+
+    }
+
+    // MARK: - UITableViewDatasource, UITableViewDelegate
     override func numberOfSections(in tableView: UITableView) -> Int {
-        return sectionCount
+        return viewModel.numberOfSections
     }
 
     override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
-        if section == sectionOptions {
-            return optionCells.count
-        } else if section == sectionBlockContact {
-            return sectionBlockContactRowCount
-        }
-        return 0
+        return viewModel.numberOfRowsInSection(section)
     }
 
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let section = indexPath.section
-        if section == sectionOptions {
-            return startChatCell
-        } else {
+        let cellType = viewModel.typeFor(section: indexPath.section)
+        switch cellType {
+        case .blockContact:
             return blockContactCell
+        case .startChat:
+            return startChatCell
+        case .sharedChats:
+            if let cell = tableView.dequeueReusableCell(withIdentifier: ContactCell.reuseIdentifier, for: indexPath) as? ContactCell {
+                viewModel.update(sharedChatCell: cell, row: indexPath.row)
+                return cell
+            }
         }
+        return UITableViewCell() // should never get here
     }
 
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let section = indexPath.section
-        if section == sectionOptions {
-            askToChatWith(contactId: contactId)
-        } else {
+        let type = viewModel.typeFor(section: indexPath.section)
+        switch type {
+        case .blockContact:
             toggleBlockContact()
+        case .startChat:
+            let contactId = viewModel.contactId
+            askToChatWith(contactId: contactId)
+        case .sharedChats:
+            let chatId = viewModel.getSharedChatIdAt(indexPath: indexPath)
+            coordinator?.showChat(chatId: chatId)
+        }
+    }
+
+    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
+        let type = viewModel.typeFor(section: indexPath.section)
+        switch type {
+        case .blockContact, .startChat:
+            return 44
+        case .sharedChats:
+            return ContactCell.cellHeight
         }
     }
 
+    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
+        return viewModel.titleFor(section: section)
+    }
+
+    // MARK: -actions
     private func askToChatWith(contactId: Int) {
         let dcContact = DcContact(id: contactId)
         let alert = UIAlertController(title: String.localizedStringWithFormat(String.localized("ask_start_chat_with"), dcContact.nameNAddr),
@@ -111,34 +136,11 @@ class ContactDetailViewController: UITableViewController {
         present(alert, animated: true, completion: nil)
     }
 
-    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
-        if section == 0 {
-            return ContactDetailHeader.cellHeight
-        }
-        return 0
-    }
-    
-    override func tableView(_: UITableView, viewForHeaderInSection section: Int) -> UIView? {
-        if section == 0 {
-            let header = ContactDetailHeader()
-            let displayName = contact.displayName
-            header.updateDetails(title: displayName, subtitle: contact.email)
-            if let img = contact.profileImage {
-                header.setImage(img)
-            } else {
-                header.setBackupImage(name: displayName, color: contact.color)
-            }
-            header.setVerified(isVerified: contact.isVerified)
-            return header
-        }
-        return nil
-    }
-
     private func toggleBlockContact() {
-        if contact.isBlocked {
+        if viewModel.contact.isBlocked {
             let alert = UIAlertController(title: String.localized("ask_unblock_contact"), message: nil, preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("menu_unblock_contact"), style: .default, handler: { _ in
-                self.contact.unblock()
+                self.viewModel.contact.unblock()
                 self.updateBlockContactCell()
             }))
             alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
@@ -146,7 +148,7 @@ class ContactDetailViewController: UITableViewController {
         } else {
             let alert = UIAlertController(title: String.localized("ask_block_contact"), message: nil, preferredStyle: .safeActionSheet)
             alert.addAction(UIAlertAction(title: String.localized("menu_block_contact"), style: .destructive, handler: { _ in
-                self.contact.block()
+                self.viewModel.contact.block()
                 self.updateBlockContactCell()
             }))
             alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
@@ -155,8 +157,8 @@ class ContactDetailViewController: UITableViewController {
     }
 
     private func updateBlockContactCell() {
-        blockContactCell.actionTitle = contact.isBlocked ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
-        blockContactCell.actionColor = contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
+        blockContactCell.actionTitle = viewModel.contact.isBlocked ? String.localized("menu_unblock_contact") : String.localized("menu_block_contact")
+        blockContactCell.actionColor = viewModel.contact.isBlocked ? SystemColor.blue.uiColor : UIColor.red
     }
 
     private func showNotificationSetup() {
@@ -169,6 +171,6 @@ class ContactDetailViewController: UITableViewController {
     }
 
     @objc private func editButtonPressed() {
-        coordinator?.showEditContact(contactId: contactId)
+        coordinator?.showEditContact(contactId: viewModel.contactId)
     }
 }

+ 16 - 7
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -326,13 +326,16 @@ class NewChatCoordinator: Coordinator {
         navigationController.viewControllers.remove(at: 1)
     }
 
+
     func showContactDetail(contactId: Int) {
-        let contactDetailController = ContactDetailViewController(contactId: contactId)
+        let viewModel = ContactDetailViewModel(contactId: contactId, startChatOption: true, context: dcContext)
+        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
         let coordinator = ContactDetailCoordinator(dcContext: dcContext, navigationController: navigationController)
         childCoordinators.append(coordinator)
         contactDetailController.coordinator = coordinator
         navigationController.pushViewController(contactDetailController, animated: true)
     }
+    
 }
 
 class GroupChatDetailCoordinator: Coordinator {
@@ -376,7 +379,8 @@ class GroupChatDetailCoordinator: Coordinator {
     }
 
     func showContactDetail(of contactId: Int) {
-        let contactDetailController = ContactDetailViewController(contactId: contactId)
+        let viewModel = ContactDetailViewModel(contactId: contactId, startChatOption: true, context: dcContext)
+        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
         let coordinator = ContactDetailCoordinator(dcContext: dcContext, navigationController: navigationController)
         childCoordinators.append(coordinator)
         contactDetailController.coordinator = coordinator
@@ -410,8 +414,8 @@ class ChatViewCoordinator: NSObject, Coordinator {
         switch chat.chatType {
         case .SINGLE:
             if let contactId = chat.contactIds.first {
-                let contactDetailController = ContactDetailViewController(contactId: contactId)
-                contactDetailController.showStartChat = false
+                let viewModel = ContactDetailViewModel(contactId: contactId, startChatOption: false, context: dcContext)
+                let contactDetailController = ContactDetailViewController(viewModel: viewModel)
                 let coordinator = ContactDetailCoordinator(dcContext: dcContext, navigationController: navigationController)
                 childCoordinators.append(coordinator)
                 contactDetailController.coordinator = coordinator
@@ -427,10 +431,15 @@ class ChatViewCoordinator: NSObject, Coordinator {
     }
 
     func showContactDetail(of contactId: Int, in chatOfType: ChatType) {
-        let contactDetailController = ContactDetailViewController(contactId: contactId)
-        if chatOfType == .SINGLE {
-            contactDetailController.showStartChat = false
+        let startChatOption: Bool
+        switch chatOfType {
+        case .GROUP, .VERYFIEDGROUP:
+            startChatOption = true
+        case .SINGLE:
+            startChatOption = false
         }
+        let viewModel = ContactDetailViewModel(contactId: contactId, startChatOption: startChatOption, context: dcContext )
+        let contactDetailController = ContactDetailViewController(viewModel: viewModel)
         let coordinator = ContactDetailCoordinator(dcContext: dcContext, navigationController: navigationController)
         childCoordinators.append(coordinator)
         contactDetailController.coordinator = coordinator

+ 3 - 0
deltachat-ios/View/ActionCell.swift

@@ -3,6 +3,9 @@ import UIKit
 // a cell with a centered label in system blue
 
 class ActionCell: UITableViewCell {
+
+    static let reuseIdentifier = "action_cell_reuse_identifier"
+
     var actionTitle: String? {
         didSet {
             actionLabel.text = actionTitle

+ 36 - 0
deltachat-ios/View/ContactCell.swift

@@ -6,6 +6,8 @@ protocol ContactCellDelegate: class {
 
 class ContactCell: UITableViewCell {
 
+    static let reuseIdentifier = "contact_cell_reuse_identifier"
+
     public static let cellHeight: CGFloat = 74.5
     weak var delegate: ContactCellDelegate?
     var rowIndex = -1
@@ -191,4 +193,38 @@ class ContactCell: UITableViewCell {
     required init?(coder _: NSCoder) {
         fatalError("init(coder:) has not been implemented")
     }
+
+    func updateCell(cellViewModel: AvatarCellViewModel) {
+        // subtitle
+        emailLabel.attributedText = cellViewModel.subtitle.boldAt(indexes: cellViewModel.subtitleHighlightIndexes, fontSize: emailLabel.font.pointSize)
+
+        switch cellViewModel.type {
+        case .CHAT(let chatData):
+            let chat = DcChat(id: chatData.chatId)
+
+            // text bold if chat contains unread messages - otherwise hightlight search results if needed
+            if chatData.unreadMessages > 0 {
+                nameLabel.attributedText = NSAttributedString(string: cellViewModel.title, attributes: [ .font: UIFont.systemFont(ofSize: 16, weight: .bold) ])
+            } else {
+                nameLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: nameLabel.font.pointSize)
+            }
+
+            if let img = chat.profileImage {
+                resetBackupImage()
+                setImage(img)
+            } else {
+              setBackupImage(name: chat.name, color: chat.color)
+            }
+            setVerified(isVerified: chat.isVerified)
+            setTimeLabel(chatData.summary.timestamp)
+            setUnreadMessageCounter(chatData.unreadMessages)
+            setDeliveryStatusIndicator(chatData.summary.state)
+
+        case .CONTACT(let contactData):
+            let contact = DcContact(id: contactData.contactId)
+            nameLabel.attributedText = cellViewModel.title.boldAt(indexes: cellViewModel.titleHighlightIndexes, fontSize: nameLabel.font.pointSize)
+            avatar.setName(cellViewModel.title)
+            avatar.setColor(contact.color)
+        }
+    }
 }

+ 97 - 0
deltachat-ios/ViewModel/ContactCellViewModel.swift

@@ -0,0 +1,97 @@
+//
+//  ContactCellViewModel.swift
+//  deltachat-ios
+//
+//  Created by Bastian van de Wetering on 31.01.20.
+//  Copyright © 2020 Jonas Reinsch. All rights reserved.
+//
+
+/*
+ this file and the containing classes are manually imported from searchBarContactList-branch which has not been merged into master at this time. Once it has been merged, this file can be deleted.
+ */
+
+import Foundation
+
+protocol AvatarCellViewModel {
+    var type: CellModel { get }
+    var title: String { get }
+    var titleHighlightIndexes: [Int] { get }
+    var subtitle: String { get }
+    var subtitleHighlightIndexes: [Int] { get }
+}
+
+enum CellModel {
+    case CONTACT(ContactCellData)
+    case CHAT(ChatCellData)
+}
+
+struct ContactCellData {
+    let contactId: Int
+}
+
+struct ChatCellData {
+    let chatId: Int
+    let summary: DcLot
+    let unreadMessages: Int
+}
+
+class ContactCellViewModel: AvatarCellViewModel {
+
+    private let contact: DcContact
+
+    var type: CellModel
+    var title: String {
+        return contact.displayName
+    }
+    var subtitle: String {
+        return contact.email
+    }
+
+    var avartarTitle: String {
+        return Utils.getInitials(inputName: title)
+    }
+
+    var titleHighlightIndexes: [Int]
+    var subtitleHighlightIndexes: [Int]
+
+    init(contactData: ContactCellData, titleHighlightIndexes: [Int] = [], subtitleHighlightIndexes: [Int] = []) {
+        type = CellModel.CONTACT(contactData)
+        self.titleHighlightIndexes = titleHighlightIndexes
+        self.subtitleHighlightIndexes = subtitleHighlightIndexes
+        self.contact = DcContact(id: contactData.contactId)
+    }
+}
+
+class ChatCellViewModel: AvatarCellViewModel{
+
+    private let chat: DcChat
+    private let summary: DcLot
+
+    var type: CellModel
+    var title: String {
+        return chat.name
+    }
+
+    var subtitle: String {
+        let result1 = summary.text1 ?? ""
+        let result2 = summary.text2 ?? ""
+        let result: String
+        if !result1.isEmpty, !result2.isEmpty {
+            result = "\(result1): \(result2)"
+        } else {
+            result = "\(result1)\(result2)"
+        }
+        return result
+    }
+
+    var titleHighlightIndexes: [Int]
+    var subtitleHighlightIndexes: [Int]
+
+    init(chatData: ChatCellData, titleHighlightIndexes: [Int] = [], subtitleHighlightIndexes: [Int] = []) {
+        self.type = CellModel.CHAT(chatData)
+        self.titleHighlightIndexes = titleHighlightIndexes
+        self.subtitleHighlightIndexes = subtitleHighlightIndexes
+        self.summary = chatData.summary
+        self.chat = DcChat(id: chatData.chatId)
+    }
+}

+ 84 - 0
deltachat-ios/ViewModel/ContactDetailViewModel.swift

@@ -0,0 +1,84 @@
+import UIKit
+
+protocol ContactDetailViewModelProtocol {
+    var contactId: Int { get }
+    var contact: DcContact { get }
+    var numberOfSections: Int { get }
+    func numberOfRowsInSection(_ : Int) -> Int
+    func typeFor(section: Int) -> ContactDetailViewModel.SectionType
+    func update(sharedChatCell: ContactCell, row index: Int)
+    func getSharedChatIdAt(indexPath: IndexPath) -> Int
+    func titleFor(section: Int) -> String?
+}
+
+class ContactDetailViewModel: ContactDetailViewModelProtocol {
+
+    let context: DcContext
+    enum SectionType {
+        case startChat
+        case sharedChats
+        case blockContact
+    }
+
+    var contactId: Int
+
+    var contact: DcContact
+    private let sharedChats: DcChatlist
+    private let startChatOption: Bool
+
+    private var sections: [SectionType] = []
+
+    init(contactId: Int, startChatOption: Bool, context: DcContext) {
+        self.context = context
+        self.contactId = contactId
+        self.contact = DcContact(id: contactId)
+        self.startChatOption = startChatOption
+        self.sharedChats = context.getChatlist(flags: 0, queryString: nil, queryId: contactId)
+
+        if startChatOption {
+            sections.append(.startChat)
+        }
+        if sharedChats.length > 0 {
+            sections.append(.sharedChats)
+        }
+        sections.append(.blockContact)
+    }
+
+    func typeFor(section: Int) -> ContactDetailViewModel.SectionType {
+        return sections[section]
+    }
+
+    var numberOfSections: Int {
+        return sections.count
+    }
+
+    func numberOfRowsInSection(_ section: Int) -> Int {
+        switch sections[section] {
+        case .sharedChats: return sharedChats.length
+        case .blockContact, .startChat: return 1
+        }
+    }
+
+    func getSharedChatIdAt(indexPath: IndexPath) -> Int {
+        let index = indexPath.row
+        // assert(sections[indexPath.section] == .sharedChats)
+        return sharedChats.getChatId(index: index)
+    }
+
+    func update(sharedChatCell cell: ContactCell, row index: Int) {
+        let chatId = sharedChats.getChatId(index: index)
+        let summary = sharedChats.getSummary(index: index)
+        let unreadMessages = context.getUnreadMessages(chatId: chatId)
+
+        let cellData = ChatCellData(chatId: chatId, summary: summary, unreadMessages: unreadMessages)
+        let cellViewModel = ChatCellViewModel(chatData: cellData)
+        cell.updateCell(cellViewModel: cellViewModel)
+    }
+
+    func titleFor(section: Int) -> String? {
+        if sections[section] == .sharedChats {
+           return String.localized("profile_shared_chats")
+        }
+        return nil
+      }
+}