Răsfoiți Sursa

Merge pull request #549 from deltachat/image_and_document_gallery

Image and document gallery
bjoern 5 ani în urmă
părinte
comite
63e3677ed1

+ 42 - 4
deltachat-ios/Controller/ContactDetailViewController.swift

@@ -49,6 +49,21 @@ class ContactDetailViewController: UITableViewController {
         return cell
     }()
 
+    private lazy var galleryCell: UITableViewCell = {
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = String.localized("gallery")
+        cell.accessoryType = .disclosureIndicator
+        return cell
+    }()
+
+    private lazy var documentsCell: UITableViewCell = {
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = String.localized("documents")
+        cell.accessoryType = .disclosureIndicator
+        return cell
+    }()
+
+
     init(viewModel: ContactDetailViewModelProtocol) {
         self.viewModel = viewModel
         super.init(style: .grouped)
@@ -95,8 +110,15 @@ class ContactDetailViewController: UITableViewController {
         let row = indexPath.row
         let cellType = viewModel.typeFor(section: indexPath.section)
         switch cellType {
+        case .attachments:
+            switch viewModel.attachmentActionFor(row: row) {
+            case .documents:
+                return documentsCell
+            case .gallery:
+                return galleryCell
+            }
         case .chatActions:
-            switch viewModel.actionFor(row: row) {
+            switch viewModel.chatActionFor(row: row) {
             case .archiveChat:
                 return archiveChatCell
             case .blockChat:
@@ -118,6 +140,8 @@ class ContactDetailViewController: UITableViewController {
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
         let type = viewModel.typeFor(section: indexPath.section)
         switch type {
+        case .attachments:
+            handleAttachmentAction(for: indexPath.row)
         case .chatActions:
             handleCellAction(for: indexPath.row)
         case .startChat:
@@ -132,8 +156,8 @@ class ContactDetailViewController: UITableViewController {
     override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
         let type = viewModel.typeFor(section: indexPath.section)
         switch type {
-        case .chatActions, .startChat:
-            return 44
+        case .chatActions, .startChat, .attachments:
+            return Constants.defaultCellHeight
         case .sharedChats:
             return ContactCell.cellHeight
         }
@@ -143,10 +167,14 @@ class ContactDetailViewController: UITableViewController {
         return viewModel.titleFor(section: section)
     }
 
+    override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        return Constants.defaultHeaderHeight
+    }
+
     // MARK: - actions
 
     private func handleCellAction(for index: Int) {
-        let action = viewModel.actionFor(row: index)
+        let action = viewModel.chatActionFor(row: index)
         switch action {
         case .archiveChat:
             toggleArchiveChat()
@@ -157,6 +185,16 @@ class ContactDetailViewController: UITableViewController {
         }
     }
 
+    private func handleAttachmentAction(for index: Int) {
+        let action = viewModel.attachmentActionFor(row: index)
+        switch action {
+        case .documents:
+            coordinator?.showDocuments()
+        case .gallery:
+            coordinator?.showGallery()
+        }
+    }
+
     private func toggleArchiveChat() {
         let archived = viewModel.toggleArchiveChat()
         if archived {

+ 99 - 44
deltachat-ios/Controller/GroupChatDetailViewController.swift

@@ -3,15 +3,24 @@ import UIKit
 class GroupChatDetailViewController: UIViewController {
 
     enum ProfileSections {
-        case memberManagement // add member, qr invideCode
-        case members // contactCells
-        case chatActions // archive, leave, delete
+        case attachments
+        case members
+        case chatActions
     }
 
+    private let attachmentsRowGallery = 0
+    private let attachmentsRowDocuments = 1
+    private let membersRowAddMembers = 0
+    private let membersRowQrInvite = 1
+    private let memberManagementRows = 2
+    private let chatActionsRowArchiveChat = 0
+    private let chatActionsRowLeaveGroup = 1
+    private let chatActionsRowDeleteChat = 2
+
     private let context: DcContext
     weak var coordinator: GroupChatDetailCoordinator?
 
-    private let sections: [ProfileSections] = [.memberManagement, .members, .chatActions]
+    private let sections: [ProfileSections] = [.attachments, .members, .chatActions]
 
     private var currentUser: DcContact? {
         let myId = groupMemberIds.filter { DcContact(id: $0).email == DcConfig.addr }.first
@@ -62,7 +71,7 @@ class GroupChatDetailViewController: UIViewController {
     private lazy var archiveChatCell: ActionCell = {
         let cell = ActionCell()
         cell.actionTitle = chat.isArchived ? String.localized("menu_unarchive_chat") :  String.localized("menu_archive_chat")
-        cell.actionColor = SystemColor.blue.uiColor
+        cell.actionColor = UIColor.systemBlue
         cell.selectionStyle = .none
         return cell
     }()
@@ -83,6 +92,20 @@ class GroupChatDetailViewController: UIViewController {
         return cell
     }()
 
+    private lazy var galleryCell: UITableViewCell = {
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = String.localized("gallery")
+        cell.accessoryType = .disclosureIndicator
+        return cell
+    }()
+
+    private lazy var documentsCell: UITableViewCell = {
+        let cell = UITableViewCell(style: .value1, reuseIdentifier: nil)
+        cell.textLabel?.text = String.localized("documents")
+        cell.accessoryType = .disclosureIndicator
+        return cell
+    }()
+
     init(chatId: Int, context: DcContext) {
         self.context = context
         chat = DcChat(id: chatId)
@@ -150,6 +173,14 @@ class GroupChatDetailViewController: UIViewController {
         }
         self.chat = DcChat(id: chat.id)
      }
+
+    private func getGroupMemberIdFor(_ row: Int) -> Int {
+        return groupMemberIds[row - memberManagementRows]
+    }
+
+    private func isMemberManagementRow(row: Int) -> Bool {
+        return row < memberManagementRows
+    }
 }
 
 // MARK: - UITableViewDelegate, UITableViewDataSource
@@ -162,10 +193,10 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
     func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
         let sectionType = sections[section]
         switch sectionType {
-        case .memberManagement:
+        case .attachments:
             return 2
         case .members:
-            return groupMemberIds.count
+            return groupMemberIds.count + memberManagementRows
         case .chatActions:
             return 3
         }
@@ -173,13 +204,17 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
 
     func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
         let sectionType = sections[indexPath.section]
+        let row = indexPath.row
         switch sectionType {
-        case .memberManagement:
+        case .attachments, .chatActions:
             return Constants.defaultCellHeight
         case .members:
-            return ContactCell.cellHeight
-        case .chatActions:
-            return Constants.defaultCellHeight
+            switch row {
+            case membersRowAddMembers, membersRowQrInvite:
+                return Constants.defaultCellHeight
+            default:
+                return ContactCell.cellHeight
+            }
         }
     }
 
@@ -187,36 +222,42 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
         let row = indexPath.row
         let sectionType = sections[indexPath.section]
         switch sectionType {
-        case .memberManagement:
-            guard let actionCell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as? ActionCell else {
-                safe_fatalError("could not dequeu action cell")
+        case .attachments:
+            if row == attachmentsRowGallery {
+                return galleryCell
+            } else if row == attachmentsRowDocuments {
+                return documentsCell
+            }
+        case .members:
+            if row == membersRowAddMembers || row == membersRowQrInvite {
+                guard let actionCell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath) as? ActionCell else {
+                safe_fatalError("could not dequeue action cell")
                 break
+                }
+                if row == membersRowAddMembers {
+                    actionCell.actionTitle = String.localized("group_add_members")
+                    actionCell.actionColor = UIColor.systemBlue
+                } else if row == membersRowQrInvite {
+                    actionCell.actionTitle = String.localized("qrshow_join_group_title")
+                    actionCell.actionColor = UIColor.systemBlue
+                }
+                return actionCell
             }
-            if row == 0 {
-                actionCell.actionTitle = String.localized("group_add_members")
-                actionCell.actionColor = UIColor.systemBlue
 
-            } else {
-                actionCell.actionTitle = String.localized("qrshow_join_group_title")
-                actionCell.actionColor = UIColor.systemBlue
-            }
-            return actionCell
-        case .members:
             guard let contactCell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath) as? ContactCell else {
-                safe_fatalError("could not dequeu contactCell cell")
+                safe_fatalError("could not dequeue contactCell cell")
                 break
-
             }
-            let cellData = ContactCellData(contactId: groupMemberIds[row])
+            let cellData = ContactCellData(contactId: getGroupMemberIdFor(row))
             let cellViewModel = ContactCellViewModel(contactData: cellData)
             contactCell.updateCell(cellViewModel: cellViewModel)
             return contactCell
         case .chatActions:
-            if row == 0 {
+            if row == chatActionsRowArchiveChat {
                 return archiveChatCell
-            } else if row == 1 {
+            } else if row == chatActionsRowLeaveGroup {
                 return leaveGroupCell
-            } else if row == 2 {
+            } else if row == chatActionsRowDeleteChat {
                 return deleteChatCell
             }
         }
@@ -229,21 +270,27 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
         let row = indexPath.row
 
         switch sectionType {
-        case .memberManagement:
-            if row == 0 {
+        case .attachments:
+            if row == attachmentsRowGallery {
+                coordinator?.showGallery()
+            } else if row == attachmentsRowDocuments {
+                coordinator?.showDocuments()
+            }
+        case .members:
+            if row == membersRowAddMembers {
                 coordinator?.showAddGroupMember(chatId: chat.id)
-            } else if row == 1 {
+            } else if row == membersRowQrInvite {
                 coordinator?.showQrCodeInvite(chatId: chat.id)
+            } else {
+                let member = getGroupMember(at: row)
+                coordinator?.showContactDetail(of: member.id)
             }
-        case .members:
-            let member = getGroupMember(at: row)
-            coordinator?.showContactDetail(of: member.id)
         case .chatActions:
-            if row == 0 {
+            if row == chatActionsRowArchiveChat {
                 toggleArchiveChat()
-            } else if row == 1 {
+            } else if row == chatActionsRowLeaveGroup {
                 showLeaveGroupConfirmationAlert()
-            } else if row == 2 {
+            } else if row == chatActionsRowDeleteChat {
                 showDeleteChatConfirmationAlert()
             }
         }
@@ -256,13 +303,19 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
         return nil
     }
 
+    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
+        return Constants.defaultHeaderHeight
+    }
+
     func tableView(_: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
         guard let currentUser = self.currentUser else {
             return false
         }
         let row = indexPath.row
         let sectionType = sections[indexPath.section]
-        if sectionType == .members && groupMemberIds[row] != currentUser.id {
+        if sectionType == .members &&
+            !isMemberManagementRow(row: row) &&
+            getGroupMemberIdFor(row) != currentUser.id {
             return true
         }
         return false
@@ -274,7 +327,9 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
         }
         let row = indexPath.row
         let sectionType = sections[indexPath.section]
-        if sectionType == .members && groupMemberIds[row] != currentUser.id {
+        if sectionType == .members &&
+            !isMemberManagementRow(row: row) &&
+            getGroupMemberIdFor(row) != currentUser.id {
             // action set for members except for current user
             let delete = UITableViewRowAction(style: .destructive, title: String.localized("remove_desktop")) { [unowned self] _, indexPath in
 
@@ -284,7 +339,7 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
                 alert.addAction(UIAlertAction(title: String.localized("remove_desktop"), style: .destructive, handler: { _ in
                     let success = dc_remove_contact_from_chat(mailboxPointer, UInt32(self.chat.id), UInt32(contact.id))
                     if success == 1 {
-                        self.removeGroupeMemberFromTableAt(indexPath)
+                        self.removeGroupMemberFromTableAt(indexPath)
                     }
                 }))
                 alert.addAction(UIAlertAction(title: String.localized("cancel"), style: .cancel, handler: nil))
@@ -298,11 +353,11 @@ extension GroupChatDetailViewController: UITableViewDelegate, UITableViewDataSou
     }
 
     private func getGroupMember(at row: Int) -> DcContact {
-        return DcContact(id: groupMemberIds[row])
+        return DcContact(id: getGroupMemberIdFor(row))
     }
 
-    private func removeGroupeMemberFromTableAt(_ indexPath: IndexPath) {
-        self.groupMemberIds.remove(at: indexPath.row)
+    private func removeGroupMemberFromTableAt(_ indexPath: IndexPath) {
+        self.groupMemberIds.remove(at: indexPath.row - memberManagementRows)
         self.tableView.deleteRows(at: [indexPath], with: .automatic)
         updateHeader()  // to display correct group size
     }

+ 56 - 1
deltachat-ios/Coordinator/AppCoordinator.swift

@@ -349,6 +349,7 @@ class GroupChatDetailCoordinator: Coordinator {
     let chatId: Int
 
     private var childCoordinators: [Coordinator] = []
+    private var previewController: PreviewController?
 
     init(dcContext: DcContext, chatId: Int, navigationController: UINavigationController) {
         self.dcContext = dcContext
@@ -394,6 +395,29 @@ class GroupChatDetailCoordinator: Coordinator {
         navigationController.pushViewController(contactDetailController, animated: true)
     }
 
+    func showDocuments() {
+        presentPreview(for: DC_MSG_FILE, messageType2: DC_MSG_AUDIO, messageType3: 0)
+    }
+
+    func showGallery() {
+        presentPreview(for: DC_MSG_IMAGE, messageType2: DC_MSG_GIF, messageType3: DC_MSG_VIDEO)
+    }
+
+    private func presentPreview(for messageType: Int32, messageType2: Int32, messageType3: Int32) {
+        let messageIds = dcContext.getChatMedia(chatId: chatId, messageType: messageType, messageType2: messageType2, messageType3: messageType3)
+        var mediaUrls: [URL] = []
+        for messageId in messageIds {
+            let message = DcMsg.init(id: messageId)
+            if let url = message.fileURL {
+                mediaUrls.insert(url, at: 0)
+            }
+        }
+        previewController = PreviewController(currentIndex: 0, urls: mediaUrls)
+        if let previewController = previewController {
+            navigationController.pushViewController(previewController.qlController, animated: true)
+        }
+    }
+
     func deleteChat() {
         /*
         app will navigate to chatlist or archive and delete the chat there
@@ -596,9 +620,9 @@ class NewGroupCoordinator: Coordinator {
 }
 
 class ContactDetailCoordinator: Coordinator, ContactDetailCoordinatorProtocol {
-
     var dcContext: DcContext
     let navigationController: UINavigationController
+    var previewController: PreviewController?
     let chatId: Int?
 
     private var childCoordinators: [Coordinator] = []
@@ -626,6 +650,31 @@ class ContactDetailCoordinator: Coordinator, ContactDetailCoordinatorProtocol {
         navigationController.pushViewController(editContactController, animated: true)
     }
 
+    func showDocuments() {
+        presentPreview(for: DC_MSG_FILE, messageType2: DC_MSG_AUDIO, messageType3: 0)
+    }
+
+    func showGallery() {
+        presentPreview(for: DC_MSG_IMAGE, messageType2: DC_MSG_GIF, messageType3: DC_MSG_VIDEO)
+    }
+
+    private func presentPreview(for messageType: Int32, messageType2: Int32, messageType3: Int32) {
+        guard let chatId = self.chatId else { return }
+        let messageIds = dcContext.getChatMedia(chatId: chatId, messageType: messageType, messageType2: messageType2, messageType3: messageType3)
+        var mediaUrls: [URL] = []
+        for messageId in messageIds {
+            let message = DcMsg.init(id: messageId)
+            if let url = message.fileURL {
+                mediaUrls.insert(url, at: 0)
+            }
+        }
+        previewController = PreviewController(currentIndex: 0, urls: mediaUrls)
+        if let previewController = previewController {
+            navigationController.pushViewController(previewController.qlController, animated: true)
+        }
+    }
+
+
     func deleteChat() {
         guard let chatId = chatId else {
             return
@@ -712,10 +761,16 @@ class EditContactCoordinator: Coordinator, EditContactCoordinatorProtocol {
     }
 }
 
+
+/*
+ boilerplate - I tend to remove that interface (cyberta)
+ */
 protocol ContactDetailCoordinatorProtocol: class {
     func showEditContact(contactId: Int)
     func showChat(chatId: Int)
     func deleteChat()
+    func showDocuments()
+    func showGallery()
 }
 
 protocol EditContactCoordinatorProtocol: class {

+ 9 - 0
deltachat-ios/DC/Wrapper.swift

@@ -45,6 +45,15 @@ class DcContext {
         return chatlist
     }
 
+    func getChatMedia(chatId: Int, messageType: Int32, messageType2: Int32, messageType3: Int32) -> [Int] {
+        guard let messagesPointer = dc_get_chat_media(contextPointer, UInt32(chatId), messageType, messageType2, messageType3) else {
+            return []
+        }
+
+        let messageIds: [Int] =  Utils.copyAndFreeArray(inputArray: messagesPointer)
+        return messageIds
+    }
+
     @discardableResult
     func createChat(contactId: Int) -> Int {
         return Int(dc_create_chat_by_contact_id(contextPointer, UInt32(contactId)))

+ 1 - 0
deltachat-ios/Helper/Constants.swift

@@ -17,4 +17,5 @@ struct Constants {
     static let notificationIdentifier = "deltachat-ios-local-notifications"
 
     static let defaultCellHeight: CGFloat = 48
+    static let defaultHeaderHeight: CGFloat = 20
 }

+ 21 - 7
deltachat-ios/ViewModel/ContactDetailViewModel.swift

@@ -7,7 +7,8 @@ protocol ContactDetailViewModelProtocol {
     var chatIsArchived: Bool { get }
     func numberOfRowsInSection(_ : Int) -> Int
     func typeFor(section: Int) -> ContactDetailViewModel.ProfileSections
-    func actionFor(row: Int) -> ContactDetailViewModel.ChatAction
+    func chatActionFor(row: Int) -> ContactDetailViewModel.ChatAction
+    func attachmentActionFor(row: Int) -> ContactDetailViewModel.AttachmentAction
     func update(sharedChatCell: ContactCell, row index: Int)
     func getSharedChatIdAt(indexPath: IndexPath) -> Int
     func titleFor(section: Int) -> String?
@@ -19,6 +20,7 @@ class ContactDetailViewModel: ContactDetailViewModelProtocol {
     let context: DcContext
     enum ProfileSections {
         case startChat
+        case attachments
         case sharedChats
         case chatActions //  archive chat, block chat, delete chats
     }
@@ -29,13 +31,19 @@ class ContactDetailViewModel: ContactDetailViewModelProtocol {
         case deleteChat
     }
 
+    enum AttachmentAction {
+        case gallery
+        case documents
+    }
+
     var contactId: Int
 
     var contact: DcContact
     private let chatId: Int?
     private let sharedChats: DcChatlist
     private var sections: [ProfileSections] = []
-    private var actions: [ChatAction] = [] // chatDetail: archive, block, delete - else: block
+    private var chatActions: [ChatAction] = [] // chatDetail: archive, block, delete - else: block
+    private var attachmentActions: [AttachmentAction] = [.gallery, .documents]
 
     /// if chatId is nil this is a contact detail with 'start chat'-option
     init(contactId: Int, chatId: Int?, context: DcContext) {
@@ -45,6 +53,7 @@ class ContactDetailViewModel: ContactDetailViewModelProtocol {
         self.contact = DcContact(id: contactId)
         self.sharedChats = context.getChatlist(flags: 0, queryString: nil, queryId: contactId)
 
+        sections.append(.attachments)
         sections.append(.startChat)
         if sharedChats.length > 0 {
             sections.append(.sharedChats)
@@ -52,9 +61,9 @@ class ContactDetailViewModel: ContactDetailViewModelProtocol {
         sections.append(.chatActions)
 
         if chatId != nil {
-            actions = [.archiveChat, .blockChat, .deleteChat]
+            chatActions = [.archiveChat, .blockChat, .deleteChat]
         } else {
-            actions = [.blockChat]
+            chatActions = [.blockChat]
         }
     }
 
@@ -62,8 +71,12 @@ class ContactDetailViewModel: ContactDetailViewModelProtocol {
         return sections[section]
     }
 
-    func actionFor(row: Int) -> ContactDetailViewModel.ChatAction {
-        return actions[row]
+    func chatActionFor(row: Int) -> ContactDetailViewModel.ChatAction {
+        return chatActions[row]
+    }
+
+    func attachmentActionFor(row: Int) -> ContactDetailViewModel.AttachmentAction {
+        return attachmentActions[row]
     }
 
     var chatIsArchived: Bool {
@@ -80,9 +93,10 @@ class ContactDetailViewModel: ContactDetailViewModelProtocol {
 
     func numberOfRowsInSection(_ section: Int) -> Int {
         switch sections[section] {
+        case .attachments: return 2
         case .sharedChats: return sharedChats.length
         case .startChat: return 1
-        case .chatActions: return actions.count
+        case .chatActions: return chatActions.count
         }
     }