Jelajahi Sumber

let new-group-view also handle broadcasts

B. Petersen 3 tahun lalu
induk
melakukan
3b1fa650f5

+ 4 - 0
DcCore/DcCore/DC/Wrapper.swift

@@ -256,6 +256,10 @@ public class DcContext {
         return Int(dc_create_group_chat(contextPointer, verified ? 1 : 0, name))
     }
 
+    public func createBroadcastList() -> Int {
+        return Int(dc_create_broadcast_list(contextPointer))
+    }
+
     public func addContactToChat(chatId: Int, contactId: Int) -> Bool {
         return dc_add_contact_to_chat(contextPointer, UInt32(chatId), UInt32(contactId)) == 1
     }

+ 0 - 7
deltachat-ios/Controller/AddGroupMembersViewController.swift

@@ -5,10 +5,6 @@ class AddGroupMembersViewController: GroupMembersViewController {
     var onMembersSelected: ((Set<Int>) -> Void)?
     lazy var isVerifiedGroup: Bool = false
 
-    lazy var isNewGroup: Bool = {
-        return chat == nil
-    }()
-
     private lazy var sections: [AddGroupMemberSections] = {
         if isVerifiedGroup {
             return [.memberList]
@@ -91,9 +87,6 @@ class AddGroupMembersViewController: GroupMembersViewController {
 
     @objc func doneButtonPressed() {
         if let onMembersSelected = onMembersSelected {
-            if isNewGroup {
-                selectedContactIds.insert(Int(DC_CONTACT_ID_SELF))
-            }
             onMembersSelected(selectedContactIds)
         }
         navigationController?.popViewController(animated: true)

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

@@ -314,7 +314,7 @@ class NewChatViewController: UITableViewController {
 
     // MARK: - coordinator
     private func showNewGroupController(isVerified: Bool, createBroadcast: Bool = false) {
-        let newGroupController = NewGroupController(dcContext: dcContext, isVerified: isVerified)
+        let newGroupController = NewGroupController(dcContext: dcContext, isVerified: isVerified, createBroadcast: createBroadcast)
         navigationController?.pushViewController(newGroupController, animated: true)
     }
 

+ 101 - 58
deltachat-ios/Controller/NewGroupController.swift

@@ -13,18 +13,28 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     private var deleteGroupImage: Bool = false
 
     let isVerifiedGroup: Bool
+    let createBroadcast: Bool
     let dcContext: DcContext
     private var contactAddedObserver: NSObjectProtocol?
 
-    private let sectionGroupDetails = 0
-    private let sectionGroupDetailsRowName = 0
-    private let sectionGroupDetailsRowAvatar = 1
-    private let countSectionGroupDetails = 2
-    private let sectionInvite = 1
-    private let sectionInviteRowAddMembers = 0
-    private let sectionInviteRowShowQrCode = 1
-    private lazy var countSectionInvite: Int = 2
-    private let sectionGroupMembers = 2
+    enum DetailsRows {
+        case name
+        case avatar
+    }
+    private let detailsRows: [DetailsRows]
+
+    enum InviteRows {
+        case addMembers
+        case showQrCode
+    }
+    private let inviteRows: [InviteRows]
+
+    enum NewGroupSections {
+        case details
+        case invite
+        case members
+    }
+    private let sections: [NewGroupSections]
 
     private lazy var mediaPicker: MediaPicker? = {
         let mediaPicker = MediaPicker(navigationController: navigationController)
@@ -51,11 +61,22 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
 
     var qrInviteCodeCell: ActionCell?
 
-    init(dcContext: DcContext, isVerified: Bool) {
-        self.contactIdsForGroup = [Int(DC_CONTACT_ID_SELF)]
-        self.groupContactIds = Array(contactIdsForGroup)
+    init(dcContext: DcContext, isVerified: Bool, createBroadcast: Bool) {
         self.isVerifiedGroup = isVerified
+        self.createBroadcast = createBroadcast
         self.dcContext = dcContext
+        if createBroadcast {
+            self.sections = [.invite, .members]
+            self.detailsRows = []
+            self.inviteRows = [.addMembers]
+            self.contactIdsForGroup = []
+        } else {
+            self.sections = [.details, .invite, .members]
+            self.detailsRows = [.name, .avatar]
+            self.inviteRows = [.addMembers, .showQrCode]
+            self.contactIdsForGroup = [Int(DC_CONTACT_ID_SELF)]
+        }
+        self.groupContactIds = Array(contactIdsForGroup)
         super.init(style: .grouped)
     }
 
@@ -65,13 +86,19 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
 
     override func viewDidLoad() {
         super.viewDidLoad()
-        title = isVerifiedGroup ? String.localized("menu_new_verified_group") : String.localized("menu_new_group")
+        if isVerifiedGroup {
+            title = String.localized("menu_new_verified_group")
+        } else if createBroadcast {
+            title = String.localized("new_broadcast_list")
+        } else {
+            title = String.localized("menu_new_group")
+        }
         doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonPressed))
         navigationItem.rightBarButtonItem = doneButton
-        doneButton.isEnabled = false
         tableView.register(ContactCell.self, forCellReuseIdentifier: "contactCell")
         tableView.register(ActionCell.self, forCellReuseIdentifier: "actionCell")
         self.hideKeyboardOnTap()
+        checkDoneButton()
     }
 
     override func viewWillAppear(_ animated: Bool) {
@@ -99,53 +126,55 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
         }
     }
 
+    private func checkDoneButton() {
+        var nameOk = true
+        if !createBroadcast {
+            let name = groupNameCell.textField.text ?? ""
+            nameOk = !name.isEmpty
+        }
+        doneButton.isEnabled = nameOk && contactIdsForGroup.count >= 1
+    }
 
     @objc func doneButtonPressed() {
-        if groupChatId == 0 {
+        if createBroadcast {
+            groupChatId = dcContext.createBroadcastList()
+        } else if groupChatId == 0 {
             groupChatId = dcContext.createGroupChat(verified: isVerifiedGroup, name: groupName)
         } else {
             _ = dcContext.setChatName(chatId: groupChatId, name: groupName)
         }
 
         for contactId in contactIdsForGroup {
-            let success = dcContext.addContactToChat(chatId: groupChatId, contactId: contactId)
-
-            if let groupImage = changeGroupImage {
-                AvatarHelper.saveChatAvatar(dcContext: dcContext, image: groupImage, for: groupChatId)
-            } else if deleteGroupImage {
-                AvatarHelper.saveChatAvatar(dcContext: dcContext, image: nil, for: groupChatId)
-            }
-
-            if success {
-                logger.info("successfully added \(contactId) to group \(groupName)")
-            } else {
-                logger.error("failed to add \(contactId) to group \(groupName)")
-            }
+            _ = dcContext.addContactToChat(chatId: groupChatId, contactId: contactId)
+        }
+        if let groupImage = changeGroupImage {
+            AvatarHelper.saveChatAvatar(dcContext: dcContext, image: groupImage, for: groupChatId)
+        } else if deleteGroupImage {
+            AvatarHelper.saveChatAvatar(dcContext: dcContext, image: nil, for: groupChatId)
         }
 
         showGroupChat(chatId: Int(groupChatId))
     }
 
     override func numberOfSections(in _: UITableView) -> Int {
-        return 3
+        return sections.count
     }
 
     override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
-        let section = indexPath.section
         let row = indexPath.row
 
-        switch section {
-        case sectionGroupDetails:
-             if row == sectionGroupDetailsRowAvatar {
+        switch sections[indexPath.section] {
+        case .details:
+            if detailsRows[row] == .avatar {
                 return avatarSelectionCell
             } else {
                 return groupNameCell
             }
-        case sectionInvite:
-            if row == sectionInviteRowAddMembers {
+        case .invite:
+            if inviteRows[row] == .addMembers {
                 let cell = tableView.dequeueReusableCell(withIdentifier: "actionCell", for: indexPath)
                 if let actionCell = cell as? ActionCell {
-                    actionCell.actionTitle = String.localized("group_add_members")
+                    actionCell.actionTitle = String.localized(createBroadcast ? "add_recipients" : "group_add_members")
                     actionCell.actionColor = UIColor.systemBlue
                     actionCell.isUserInteractionEnabled = true
                 }
@@ -160,7 +189,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
                 }
                 return cell
             }
-        default:
+        case .members:
             let cell = tableView.dequeueReusableCell(withIdentifier: "contactCell", for: indexPath)
             if let contactCell = cell as? ContactCell {
                 let contact = dcContext.getContact(id: groupContactIds[row])
@@ -180,47 +209,50 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     }
 
     override func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
-        if section == sectionGroupDetails && isVerifiedGroup {
+        if sections[section] == .details && isVerifiedGroup {
             return String.localized("verified_group_explain")
+        } else if sections[section] == .invite && createBroadcast {
+            return String.localized("chat_new_broadcast_hint")
         }
         return nil
     }
 
     override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
-        let section = indexPath.section
-        switch section {
-        case sectionGroupDetails, sectionInvite:
+        switch sections[indexPath.section] {
+        case .details, .invite:
             return UITableView.automaticDimension
-        default:
+        case .members:
             return ContactCell.cellHeight
         }
     }
 
     override func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
-        switch section {
-        case sectionGroupDetails:
-            return countSectionGroupDetails
-        case sectionInvite:
-            return countSectionInvite
-        default:
+        switch sections[section] {
+        case .details:
+            return detailsRows.count
+        case .invite:
+            return inviteRows.count
+        case .members:
             return contactIdsForGroup.count
         }
     }
 
     override func tableView(_: UITableView, titleForHeaderInSection section: Int) -> String? {
-        if section == sectionGroupMembers {
-            return String.localized("in_this_group_desktop")
+        if sections[section] == .members {
+            if createBroadcast {
+                return String.localized(stringID: "n_recipients", count: contactIdsForGroup.count)
+            } else {
+                return String.localized(stringID: "n_members", count: contactIdsForGroup.count)
+            }
         } else {
             return nil
         }
     }
 
     override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
-        let section = indexPath.section
-        let row = indexPath.row
-        if section == sectionInvite {
+        if sections[indexPath.section] == .invite {
             tableView.deselectRow(at: indexPath, animated: false)
-            if row == sectionInviteRowAddMembers {
+            if inviteRows[indexPath.row] == .addMembers {
                 var contactsWithoutSelf = contactIdsForGroup
                 contactsWithoutSelf.remove(Int(DC_CONTACT_ID_SELF))
                 showAddMembers(preselectedMembers: contactsWithoutSelf, isVerified: self.isVerifiedGroup)
@@ -232,11 +264,10 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
     }
 
     override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
-        let section = indexPath.section
         let row = indexPath.row
 
         // swipe by delete
-        if section == sectionGroupMembers, groupContactIds[row] != DC_CONTACT_ID_SELF {
+        if sections[indexPath.section] == .members, groupContactIds[indexPath.row] != DC_CONTACT_ID_SELF {
             let delete = UITableViewRowAction(style: .destructive, title: String.localized("remove_desktop")) { [weak self] _, indexPath in
                 guard let self = self else { return }
                 if self.groupChatId != 0,
@@ -256,13 +287,12 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
         }
     }
 
-
     private func updateGroupName(textView: UITextField) {
         let name = textView.text ?? ""
         groupName = name
-        doneButton.isEnabled = name.containsCharacters()
         qrInviteCodeCell?.isUserInteractionEnabled = name.containsCharacters()
         qrInviteCodeCell?.actionColor = groupName.isEmpty ? DcColors.colorDisabled : UIColor.systemBlue
+        checkDoneButton()
     }
 
     private func onAvatarTapped() {
@@ -302,6 +332,7 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
         }
         groupContactIds = Array(contactIdsForGroup)
         self.tableView.reloadData()
+        checkDoneButton()
     }
 
     func updateGroupContactIdsOnListSelection(_ members: Set<Int>) {
@@ -314,13 +345,21 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
         contactIdsForGroup = members
         groupContactIds = Array(members)
         self.tableView.reloadData()
+        checkDoneButton()
     }
 
     func removeGroupContactFromList(at indexPath: IndexPath) {
         let row = indexPath.row
         self.contactIdsForGroup.remove(self.groupContactIds[row])
         self.groupContactIds.remove(at: row)
+
+        CATransaction.begin()
+        CATransaction.setCompletionBlock {
+            self.tableView.reloadData() // needed to update the "N members"-title, however do not interrupt the nice delete-animation
+            self.checkDoneButton()
+        }
         tableView.deleteRows(at: [indexPath], with: .fade)
+        CATransaction.commit()
     }
 
     // MARK: - coordinator
@@ -358,6 +397,10 @@ class NewGroupController: UITableViewController, MediaPickerDelegate {
                                                                isVerified: isVerified)
         newGroupController.onMembersSelected = { [weak self] (memberIds: Set<Int>) -> Void in
             guard let self = self else { return }
+            var memberIds = memberIds
+            if !self.createBroadcast {
+                memberIds.insert(Int(DC_CONTACT_ID_SELF))
+            }
             self.updateGroupContactIdsOnListSelection(memberIds)
         }
         navigationController?.pushViewController(newGroupController, animated: true)