diff --git a/src/app/modules/main/chat_section/chat_content/users/module.nim b/src/app/modules/main/chat_section/chat_content/users/module.nim index fe509f2e81..12d0f5a2de 100644 --- a/src/app/modules/main/chat_section/chat_content/users/module.nim +++ b/src/app/modules/main/chat_section/chat_content/users/module.nim @@ -1,4 +1,4 @@ -import NimQml +import NimQml, strutils import io_interface import ../io_interface as delegate_interface import view, controller @@ -184,3 +184,8 @@ method onChatMemberUpdated*(self: Module, publicKey: string, admin: bool, joined contactDetails.isidenticon, admin, joined) + +method getMembersPublicKeys*(self: Module): string = + let publicKeys = self.controller.getMembersPublicKeys() + return publicKeys.join(" ") + diff --git a/src/app/modules/main/chat_section/chat_content/users/private_interfaces/module_view_delegate_interface.nim b/src/app/modules/main/chat_section/chat_content/users/private_interfaces/module_view_delegate_interface.nim index 2c8910abd9..7a7920a044 100644 --- a/src/app/modules/main/chat_section/chat_content/users/private_interfaces/module_view_delegate_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/users/private_interfaces/module_view_delegate_interface.nim @@ -1,2 +1,5 @@ method viewDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") + +method getMembersPublicKeys*(self: AccessInterface): string {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/chat_content/users/view.nim b/src/app/modules/main/chat_section/chat_content/users/view.nim index b575392ef0..d6cf5b0db4 100644 --- a/src/app/modules/main/chat_section/chat_content/users/view.nim +++ b/src/app/modules/main/chat_section/chat_content/users/view.nim @@ -35,3 +35,6 @@ QtObject: QtProperty[QVariant] model: read = getModel notify = modelChanged + + proc getMembersPublicKeys*(self: View): string {.slot.} = + return self.delegate.getMembersPublicKeys() diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 1814a14b42..60d6873bce 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -1,4 +1,4 @@ -import NimQml, Tables, chronicles, json, sequtils +import NimQml, Tables, chronicles, json, sequtils, strutils import io_interface import ../io_interface as delegate_interface import view, controller, item, sub_item, sub_model, base_item @@ -229,11 +229,15 @@ proc initContactRequestsModel(self: Module) = self.view.contactRequestsModel().addItems(contactsWhoAddedMe) -method initListOfMyContacts*(self: Module) = +proc convertPubKeysToJson(self: Module, pubKeys: string): seq[string] = + return map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr) + + +method initListOfMyContacts*(self: Module, pubKeys: string) = var myContacts: seq[contacts_item.Item] let contacts = self.controller.getContacts() for c in contacts: - if(c.isContact() and not c.isBlocked()): + if(c.isContact() and not c.isBlocked() and not pubKeys.contains(c.id)): let item = self.createItemFromPublicKey(c.id) myContacts.add(item) @@ -595,9 +599,6 @@ method onNewMessagesReceived*(self: Module, chatId: string, unviewedMessagesCoun return self.updateNotifications(chatId, unviewedMessagesCount, unviewedMentionsCount) -proc convertPubKeysToJson(self: Module, pubKeys: string): seq[string] = - return map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr) - method addGroupMembers*(self: Module, chatId: string, pubKeys: string) = self.controller.addGroupMembers(chatId, self.convertPubKeysToJson(pubKeys)) diff --git a/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim b/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim index eaf44ea1fe..4da718fbd6 100644 --- a/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim +++ b/src/app/modules/main/chat_section/private_interfaces/module_view_delegate_interface.nim @@ -78,7 +78,7 @@ method joinGroup*(self: AccessInterface) {.base.} = method joinGroupChatFromInvitation*(self: AccessInterface, groupName: string, chatId: string, adminPK: string) {.base.} = raise newException(ValueError, "No implementation available") -method initListOfMyContacts*(self: AccessInterface) {.base.} = +method initListOfMyContacts*(self: AccessInterface, pubKeys: string) {.base.} = raise newException(ValueError, "No implementation available") method clearListOfMyContacts*(self: AccessInterface) {.base.} = diff --git a/src/app/modules/main/chat_section/view.nim b/src/app/modules/main/chat_section/view.nim index 7cf18a4f24..5c953d3d0a 100644 --- a/src/app/modules/main/chat_section/view.nim +++ b/src/app/modules/main/chat_section/view.nim @@ -82,8 +82,8 @@ QtObject: proc listOfMyContactsChanged*(self: View) {.signal.} - proc populateMyContacts*(self: View) {.slot.} = - self.delegate.initListOfMyContacts() + proc populateMyContacts*(self: View, pubKeys: string) {.slot.} = + self.delegate.initListOfMyContacts(pubKeys) self.listOfMyContactsChanged() proc clearMyContacts*(self: View) {.slot.} = diff --git a/ui/app/AppLayouts/Chat/ChatLayout.qml b/ui/app/AppLayouts/Chat/ChatLayout.qml index b0eaa1ec14..650c18ea4d 100644 --- a/ui/app/AppLayouts/Chat/ChatLayout.qml +++ b/ui/app/AppLayouts/Chat/ChatLayout.qml @@ -31,7 +31,9 @@ StatusAppThreePanelLayout { // Not Refactored property var messageStore - property RootStore rootStore: RootStore { } + property RootStore rootStore: RootStore { + contactsStore: root.contactsStore + } property Component pinnedMessagesListPopupComponent property bool stickersLoaded: false diff --git a/ui/app/AppLayouts/Chat/panels/ContactListPanel.qml b/ui/app/AppLayouts/Chat/panels/ContactListPanel.qml index 5aa5d20300..2d3686707c 100644 --- a/ui/app/AppLayouts/Chat/panels/ContactListPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/ContactListPanel.qml @@ -14,9 +14,12 @@ ScrollView { property bool selectMode: true property var onItemChecked + property var selectedPubKeys: [] + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.vertical.policy: groupMembers.contentHeight > groupMembers.height ? ScrollBar.AlwaysOn : ScrollBar.AlwaysOff + ListView { id: groupMembers anchors.fill: parent @@ -24,7 +27,7 @@ ScrollView { clip: true delegate: StatusListItem { id: contactDelegate - property bool isChecked: false + property bool isChecked: selectedPubKeys.indexOf(model.pubKey) !== -1 title: !model.name.endsWith(".eth") && !!model.localNickname ? model.localNickname : Utils.removeStatusEns(model.name) image.source: Global.getProfileImage(model.pubKey) || model.identicon @@ -35,20 +38,34 @@ ScrollView { } return checkbox.checked } + + height: visible ? implicitHeight : 0 + + function contactToggled(pubKey) { + if (contactListPanel.selectMode) { + let pubkeys = contactListPanel.selectedPubKeys + let idx = pubkeys.indexOf(pubKey) + if (idx === -1) { + pubkeys.push(pubKey) + } else if (idx > -1) { + pubkeys.splice(idx, 1); + } + contactListPanel.selectedPubKeys = pubkeys + } + } + components: [ StatusCheckBox { id: checkbox - visible: contactListPanel.selectMode && !model.isUser - checked: contactDelegate.isChecked + visible: contactListPanel.selectMode + checked: selectedPubKeys.indexOf(model.pubKey) !== -1 onClicked: { - contactDelegate.isChecked = !contactDelegate.isChecked - onItemChecked(model.pubKey, contactDelegate.isChecked) + contactDelegate.contactToggled(model.pubKey) } } ] onClicked: { - contactDelegate.isChecked = !contactDelegate.isChecked - onItemChecked(model.pubKey, contactDelegate.isChecked) + contactDelegate.contactToggled(model.pubKey) } } } diff --git a/ui/app/AppLayouts/Chat/popups/GroupChatPopup.qml b/ui/app/AppLayouts/Chat/popups/GroupChatPopup.qml index 148b986813..89e5966b9a 100644 --- a/ui/app/AppLayouts/Chat/popups/GroupChatPopup.qml +++ b/ui/app/AppLayouts/Chat/popups/GroupChatPopup.qml @@ -20,7 +20,6 @@ ModalPopup { property var chatSectionModule property var store - property var pubKeys: [] property bool selectChatMembers: true property int memberCount: 1 readonly property int maxMembers: 10 @@ -31,9 +30,9 @@ ModalPopup { searchBox.text = ""; selectChatMembers = true; memberCount = 1; - pubKeys = []; + contactList.selectedPubKeys = []; - chatSectionModule.populateMyContacts() + chatSectionModule.populateMyContacts("") noContactsRect.visible = !chatSectionModule.listOfMyContacts.rowCount() > 0 contactList.visible = !noContactsRect.visible; @@ -62,11 +61,11 @@ ModalPopup { if (!validate()) { return } - if (pubKeys.length === 0) { + if (contactList.selectedPubKeys.length === 0) { return; } - popup.chatSectionModule.createGroupChat(Utils.filterXSS(groupName.text), JSON.stringify(pubKeys)); + popup.chatSectionModule.createGroupChat(Utils.filterXSS(groupName.text), JSON.stringify(contactList.selectedPubKeys)); popup.close(); } @@ -134,19 +133,8 @@ ModalPopup { selectMode: selectChatMembers && memberCount < maxMembers anchors.topMargin: 50 anchors.top: searchBox.bottom - onItemChecked: function(pubKey, itemChecked){ - var idx = pubKeys.indexOf(pubKey) - if(itemChecked){ - if(idx === -1){ - pubKeys.push(pubKey) - } - } else { - if(idx > -1){ - pubKeys.splice(idx, 1); - } - } - memberCount = pubKeys.length + 1; - btnSelectMembers.enabled = pubKeys.length > 0 + onSelectedPubKeysChanged:{ + memberCount = selectedPubKeys.length + 1; } } @@ -162,9 +150,9 @@ ModalPopup { icon.name: "arrow-right" icon.width: 20 icon.height: 16 - enabled: !!pubKeys.length + enabled: contactList.selectedPubKeys.length > 0 onClicked : { - if(pubKeys.length > 0) + if (contactList.selectedPubKeys.length > 0) selectChatMembers = false searchBox.text = "" groupName.forceActiveFocus(Qt.MouseFocusReason) diff --git a/ui/app/AppLayouts/Chat/popups/GroupInfoPopup.qml b/ui/app/AppLayouts/Chat/popups/GroupInfoPopup.qml index 951c3ed706..e902abac11 100644 --- a/ui/app/AppLayouts/Chat/popups/GroupInfoPopup.qml +++ b/ui/app/AppLayouts/Chat/popups/GroupInfoPopup.qml @@ -28,27 +28,26 @@ StatusModal { property bool addMembers: false property int currMemberCount: 1 property int memberCount: 1 - readonly property int maxMembers: 10 - property var pubKeys: [] + property int channelType: GroupInfoPopup.ChannelType.ActiveChannel - property QtObject channel - property bool isAdmin: false + property var chatDetails + property bool isAdmin: popup.chatSectionModule.activeItem.amIChatAdmin property Component pinnedMessagesPopupComponent property var chatContentModule - function resetSelectedMembers(){ - pubKeys = [] + readonly property int maxMembers: 10 + + function resetSelectedMembers() { + contactList.selectedPubKeys = [] memberCount = popup.chatContentModule.usersModule.model.rowCount() currMemberCount = memberCount - popup.store.addToGroupContacts.clear() } - function doAddMembers(){ - if(pubKeys.length === 0) return; - if (popup.channel) { - popup.chatSectionModule.addGroupMembers(popup.channel.id, JSON.stringify(pubKeys)); + function doAddMembers() { + if (popup.chatDetails.id && contactList.selectedPubKeys.length > 0) { + popup.chatSectionModule.addGroupMembers(popup.chatDetails.id, JSON.stringify(contactList.selectedPubKeys)); } popup.close(); } @@ -57,23 +56,25 @@ StatusModal { anchors.centerIn: parent //% "Add members" - header.title: addMembers ? qsTrId("add-members") : (popup.channel ? popup.channel.name : "") + header.title: addMembers ? qsTrId("add-members") : (popup.chatDetails ? popup.chatDetails.name : "") header.subTitle: { let cnt = memberCount; - if(addMembers){ + if (addMembers) { //% "%1 / 10 members" return qsTrId("%1-/-10-members").arg(cnt) } else { //% "%1 members" - if(cnt > 1) return qsTrId("%1-members").arg(cnt); + if (cnt > 1) { + return qsTrId("%1-members").arg(cnt); + } //% "1 member" return qsTrId("1-member"); } } header.editable: !addMembers && popup.isAdmin header.icon.isLetterIdenticon: true - header.icon.name: popup.channel ? popup.channel.name : "" - header.icon.background.color: popup.channel ? popup.channel.color : "transparent" + header.icon.name: popup.chatDetails ? popup.chatDetails.name : "" + header.icon.background.color: popup.chatDetails ? popup.chatDetails.color : "transparent" onEditButtonClicked: renameGroupPopup.open() @@ -83,14 +84,10 @@ StatusModal { } onOpened: { - popup.chatContentModule = popup.store.currentChatContentModule() - - chatSectionModule.populateMyContacts() + chatSectionModule.populateMyContacts(popup.chatContentModule.usersModule.getMembersPublicKeys()) addMembers = false; - if (popup.channel) { - popup.isAdmin = popup.chatSectionModule.activeItem.amIChatAdmin - } + btnSelectMembers.enabled = false; resetSelectedMembers(); } @@ -98,69 +95,66 @@ StatusModal { ColumnLayout { id: addMembersItem - width: parent.width - 2*Style.current.padding - height: parent.height anchors.top: parent.top anchors.topMargin: Style.current.halfPadding anchors.horizontalCenter: parent.horizontalCenter + width: parent.width - 2 * Style.current.padding + height: parent.height + visible: addMembers spacing: Style.current.padding StatusBaseInput { id: searchBox + Layout.fillWidth: true + Layout.alignment: Qt.AlignTop + implicitHeight: 36 //% "Search" placeholderText: qsTrId("search") placeholderFont.pixelSize: 15 + icon.name: "search" icon.width: 17 icon.height: 17 - Layout.fillWidth: true - Layout.alignment: Qt.AlignTop } NoFriendsRectangle { - visible: popup.store.addToGroupContacts.count === 0 && memberCount === 0 Layout.preferredHeight: childrenRect.height Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter Layout.bottomMargin: childrenRect.height + + visible: popup.store.contactsStore.myContactsModel.count === 0 } NoFriendsRectangle { - visible: popup.store.addToGroupContacts.count === 0 && memberCount > 0 + Layout.preferredHeight: childrenRect.height + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + Layout.bottomMargin: childrenRect.height + + visible: chatSectionModule.listOfMyContacts.rowCount() === 0 //% "All your contacts are already in the group" text: qsTrId("group-chat-all-contacts-invited") textColor: Style.current.textColor - Layout.preferredHeight: childrenRect.height - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - Layout.bottomMargin: childrenRect.height } ContactListPanel { id: contactList - visible: popup.chatContentModule.usersModule.model.rowCount() > 0 + Layout.fillHeight: true Layout.fillWidth: true + + visible: popup.addMembers && chatSectionModule.listOfMyContacts.rowCount() > 0 model: chatSectionModule.listOfMyContacts selectMode: memberCount < maxMembers searchString: searchBox.text.toLowerCase() - onItemChecked: function(pubKey, itemChecked){ - var idx = pubKeys.indexOf(pubKey) - if(itemChecked){ - if(idx === -1){ - pubKeys.push(pubKey) - } - } else { - if(idx > -1){ - pubKeys.splice(idx, 1); - } - } - memberCount = popup.chatContentModule.usersModule.model.rowCount() + pubKeys.length; - btnSelectMembers.enabled = pubKeys.length > 0 + onSelectedPubKeysChanged: { + memberCount = popup.chatContentModule.usersModule.model.rowCount() + selectedPubKeys.length; + btnSelectMembers.enabled = selectedPubKeys.length > 0 } } } @@ -168,12 +162,13 @@ StatusModal { ColumnLayout { id: groupInfoItem - width: parent.width - 2*Style.current.padding - height: parent.height - 2*Style.current.padding anchors.top: parent.top anchors.topMargin: Style.current.padding anchors.horizontalCenter: parent.horizontalCenter + width: parent.width - 2*Style.current.padding + height: parent.height - 2*Style.current.padding + visible: !addMembers spacing: Style.current.padding @@ -247,7 +242,7 @@ StatusModal { icon.height: 16 //% "Make Admin" text: qsTrId("make-admin") - onTriggered: popup.chatSectionModule.makeAdmin(popup.channel.id, model.id) + onTriggered: popup.chatSectionModule.makeAdmin(popup.chatDetails.id, model.id) } StatusMenuItem { icon.name: "remove-contact" @@ -256,7 +251,7 @@ StatusModal { type: StatusMenuItem.Type.Danger //% "Remove From Group" text: qsTrId("remove-from-group") - onTriggered: popup.chatSectionModule.removeMemberFromGroupChat(popup.channel.id, model.id) + onTriggered: popup.chatSectionModule.removeMemberFromGroupChat(popup.chatDetails.id, model.id) } } } diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 7d6b1b8895..3ef85ea90f 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -5,6 +5,7 @@ import utils 1.0 QtObject { id: root + property var contactsStore // Important: // Each `ChatLayout` has its own chatCommunitySectionModule // (on the backend chat and community sections share the same module since they are actually the same) diff --git a/ui/app/AppLayouts/Chat/views/ChatContentView.qml b/ui/app/AppLayouts/Chat/views/ChatContentView.qml index 61f3fc14a0..851916efe4 100644 --- a/ui/app/AppLayouts/Chat/views/ChatContentView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatContentView.qml @@ -125,9 +125,9 @@ ColumnLayout { switch (chatContentModule.chatDetails.type) { case Constants.chatType.privateGroupChat: Global.openPopup(groupInfoPopupComponent, { - channelType: GroupInfoPopup.ChannelType.ActiveChannel, - channel: chatContentModule.chatDetails - }) + chatContentModule: chatContentModule, + chatDetails: chatContentModule.chatDetails + }) break; case Constants.chatType.oneToOne: Global.openProfilePopup(chatContentModule.chatDetails.id) @@ -230,9 +230,9 @@ ColumnLayout { onDisplayGroupInfoPopup: { Global.openPopup(groupInfoPopupComponent, { - channelType: GroupInfoPopup.ChannelType.ActiveChannel, - channel: chatContentModule.chatDetails - }) + chatContentModule: chatContentModule, + chatDetails: chatContentModule.chatDetails + }) } } } diff --git a/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml b/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml index 9dbb1d19d1..6bfc20e9bf 100644 --- a/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/CommunityColumnView.qml @@ -315,12 +315,11 @@ Item { onDisplayGroupInfoPopup: { communitySectionModule.prepareChatContentModuleForChatId(chatId) - let chatContentModule = communitySectionModule.getChatContentModule() Global.openPopup(groupInfoPopupComponent, { - channelType: GroupInfoPopup.ChannelType.ActiveChannel, - channel: chatContentModule.chatDetails - }) + chatContentModule: chatContentModule, + chatDetails: chatContentModule.chatDetails + }) } onEditCommunityChannel: { diff --git a/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml b/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml index 6e2dd2c7a9..8e6bae16be 100644 --- a/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml +++ b/ui/app/AppLayouts/Chat/views/ContactsColumnView.qml @@ -297,8 +297,8 @@ Item { chatSectionModule.prepareChatContentModuleForChatId(chatId) let chatContentModule = chatSectionModule.getChatContentModule() Global.openPopup(groupInfoPopupComponent, { - channelType: GroupInfoPopup.ChannelType.ActiveChannel, - channel: chatContentModule.chatDetails + chatContentModule: chatContentModule, + chatDetails: chatContentModule.chatDetails }) } }