From a586c6d3529fe5fac96ba61e960b2c8a20faad7e Mon Sep 17 00:00:00 2001 From: Mykhailo Prakhov Date: Wed, 20 Mar 2024 11:50:10 +0100 Subject: [PATCH] feat(desktop/communities): View community member messages functionality (#14002) * feat(desktop/communities): View member messages functionality --- .../modules/main/activity_center/module.nim | 1 + .../chat_content/io_interface.nim | 3 + .../chat_content/messages/module.nim | 5 +- .../main/chat_section/chat_content/module.nim | 8 +- .../modules/main/chat_section/controller.nim | 25 +++ .../main/chat_section/io_interface.nim | 17 +++ src/app/modules/main/chat_section/module.nim | 134 +++++++++++++++- src/app/modules/main/chat_section/view.nim | 27 +++- .../modules/shared_models/message_item.nim | 15 +- .../modules/shared_models/message_model.nim | 4 + .../service/message/async_tasks.nim | 33 +++- .../service/message/dto/message.nim | 4 +- src/app_service/service/message/service.nim | 69 ++++++++- src/backend/messages.nim | 21 +++ ui/app/AppLayouts/Chat/stores/RootStore.qml | 4 + .../panels/MembersSettingsPanel.qml | 4 + .../Communities/panels/MembersTabPanel.qml | 29 ++++ .../popups/CommunityMemberMessagesPopup.qml | 143 ++++++++++++++++++ ui/app/AppLayouts/Communities/popups/qmldir | 1 + .../views/CommunitySettingsView.qml | 6 +- ui/app/mainui/AppMain.qml | 9 ++ ui/app/mainui/Popups.qml | 16 ++ ui/imports/shared/views/chat/MessageView.qml | 28 ++-- ui/imports/utils/Global.qml | 2 + vendor/status-go | 2 +- 25 files changed, 580 insertions(+), 30 deletions(-) create mode 100644 ui/app/AppLayouts/Communities/popups/CommunityMemberMessagesPopup.qml diff --git a/src/app/modules/main/activity_center/module.nim b/src/app/modules/main/activity_center/module.nim index dfb4462a5d..0f758b8912 100644 --- a/src/app/modules/main/activity_center/module.nim +++ b/src/app/modules/main/activity_center/module.nim @@ -100,6 +100,7 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, communityId: st return msg_item_qobj.newMessageItem(msg_item.initItem( message.id, communityId, # we don't received community id via `activityCenterNotifications` api call + message.chatId, message.responseTo, message.`from`, contactDetails.defaultDisplayName, diff --git a/src/app/modules/main/chat_section/chat_content/io_interface.nim b/src/app/modules/main/chat_section/chat_content/io_interface.nim index 9ab46ebbcc..a8e0d5986e 100644 --- a/src/app/modules/main/chat_section/chat_content/io_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/io_interface.nim @@ -142,3 +142,6 @@ method setPermissionsCheckOngoing*(self: AccessInterface, value: bool) {.base.} method getPermissionsCheckOngoing*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") + +method scrollToMessage*(self: AccessInterface, messageId: string) {.base.} = + raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/messages/module.nim b/src/app/modules/main/chat_section/chat_content/messages/module.nim index 1cc3576f1e..7056823316 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/module.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/module.nim @@ -151,6 +151,7 @@ proc createMessageItemsFromMessageDtos(self: Module, messages: seq[MessageDto], var item = initItem( message.id, + message.chatId, message.communityId, message.responseTo, message.`from`, @@ -239,6 +240,7 @@ proc createFetchMoreMessagesItem(self: Module): Item = result = initItem( FETCH_MORE_MESSAGES_MESSAGE_ID, communityId = "", + chatId = "", responseToMessageWithId = "", senderId = chatDto.id, senderDisplayName = "", @@ -306,6 +308,7 @@ proc createChatIdentifierItem(self: Module): Item = result = initItem( CHAT_IDENTIFIER_MESSAGE_ID, communityId = "", + chatId = "", responseToMessageWithId = "", senderId = chatDto.id, senderDisplayName = chatName, @@ -389,7 +392,7 @@ proc currentUserWalletContainsAddress(self: Module, address: string): bool = return false method reevaluateViewLoadingState*(self: Module) = - let loading = not self.initialMessagesLoaded or + let loading = not self.initialMessagesLoaded or not self.firstUnseenMessageState.initialized or self.firstUnseenMessageState.fetching or self.view.getMessageSearchOngoing() diff --git a/src/app/modules/main/chat_section/chat_content/module.nim b/src/app/modules/main/chat_section/chat_content/module.nim index 3363485a6f..12854287fd 100644 --- a/src/app/modules/main/chat_section/chat_content/module.nim +++ b/src/app/modules/main/chat_section/chat_content/module.nim @@ -58,7 +58,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt messageService) result.moduleLoaded = false - result.inputAreaModule = input_area_module.newModule(result, events, sectionId, chatId, belongsToCommunity, + result.inputAreaModule = input_area_module.newModule(result, events, sectionId, chatId, belongsToCommunity, chatService, communityService, contactService, gifService, messageService, settingsService) result.messagesModule = messages_module.newModule(result, events, sectionId, chatId, belongsToCommunity, contactService, communityService, chatService, messageService, mailserversService, sharedUrlsService) @@ -174,6 +174,7 @@ proc buildPinnedMessageItem(self: Module, message: MessageDto, actionInitiatedBy item = pinned_msg_item.initItem( message.id, message.communityId, + message.chatId, message.responseTo, message.`from`, contactDetails.defaultDisplayName, @@ -411,7 +412,7 @@ method amIChatAdmin*(self: Module): bool = return false else: let communityDto = self.controller.getCommunityDetails() - return communityDto.memberRole == MemberRole.Owner or + return communityDto.memberRole == MemberRole.Owner or communityDto.memberRole == MemberRole.Admin or communityDto.memberRole == MemberRole.TokenMaster method onUpdateViewOnlyPermissionsSatisfied*(self: Module, value: bool) = @@ -425,3 +426,6 @@ method setPermissionsCheckOngoing*(self: Module, value: bool) = method getPermissionsCheckOngoing*(self: Module): bool = self.view.getPermissionsCheckOngoing() + +method scrollToMessage*(self: Module, messageId: string) = + self.messagesModule.scrollToMessage(messageId) \ No newline at end of file diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 63ec733200..61aa898a13 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -397,6 +397,20 @@ proc init*(self: Controller) = self.events.on(SIGNAL_MAILSERVER_HISTORY_REQUEST_COMPLETED) do(e:Args): self.delegate.setLoadingHistoryMessagesInProgress(false) + self.events.on(SIGNAL_COMMUNITY_MEMBER_ALL_MESSAGES) do(e:Args): + var args = CommunityMemberMessagesArgs(e) + if args.communityId == self.sectionId: + self.delegate.onCommunityMemberMessagesLoaded(args.messages) + + self.events.on(SIGNAL_MESSAGES_DELETED) do(e:Args): + var args = MessagesDeletedArgs(e) + let isSectionEmpty = args.communityId == "" + if args.communityId == self.sectionId or isSectionEmpty: + for chatId, messagesIds in args.deletedMessages: + if isSectionEmpty and not self.delegate.communityContainsChat(chatId): + continue + self.delegate.onCommunityMemberMessagesDeleted(messagesIds) + proc isCommunity*(self: Controller): bool = return self.isCommunitySection @@ -725,3 +739,14 @@ proc waitingOnNewCommunityOwnerToConfirmRequestToRejoin*(self: Controller, commu proc setCommunityShard*(self: Controller, shardIndex: int) = self.communityService.asyncSetCommunityShard(self.getMySectionId(), shardIndex) +proc loadCommunityMemberMessages*(self: Controller, communityId: string, memberPubKey: string) = + self.messageService.asyncLoadCommunityMemberAllMessages(communityId, memberPubKey) + +proc getTransactionDetails*(self: Controller, message: MessageDto): (string,string) = + return self.messageService.getTransactionDetails(message) + +proc getWalletAccounts*(self: Controller): seq[wallet_account_service.WalletAccountDto] = + return self.messageService.getWalletAccounts() + +proc deleteCommunityMemberMessages*(self: Controller, memberPubKey: string, messageId: string, chatId: string) = + self.messageService.deleteCommunityMemberMessages(self.getMySectionId(), memberPubKey, messageId, chatId) diff --git a/src/app/modules/main/chat_section/io_interface.nim b/src/app/modules/main/chat_section/io_interface.nim index 18dd7f45d7..b71239ba90 100644 --- a/src/app/modules/main/chat_section/io_interface.nim +++ b/src/app/modules/main/chat_section/io_interface.nim @@ -404,3 +404,20 @@ method setCommunityShard*(self: AccessInterface, shardIndex: int) {.base.} = method setShardingInProgress*(self: AccessInterface, value: bool) {.base.} = raise newException(ValueError, "No implementation available") +method loadCommunityMemberMessages*(self: AccessInterface, communityId: string, memberPubKey: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onCommunityMemberMessagesLoaded*(self: AccessInterface, messages: seq[MessageDto]) {.base.} = + raise newException(ValueError, "No implementation available") + +method deleteCommunityMemberMessages*(self: AccessInterface, memberPubKey: string, messageId: string, chatId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onCommunityMemberMessagesDeleted*(self: AccessInterface, messages: seq[string]) {.base.} = + raise newException(ValueError, "No implementation available") + +method communityContainsChat*(self: AccessInterface, chatId: string): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method openCommunityChatAndScrollToMessage*(self: AccessInterface, chatId: string, messageId: string) {.base.} = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 1438748886..010d2f139c 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -5,6 +5,9 @@ import ../io_interface as delegate_interface import view, controller, active_item import model as chats_model import item as chat_item +import ../../shared_models/message_item as member_msg_item +import ../../shared_models/message_model as member_msg_model +import ../../shared_models/message_transaction_parameters_item import ../../shared_models/user_item as user_item import ../../shared_models/user_model as user_model import ../../shared_models/token_permissions_model @@ -87,7 +90,7 @@ proc addOrUpdateChat(self: Module, sharedUrlsService: shared_urls_service.Service, setChatAsActive: bool = true, insertIntoModel: bool = true, - ): Item + ): chat_item.Item proc newModule*( delegate: delegate_interface.AccessInterface, @@ -120,6 +123,95 @@ proc newModule*( result.chatContentModules = initOrderedTable[string, chat_content_module.AccessInterface]() +proc currentUserWalletContainsAddress(self: Module, address: string): bool = + if (address.len == 0): + return false + let accounts = self.controller.getWalletAccounts() + for acc in accounts: + if (acc.address == address): + return true + return false + +# TODO: duplicates in chats and messages +proc buildCommunityMemberMessageItem(self: Module, message: MessageDto): member_msg_item.Item = + let contactDetails = self.controller.getContactDetails(message.`from`) + let communityChats = self.controller.getAllChats(self.getMySectionId()) + var quotedMessageAuthorDetails = ContactDetails() + if message.quotedMessage.`from` != "": + if message.`from` == message.quotedMessage.`from`: + quotedMessageAuthorDetails = contactDetails + else: + quotedMessageAuthorDetails = self.controller.getContactDetails(message.quotedMessage.`from`) + + var transactionContract = message.transactionParameters.contract + var transactionValue = message.transactionParameters.value + var isCurrentUser = contactDetails.isCurrentUser + if message.contentType == ContentType.Transaction: + (transactionContract, transactionValue) = self.controller.getTransactionDetails(message) + if message.transactionParameters.fromAddress != "": + isCurrentUser = self.currentUserWalletContainsAddress(message.transactionParameters.fromAddress) + return member_msg_item.initItem( + message.id, + message.communityId, + message.chatId, + message.responseTo, + message.`from`, + contactDetails.defaultDisplayName, + contactDetails.optionalName, + contactDetails.icon, + contactDetails.colorHash, + isCurrentUser, + contactDetails.dto.added, + message.outgoingStatus, + self.controller.getRenderedText(message.parsedText, communityChats), + message.text, + message.parsedText, + message.image, + message.containsContactMentions(), + message.seen, + timestamp = message.timestamp, + clock = message.clock, + message.contentType, + message.messageType, + message.contactRequestState, + message.sticker.url, + message.sticker.pack, + message.links, + message.linkPreviews, + newTransactionParametersItem(message.transactionParameters.id, + message.transactionParameters.fromAddress, + message.transactionParameters.address, + transactionContract, + transactionValue, + message.transactionParameters.transactionHash, + message.transactionParameters.commandState, + message.transactionParameters.signature), + message.mentionedUsersPks, + contactDetails.dto.trustStatus, + contactDetails.dto.ensVerified, + message.discordMessage, + resendError = "", + message.deleted, + message.deletedBy, + deletedByContactDetails = ContactDetails(), + message.mentioned, + message.quotedMessage.`from`, + message.quotedMessage.text, + self.controller.getRenderedText(message.quotedMessage.parsedText, communityChats), + message.quotedMessage.contentType, + message.quotedMessage.deleted, + message.quotedMessage.discordMessage, + quotedMessageAuthorDetails, + message.quotedMessage.albumImages, + message.quotedMessage.albumImagesCount, + message.albumId, + if len(message.albumId) == 0: @[] else: @[message.image], + if len(message.albumId) == 0: @[] else: @[message.id], + message.albumImagesCount, + message.bridgeMessage, + message.quotedMessage.bridgeMessage, + ) + method delete*(self: Module) = self.controller.delete self.view.delete @@ -166,7 +258,7 @@ proc removeSubmodule(self: Module, chatId: string) = self.chatContentModules.del(chatId) -proc addCategoryItem(self: Module, category: Category, memberRole: MemberRole, communityId: string, insertIntoModel: bool = true): Item = +proc addCategoryItem(self: Module, category: Category, memberRole: MemberRole, communityId: string, insertIntoModel: bool = true): chat_item.Item = let hasUnreadMessages = self.controller.chatsWithCategoryHaveUnreadMessages(communityId, category.id) result = chat_item.initItem( id = category.id, @@ -210,7 +302,7 @@ proc buildChatSectionUI( var selectedItemId = "" let sectionLastOpenChat = singletonInstance.localAccountSensitiveSettings.getSectionLastOpenChat(self.controller.getMySectionId()) - var items: seq[Item] = @[] + var items: seq[chat_item.Item] = @[] for categoryDto in channelGroup.categories: # Add items for the categories. We use a special type to identify categories items.add(self.addCategoryItem(categoryDto, channelGroup.memberRole, channelGroup.id)) @@ -544,7 +636,7 @@ proc addNewChat( sharedUrlsService: shared_urls_service.Service, setChatAsActive: bool = true, insertIntoModel: bool = true, - ): Item = + ): chat_item.Item = let hasNotification =chatDto.unviewedMessagesCount > 0 let notificationsCount = chatDto.unviewedMentionsCount @@ -1262,7 +1354,7 @@ proc addOrUpdateChat(self: Module, sharedUrlsService: shared_urls_service.Service, setChatAsActive: bool = true, insertIntoModel: bool = true, - ): Item = + ): chat_item.Item = let sectionId = self.controller.getMySectionId() if(belongsToCommunity and sectionId != chat.communityId or @@ -1326,7 +1418,7 @@ method addOrUpdateChat*(self: Module, sharedUrlsService: shared_urls_service.Service, setChatAsActive: bool = true, insertIntoModel: bool = true, - ): Item = + ): chat_item.Item = result = self.addOrUpdateChat( chat, ChannelGroupDto(), @@ -1430,3 +1522,33 @@ method setCommunityShard*(self: Module, shardIndex: int) = method setShardingInProgress*(self: Module, value: bool) = self.view.setShardingInProgress(value) +method loadCommunityMemberMessages*(self: Module, communityId: string, memberPubKey: string) = + self.view.getMemberMessagesModel().clear() + self.controller.loadCommunityMemberMessages(communityId, memberPubKey) + +method onCommunityMemberMessagesLoaded*(self: Module, messages: seq[MessageDto]) = + var viewItems: seq[member_msg_item.Item] + for message in messages: + let item = self.buildCommunityMemberMessageItem(message) + viewItems.add(item) + + if viewItems.len == 0: + return + + self.view.getMemberMessagesModel().insertItemsBasedOnClock(viewItems) + +method deleteCommunityMemberMessages*(self: Module, memberPubKey: string, messageId: string, chatId: string) = + self.controller.deleteCommunityMemberMessages(memberPubKey, messageId, chatId) + +method onCommunityMemberMessagesDeleted*(self: Module, deletedMessages: seq[string]) = + if self.view.getMemberMessagesModel().rowCount > 0: + for deletedMessageId in deletedMessages: + self.view.getMemberMessagesModel().removeItem(deletedMessageId) + +method communityContainsChat*(self: Module, chatId: string): bool = + return self.chatContentModules.hasKey(chatId) + +method openCommunityChatAndScrollToMessage*(self: Module, chatId: string, messageId: string) = + self.delegate.setActiveSectionById(self.getMySectionId()) + self.setActiveItem(chatId) + self.chatContentModules[chatId].scrollToMessage(messageId) \ No newline at end of file diff --git a/src/app/modules/main/chat_section/view.nim b/src/app/modules/main/chat_section/view.nim index b4762b571f..002f045e43 100644 --- a/src/app/modules/main/chat_section/view.nim +++ b/src/app/modules/main/chat_section/view.nim @@ -2,6 +2,7 @@ import NimQml, json, sequtils, strutils import model as chats_model import item, active_item import ../../shared_models/user_model as user_model +import ../../shared_models/message_model as member_msg_model import ../../shared_models/token_permissions_model import io_interface @@ -32,6 +33,9 @@ QtObject: isWaitingOnNewCommunityOwnerToConfirmRequestToRejoin: bool shardingInProgress: bool allChannelsAreHiddenBecauseNotPermitted: bool + memberMessagesModel: member_msg_model.Model + memberMessagesModelVariant: QVariant + proc delete*(self: View) = self.model.delete @@ -46,6 +50,8 @@ QtObject: self.editCategoryChannelsVariant.delete self.tokenPermissionsModel.delete self.tokenPermissionsVariant.delete + self.memberMessagesModel.delete + self.memberMessagesModelVariant.delete self.QObject.delete @@ -71,6 +77,8 @@ QtObject: result.chatsLoaded = false result.communityMetrics = "[]" result.isWaitingOnNewCommunityOwnerToConfirmRequestToRejoin = false + result.memberMessagesModel = member_msg_model.newModel() + result.memberMessagesModelVariant = newQVariant(result.memberMessagesModel) proc load*(self: View) = self.delegate.viewDidLoad() @@ -508,4 +516,21 @@ QtObject: if (allAreHidden == self.allChannelsAreHiddenBecauseNotPermitted): return self.allChannelsAreHiddenBecauseNotPermitted = allAreHidden - self.allChannelsAreHiddenBecauseNotPermittedChanged() \ No newline at end of file + self.allChannelsAreHiddenBecauseNotPermittedChanged() + proc getMemberMessagesModel*(self: View): member_msg_model.Model = + return self.memberMessagesModel + + proc getMemberMessagesModelVariant(self: View): QVariant {.slot.} = + return self.memberMessagesModelVariant + + QtProperty[QVariant] memberMessagesModel: + read = getMemberMessagesModelVariant + + proc loadCommunityMemberMessages*(self: View, communityId: string, memberPubKey: string) {.slot.} = + self.delegate.loadCommunityMemberMessages(communityId, memberPubKey) + + proc deleteCommunityMemberMessages*(self: View, memberPubKey: string, messageId: string, chatId: string) {.slot.} = + self.delegate.deleteCommunityMemberMessages(memberPubKey, messageId, chatId) + + proc openCommunityChatAndScrollToMessage*(self: View, chatId: string, messageId: string) {.slot.} = + self.delegate.openCommunityChatAndScrollToMessage(chatId, messageId) diff --git a/src/app/modules/shared_models/message_item.nim b/src/app/modules/shared_models/message_item.nim index 6a4206803f..2ced0adb1e 100644 --- a/src/app/modules/shared_models/message_item.nim +++ b/src/app/modules/shared_models/message_item.nim @@ -13,6 +13,7 @@ type Item* = ref object id: string communityId: string + chatId: string responseToMessageWithId: string senderId: string senderDisplayName: string @@ -75,6 +76,7 @@ type proc initItem*( id, communityId, + chatId, responseToMessageWithId, senderId, senderDisplayName, @@ -128,6 +130,7 @@ proc initItem*( result = Item() result.id = id result.communityId = communityId + result.chatId = chatId result.responseToMessageWithId = responseToMessageWithId result.senderId = senderId result.senderDisplayName = senderDisplayName @@ -228,6 +231,7 @@ proc initNewMessagesMarkerItem*(clock, timestamp: int64): Item = return initItem( id = "", communityId = "", + chatId = "", responseToMessageWithId = "", senderId = "", senderDisplayName = "", @@ -283,6 +287,7 @@ proc `$`*(self: Item): string = result = fmt"""Item( id: {$self.id}, communityId: {$self.communityId}, + chatId: {$self.chatId}, responseToMessageWithId: {self.responseToMessageWithId}, senderId: {self.senderId}, senderDisplayName: {$self.senderDisplayName}, @@ -320,6 +325,9 @@ proc id*(self: Item): string {.inline.} = proc communityId*(self: Item): string {.inline.} = self.communityId +proc chatId*(self: Item): string {.inline.} = + self.chatId + proc responseToMessageWithId*(self: Item): string {.inline.} = self.responseToMessageWithId @@ -419,7 +427,7 @@ proc albumMessageIds*(self: Item): seq[string] {.inline.} = proc `albumMessageIds=`*(self: Item, value: seq[string]) {.inline.} = self.albumMessageIds = value -proc albumImagesCount*(self: Item): int {.inline.} = +proc albumImagesCount*(self: Item): int {.inline.} = self.albumImagesCount proc bridgeName*(self: Item): string {.inline.} = @@ -516,6 +524,7 @@ proc toJsonNode*(self: Item): JsonNode = result = %* { "id": self.id, "communityId": self.communityId, + "chatId": self.chatId, "responseToMessageWithId": self.responseToMessageWithId, "senderId": self.senderId, "senderDisplayName": self.senderDisplayName, @@ -661,8 +670,8 @@ proc quotedMessageAlbumMessageImages*(self: Item): seq[string] {.inline.} = proc `quotedMessageAlbumMessageImages=`*(self: Item, value: seq[string]) {.inline.} = self.quotedMessageAlbumMessageImages = value -proc quotedMessageAlbumImagesCount*(self: Item): int {.inline.} = +proc quotedMessageAlbumImagesCount*(self: Item): int {.inline.} = self.quotedMessageAlbumImagesCount -proc `quotedMessageAlbumImagesCount=`*(self: Item, value: int) {.inline.} = +proc `quotedMessageAlbumImagesCount=`*(self: Item, value: int) {.inline.} = self.quotedMessageAlbumImagesCount = value \ No newline at end of file diff --git a/src/app/modules/shared_models/message_model.nim b/src/app/modules/shared_models/message_model.nim index 2dfaf70f79..23d0a77609 100644 --- a/src/app/modules/shared_models/message_model.nim +++ b/src/app/modules/shared_models/message_model.nim @@ -16,6 +16,7 @@ type NextMsgIndex NextMsgTimestamp CommunityId + ChatId ResponseToMessageWithId SenderId SenderDisplayName @@ -123,6 +124,7 @@ QtObject: ModelRole.NextMsgIndex.int:"nextMsgIndex", ModelRole.NextMsgTimestamp.int:"nextMsgTimestamp", ModelRole.CommunityId.int:"communityId", + ModelRole.ChatId.int:"chatId", ModelRole.ResponseToMessageWithId.int:"responseToMessageWithId", ModelRole.SenderId.int:"senderId", ModelRole.SenderDisplayName.int:"senderDisplayName", @@ -231,6 +233,8 @@ QtObject: result = newQVariant(0) of ModelRole.CommunityId: result = newQVariant(item.communityId) + of ModelRole.ChatId: + result = newQVariant(item.chatId) of ModelRole.ResponseToMessageWithId: result = newQVariant(item.responseToMessageWithId) of ModelRole.SenderId: diff --git a/src/app_service/service/message/async_tasks.nim b/src/app_service/service/message/async_tasks.nim index 2e39212fd7..6bf1f63857 100644 --- a/src/app_service/service/message/async_tasks.nim +++ b/src/app_service/service/message/async_tasks.nim @@ -186,7 +186,7 @@ type const asyncGetFirstUnseenMessageIdForTaskArg: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncGetFirstUnseenMessageIdForTaskArg](argEncoded) - + let responseJson = %*{ "messageId": "", "chatId": arg.chatId, @@ -338,3 +338,34 @@ const asyncMarkMessageAsUnreadTask: Task = proc(argEncoded: string) {.gcsafe, ni responseJson["error"] = %e.msg arg.finish(responseJson) + +################################################# +# Async load community member messages +################################################# +type + AsyncLoadCommunityMemberAllMessagesTaskArg = ref object of QObjectTaskArg + communityId*: string + memberPubKey*: string + +const asyncLoadCommunityMemberAllMessagesTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncLoadCommunityMemberAllMessagesTaskArg](argEncoded) + + var responseJson = %*{ + "communityId": arg.communityId, + "error": "" + } + + try: + let response = status_go.getCommunityMemberAllMessages(arg.communityId, arg.memberPubKey) + + if not response.error.isNil: + raise newException(CatchableError, "response error: " & response.error.message) + + responseJson["messages"] = response.result + + except Exception as e: + error "error: ", procName = "asyncLoadCommunityMemberAllMessagesTask", errName = e.name, + errDesription = e.msg, communityId=arg.communityId, memberPubKey=arg.memberPubKey + responseJson["error"] = %e.msg + + arg.finish(responseJson) diff --git a/src/app_service/service/message/dto/message.nim b/src/app_service/service/message/dto/message.nim index 5df3bfecb3..68195eae38 100644 --- a/src/app_service/service/message/dto/message.nim +++ b/src/app_service/service/message/dto/message.nim @@ -211,7 +211,7 @@ proc toQuotedMessage*(jsonObj: JsonNode): QuotedMessage = var bridgeMessageObj: JsonNode if(jsonObj.getProp("bridgeMessage", bridgeMessageObj)): result.bridgeMessage = toBridgeMessage(bridgeMessageObj) - + var quotedImagesArr: JsonNode if jsonObj.getProp("albumImages", quotedImagesArr): for element in quotedImagesArr.getElems(): @@ -313,7 +313,7 @@ proc toMessageDto*(jsonObj: JsonNode): MessageDto = if jsonObj.getProp("statusLinkPreviews", statusLinkPreviewsArr): for element in statusLinkPreviewsArr.getElems(): result.linkPreviews.add(element.toLinkPreview(false)) - + var parsedTextArr: JsonNode if(jsonObj.getProp("parsedText", parsedTextArr) and parsedTextArr.kind == JArray): for pTextObj in parsedTextArr: diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index 52fd157db8..ff564ccc1b 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -64,6 +64,7 @@ const SIGNAL_URLS_UNFURLED* = "urlsUnfurled" const SIGNAL_GET_MESSAGE_FINISHED* = "getMessageFinished" const SIGNAL_URLS_UNFURLING_PLAN_READY* = "urlsUnfurlingPlanReady" const SIGNAL_MESSAGE_MARKED_AS_UNREAD* = "messageMarkedAsUnread" +const SIGNAL_COMMUNITY_MEMBER_ALL_MESSAGES* = "communityMemberAllMessages" include async_tasks @@ -117,6 +118,7 @@ type deletedBy*: string MessagesDeletedArgs* = ref object of Args + communityId*: string deletedMessages*: Table[string, seq[string]] MessageDeliveredArgs* = ref object of Args @@ -154,6 +156,10 @@ type message*: MessageDto error*: string + CommunityMemberMessagesArgs* = ref object of Args + communityId*: string + messages*: seq[MessageDto] + QtObject: type Service* = ref object of QObject events: EventEmitter @@ -274,6 +280,17 @@ QtObject: discard self.asyncLoadMoreMessagesForChat(chatId) + proc asyncLoadCommunityMemberAllMessages*(self: Service, communityId: string, memberPublicKey: string) = + let arg = AsyncLoadCommunityMemberAllMessagesTaskArg( + communityId: communityId, + memberPubKey: memberPublicKey, + tptr: cast[ByteAddress](asyncLoadCommunityMemberAllMessagesTask), + vptr: cast[ByteAddress](self.vptr), + slot: "onAsyncLoadCommunityMemberAllMessages" + ) + + self.threadpool.start(arg) + proc handleMessagesUpdate(self: Service, chats: var seq[ChatDto], messages: var seq[MessageDto]) = # We included `chats` in this condition cause that's the form how `status-go` sends updates. # The first element from the `receivedData.chats` array contains details about the chat a messages received in @@ -358,8 +375,8 @@ QtObject: let data = MessageRemovedArgs(chatId: rm.chatId, messageId: rm.messageId, deletedBy: rm.deletedBy) self.events.emit(SIGNAL_MESSAGE_REMOVED, data) - proc handleDeletedMessagesUpdate(self: Service, deletedMessages: Table[string, seq[string]]) = - let data = MessagesDeletedArgs(deletedMessages: deletedMessages) + proc handleDeletedMessagesUpdate(self: Service, deletedMessages: Table[string, seq[string]], communityId: string) = + let data = MessagesDeletedArgs(deletedMessages: deletedMessages, communityId: communityId) self.events.emit(SIGNAL_MESSAGES_DELETED, data) proc handleEmojiReactionsUpdate(self: Service, emojiReactions: seq[ReactionDto]) = @@ -430,7 +447,7 @@ QtObject: self.handleRemovedMessagesUpdate(receivedData.removedMessages) # Handling deleted messages updates if (receivedData.deletedMessages.len > 0): - self.handleDeletedMessagesUpdate(receivedData.deletedMessages) + self.handleDeletedMessagesUpdate(receivedData.deletedMessages, "") # Handling emoji reactions updates if (receivedData.emojiReactions.len > 0): self.handleEmojiReactionsUpdate(receivedData.emojiReactions) @@ -542,6 +559,29 @@ QtObject: self.events.emit(SIGNAL_MESSAGES_LOADED, data) + proc onAsyncLoadCommunityMemberAllMessages*(self: Service, response: string) {.slot.} = + try: + let rpcResponseObj = response.parseJson + if rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != "": + raise newException(RpcException, rpcResponseObj{"error"}.getStr) + if rpcResponseObj{"messages"}.kind == JNull: + return + if rpcResponseObj{"messages"}.kind != JArray: + raise newException(RpcException, "invalid messages type in response") + + var communityId: string + discard rpcResponseObj.getProp("communityId", communityId) + + var messages = map(rpcResponseObj{"messages"}.getElems(), proc(x: JsonNode): MessageDto = x.toMessageDto()) + if messages.len > 0: + self.bulkReplacePubKeysWithDisplayNames(messages) + + let data = CommunityMemberMessagesArgs(communityId: communityId, messages: messages) + self.events.emit(SIGNAL_COMMUNITY_MEMBER_ALL_MESSAGES, data) + + except Exception as e: + error "error: ", procName="onAsyncLoadCommunityMemberAllMessages", errName = e.name, errDesription = e.msg + proc addReaction*(self: Service, chatId: string, messageId: string, emojiId: int) = try: let response = status_go.addReaction(chatId, messageId, emojiId) @@ -1147,3 +1187,26 @@ proc resendChatMessage*(self: Service, messageId: string): string = except Exception as e: error "error: ", procName="resendChatMessage", errName = e.name, errDesription = e.msg return fmt"{e.name}: {e.msg}" + +# TODO: would be nice to make it async but in this case we will need to show come spinner in the UI during deleting the message +proc deleteCommunityMemberMessages*(self: Service, communityId: string, memberPubKey: string, messageId: string, chatId: string) = + try: + let response = status_go.deleteCommunityMemberMessages(communityId, memberPubKey, messageId, chatId) + + if response.result.contains("error"): + let errMsg = response.result["error"].getStr + raise newException(RpcException, "Error deleting community member messages: " & errMsg) + + var deletedMessages = initTable[string, seq[string]]() + if response.result.contains("deletedMessages"): + let deletedMessagesObj = response.result["deletedMessages"] + for chatId, messageIdsArrayJson in deletedMessagesObj: + if not deletedMessages.hasKey(chatId): + deletedMessages[chatId] = @[] + for messageId in messageIdsArrayJson: + deletedMessages[chatId].add(messageId.getStr()) + + self.handleDeletedMessagesUpdate(deletedMessages, communityId) + + except Exception as e: + error "error: ", procName="deleteCommunityMemberMessages", errName = e.name, errDesription = e.msg \ No newline at end of file diff --git a/src/backend/messages.nim b/src/backend/messages.nim index c8f0a90f9a..0a2c6d128a 100644 --- a/src/backend/messages.nim +++ b/src/backend/messages.nim @@ -87,3 +87,24 @@ proc getTextURLsToUnfurl*(text: string): RpcResponse[JsonNode] = proc unfurlUrls*(urls: seq[string]): RpcResponse[JsonNode] = let payload = %*[urls] result = callPrivateRPC("unfurlURLs".prefix, payload) + +proc getCommunityMemberAllMessages*(communityId: string, memberPublicKey: string): RpcResponse[JsonNode] = + let payload = %* [{"communityId": communityId, "memberPublicKey": memberPublicKey}] + result = callPrivateRPC("getCommunityMemberAllMessages".prefix, payload) + + +proc deleteCommunityMemberMessages*(communityId: string, memberPubKey: string, messageId: string, chatId: string): RpcResponse[JsonNode] = + var messages: seq[JsonNode] = @[] + if messageId != "" and chatId != "": + messages.add(%*{ + "id": messageId, + "chat_id": chatId, + }) + + let payload = %* [{ + "communityId": communityId, + "memberPubKey": memberPubKey, + "messages": messages, + "deleteAll": messages.len() == 0 + }] + result = callPrivateRPC("deleteCommunityMemberMessages".prefix, payload) \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index e50bb75001..07a37886f7 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -329,6 +329,10 @@ QtObject { chatCommunitySectionModule.removeUserFromCommunity(pubKey); } + function loadCommunityMemberMessages(communityId, pubKey) { + chatCommunitySectionModule.loadCommunityMemberMessages(communityId, pubKey); + } + function banUserFromCommunity(pubKey, deleteAllMessages) { chatCommunitySectionModule.banUserFromCommunity(pubKey, deleteAllMessages); } diff --git a/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml index 80e896dd12..aeb931521c 100644 --- a/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml @@ -26,6 +26,7 @@ SettingsPage { signal unbanUserClicked(string id) signal acceptRequestToJoin(string id) signal declineRequestToJoin(string id) + signal viewMemberMessagesClicked(string pubKey, string displayName) function goTo(tab: int) { if(root.contentItem) { @@ -127,6 +128,8 @@ SettingsPage { kickBanPopup.userId = id kickBanPopup.open() } + + onViewMemberMessagesClicked: root.viewMemberMessagesClicked(pubKey, displayName) } MembersTabPanel { @@ -182,6 +185,7 @@ SettingsPage { Layout.fillHeight: true onUnbanUserClicked: root.unbanUserClicked(id) + onViewMemberMessagesClicked: root.viewMemberMessagesClicked(pubKey, displayName) } } } diff --git a/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml b/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml index b29a0a8f26..772f199b2a 100644 --- a/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/MembersTabPanel.qml @@ -29,6 +29,7 @@ Item { signal kickUserClicked(string id, string name) signal banUserClicked(string id, string name) signal unbanUserClicked(string id) + signal viewMemberMessagesClicked(string pubKey, string displayName) signal acceptRequestToJoin(string id) signal declineRequestToJoin(string id) @@ -94,6 +95,10 @@ Item { readonly property bool tabIsShowingRejectButton: root.panelType === MembersTabPanel.TabType.PendingRequests readonly property bool tabIsShowingAcceptButton: root.panelType === MembersTabPanel.TabType.PendingRequests || root.panelType === MembersTabPanel.TabType.DeclinedRequests + readonly property bool tabIsShowingViewMessagesButton: model.membershipRequestState !== Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete && + (root.panelType === MembersTabPanel.TabType.AllMembers || + root.panelType === MembersTabPanel.TabType.BannedMembers) + // Request states readonly property bool isPending: model.membershipRequestState === Constants.CommunityMembershipRequestState.Pending @@ -130,6 +135,7 @@ Item { } } readonly property bool showOnHover: isHovered && ctaAllowed + readonly property bool canDeleteMessages: itsMe || model.memberRole !== Constants.memberRole.owner /// Button visibility /// readonly property bool acceptButtonVisible: tabIsShowingAcceptButton && (isPending || isRejected || isRejectedPending || isAcceptedPending) && showOnHover @@ -141,6 +147,9 @@ Item { readonly property bool kickPendingButtonVisible: tabIsShowingKickBanButtons && isKickPending readonly property bool banPendingButtonVisible: tabIsShowingKickBanButtons && isBanPending readonly property bool unbanButtonVisible: tabIsShowingUnbanButton && isBanned && showOnHover + readonly property bool viewMessagesButtonVisible: tabIsShowingViewMessagesButton && showOnHover + readonly property bool messagesDeletedTextVisible: showOnHover && + model.membershipRequestState === Constants.CommunityMembershipRequestState.BannedWithAllMessagesDelete /// Pending states /// readonly property bool isPendingState: isAcceptedPending || isRejectedPending || isBanPending || isUnbanPending || isKickPending @@ -179,6 +188,24 @@ Item { enabled: pendingText.visible } }, + + StatusBaseText { + text: qsTr("Messages deleted") + color: Theme.palette.baseColor1 + anchors.verticalCenter: parent.verticalCenter + visible: messagesDeletedTextVisible + }, + + StatusButton { + id: viewMessages + anchors.verticalCenter: parent.verticalCenter + objectName: "MemberListItem_ViewMessages" + text: qsTr("View Messages") + visible: viewMessagesButtonVisible + size: StatusBaseButton.Size.Small + onClicked: root.viewMemberMessagesClicked(model.pubKey, model.displayName) + }, + StatusButton { id: kickButton anchors.verticalCenter: parent.verticalCenter @@ -204,6 +231,8 @@ Item { anchors.verticalCenter: parent.verticalCenter visible: unbanButtonVisible text: qsTr("Unban") + type: StatusBaseButton.Type.Danger + size: StatusBaseButton.Size.Small onClicked: root.unbanUserClicked(model.pubKey) }, diff --git a/ui/app/AppLayouts/Communities/popups/CommunityMemberMessagesPopup.qml b/ui/app/AppLayouts/Communities/popups/CommunityMemberMessagesPopup.qml new file mode 100644 index 0000000000..3a38d8ea92 --- /dev/null +++ b/ui/app/AppLayouts/Communities/popups/CommunityMemberMessagesPopup.qml @@ -0,0 +1,143 @@ +import QtQuick 2.14 +import QtQuick.Controls 2.14 +import QtQuick.Layouts 1.14 +import QtQml.Models 2.14 +import QtGraphicalEffects 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Popups 0.1 +import StatusQ.Popups.Dialog 0.1 + +import utils 1.0 +import shared.views.chat 1.0 + +StatusDialog { + id: root + + property var store + property var chatCommunitySectionModule + property var memberMessagesModel: chatCommunitySectionModule.memberMessagesModel + property string memberPubKey: "" + property string displayName: "" + + + width: 800 + + title: qsTr("%1 messages").arg(root.displayName) + subtitle: qsTr("%n message(s)", "", root.memberMessagesModel.count) + + ColumnLayout { + anchors.fill: parent + id: column + + StatusBaseText { + visible: communityMemberMessageListView.count === 0 + text: qsTr("No messages") + Layout.alignment: Qt.AlignCenter + verticalAlignment: Text.AlignVCenter + color: Style.current.secondaryText + Layout.topMargin: 40 + Layout.bottomMargin: 40 + } + + StatusListView { + id: communityMemberMessageListView + model: root.memberMessagesModel + Layout.fillWidth: true + Layout.fillHeight: count + implicitHeight: contentHeight + + delegate: Item { + id: messageDelegate + + width: ListView.view.width + height: messageItem.height + + MessageView { + id: messageItem + + width: parent.width + + rootStore: root.store + chatCommunitySectionModule: root.chatCommunitySectionModule + messageStore: root.memberMessagesModel + + messageId: model.id + chatId: model.chatId + responseToMessageWithId: model.responseToMessageWithId + amIChatAdmin: true + senderId: model.senderId + senderDisplayName: model.senderDisplayName + senderOptionalName: model.senderOptionalName + senderIsEnsVerified: model.senderEnsVerified + senderIsAdded: model.senderIsAdded + senderIcon: model.senderIcon + senderColorHash: model.senderColorHash + senderTrustStatus: model.senderTrustStatus + amISender: model.amISender + messageText: model.messageText + messageImage: model.messageImage + messageTimestamp: model.timestamp + messageOutgoingStatus: model.outgoingStatus + messageContentType: model.contentType + pinnedMessage: model.pinned + messagePinnedBy: model.pinnedBy + sticker: model.sticker + stickerPack: model.stickerPack + linkPreviewModel: model.linkPreviewModel + links: model.links + transactionParams: model.transactionParameters + quotedMessageText: model.quotedMessageParsedText + quotedMessageFrom: model.quotedMessageFrom + quotedMessageContentType: model.quotedMessageContentType + quotedMessageDeleted: model.quotedMessageDeleted + quotedMessageAuthorDetailsName: model.quotedMessageAuthorName + quotedMessageAuthorDetailsDisplayName: model.quotedMessageAuthorDisplayName + quotedMessageAuthorDetailsThumbnailImage: model.quotedMessageAuthorThumbnailImage + quotedMessageAuthorDetailsEnsVerified: model.quotedMessageAuthorEnsVerified + quotedMessageAuthorDetailsIsContact: model.quotedMessageAuthorIsContact + quotedMessageAuthorDetailsColorHash: model.quotedMessageAuthorColorHash + bridgeName: model.bridgeName + isViewMemberMessagesePopup: true + shouldRepeatHeader: true + } + + MouseArea { + id: mouseArea + acceptedButtons: Qt.LeftButton + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + + onClicked: { + root.chatCommunitySectionModule.openCommunityChatAndScrollToMessage(model.chatId, model.id) + Global.switchToCommunityChannelsView(root.chatCommunitySectionModule.getMySectionId()) + root.chatCommunitySectionModule.openCommunityChatAndScrollToMessage(model.chatId, model.id) + root.closed() + } + } + } + } + } + + footer: StatusDialogFooter { + id: footer + rightButtons: ObjectModel { + StatusFlatButton { + text: qsTr("Delete all messages by %1").arg(root.displayName) + enabled: communityMemberMessageListView.count > 0 + type: StatusBaseButton.Type.Danger + onClicked: { + root.chatCommunitySectionModule.deleteCommunityMemberMessages(root.memberPubKey, "", "") + } + borderColor: "transparent" + } + + StatusButton { + text: qsTr("Done") + onClicked: root.close() + } + } + } +} diff --git a/ui/app/AppLayouts/Communities/popups/qmldir b/ui/app/AppLayouts/Communities/popups/qmldir index 53640c5169..02e601f109 100644 --- a/ui/app/AppLayouts/Communities/popups/qmldir +++ b/ui/app/AppLayouts/Communities/popups/qmldir @@ -26,3 +26,4 @@ TokenMasterActionPopup 1.0 TokenMasterActionPopup.qml TokenPermissionsPopup 1.0 TokenPermissionsPopup.qml TransferOwnershipPopup 1.0 TransferOwnershipPopup.qml TransferOwnershipAlertPopup 1.0 TransferOwnershipAlertPopup.qml +CommunityMemberMessagesPopup 1.0 CommunityMemberMessagesPopup.qml diff --git a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml index 0fbbcc9768..afb50f51f8 100644 --- a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml @@ -252,7 +252,7 @@ StatusSectionLayout { mintPanel.openNewTokenForm(false/*Collectible owner token*/) } - onShardIndexEdited: if (root.community.shardIndex != shardIndex) { + onShardIndexEdited: if (root.community.shardIndex !== shardIndex) { root.chatCommunitySectionModule.setCommunityShard(shardIndex) } } @@ -277,6 +277,10 @@ StatusSectionLayout { onUnbanUserClicked: root.rootStore.unbanUserFromCommunity(id) onAcceptRequestToJoin: root.rootStore.acceptRequestToJoinCommunity(id, root.community.id) onDeclineRequestToJoin: root.rootStore.declineRequestToJoinCommunity(id, root.community.id) + onViewMemberMessagesClicked: { + root.rootStore.loadCommunityMemberMessages(root.community.id, pubKey) + Global.openCommunityMemberMessagesPopupRequested(root.rootStore, root.chatCommunitySectionModule, pubKey, displayName) + } } PermissionsSettingsPanel { diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index f191d6da55..d4f35a890d 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -1413,6 +1413,15 @@ Item { } } + Connections { + target: Global + function onSwitchToCommunityChannelsView(communityId: string) { + if (communityId !== model.id) + return + chatLayoutComponent.currentIndex = 0 + } + } + sendModalPopup: sendModal emojiPopup: statusEmojiPopup.item stickersPopup: statusStickersPopupLoader.item diff --git a/ui/app/mainui/Popups.qml b/ui/app/mainui/Popups.qml index 9780a2ce92..08be0fb504 100644 --- a/ui/app/mainui/Popups.qml +++ b/ui/app/mainui/Popups.qml @@ -86,6 +86,7 @@ QtObject { Global.openFirstTokenReceivedPopup.connect(openFirstTokenReceivedPopup) Global.openConfirmHideAssetPopup.connect(openConfirmHideAssetPopup) Global.openConfirmHideCollectiblePopup.connect(openConfirmHideCollectiblePopup) + Global.openCommunityMemberMessagesPopupRequested.connect(openCommunityMemberMessagesPopup) } property var currentPopup @@ -362,6 +363,15 @@ QtObject { openPopup(confirmHideCollectiblePopup, { collectibleSymbol, collectibleName, collectibleImage, isCommunityToken }) } + function openCommunityMemberMessagesPopup(store, chatCommunitySectionModule, memberPubKey, displayName) { + openPopup(communityMemberMessagesPopup, { + store: store, + chatCommunitySectionModule: chatCommunitySectionModule, + memberPubKey: memberPubKey, + displayName: displayName + }) + } + readonly property list _components: [ Component { id: removeContactConfirmationDialog @@ -1135,6 +1145,12 @@ QtObject { "") } } + }, + Component { + id: communityMemberMessagesPopup + CommunityMemberMessagesPopup { + onClosed: destroy() + } } ] } diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index b710e506a4..9519721541 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -24,6 +24,7 @@ Loader { property var usersStore property var contactsStore property var chatContentModule + property var chatCommunitySectionModule property string channelEmoji @@ -96,6 +97,7 @@ Loader { // External behavior changers property bool isInPinnedPopup: false // The pinned popup limits the number of buttons shown + property bool isViewMemberMessagesePopup: false // The view member messages popup limits the number of buttons property bool disableHover: false // Used to force the HoverHandler to be active (useful for messages in popups) property bool placeholderMessage: false @@ -261,7 +263,10 @@ Loader { property string activeMessage readonly property bool isMessageActive: d.activeMessage === root.messageId - readonly property bool addReactionAllowed: !root.isInPinnedPopup && root.chatContentModule.chatDetails.canPostReactions + + readonly property bool addReactionAllowed: !root.isInPinnedPopup && + root.chatContentModule.chatDetails.canPostReactions && + !root.isViewMemberMessagesePopup function nextMessageHasHeader() { if(!root.nextMessageAsJsonObj) { @@ -568,7 +573,7 @@ Loader { Layout.bottomMargin: 16 messageTimestamp: root.messageTimestamp previousMessageTimestamp: root.prevMessageIndex === -1 ? 0 : root.prevMessageTimestamp - visible: text !== "" && !root.isInPinnedPopup + visible: text !== "" && !root.isInPinnedPopup && !root.isViewMemberMessagesePopup } StatusMessage { @@ -910,7 +915,8 @@ Loader { quickActions: [ Loader { - active: d.addReactionAllowed && delegate.hovered + active: d.addReactionAllowed && delegate.hovered && !root.isViewMemberMessagesePopup + visible: active sourceComponent: StatusFlatRoundButton { width: d.chatButtonSize @@ -925,7 +931,7 @@ Loader { }, Loader { active: !root.isInPinnedPopup && delegate.hovered && !delegate.hideQuickActions - && root.rootStore.permissionsStore.viewAndPostCriteriaMet + && !root.isViewMemberMessagesePopup && root.rootStore.permissionsStore.viewAndPostCriteriaMet visible: active sourceComponent: StatusFlatRoundButton { objectName: "replyToMessageButton" @@ -941,7 +947,7 @@ Loader { }, Loader { active: !root.isInPinnedPopup && root.isText && !root.editModeOn && root.amISender && delegate.hovered && !delegate.hideQuickActions - && root.rootStore.permissionsStore.viewAndPostCriteriaMet + && !root.isViewMemberMessagesePopup && root.rootStore.permissionsStore.viewAndPostCriteriaMet visible: active sourceComponent: StatusFlatRoundButton { objectName: "editMessageButton" @@ -969,6 +975,10 @@ Loader { if (!root.rootStore.permissionsStore.viewAndPostCriteriaMet) return false; + if (root.isViewMemberMessagesePopup) { + return false + } + const chatType = root.messageStore.chatType; const pinMessageAllowedForMembers = root.messageStore.isPinMessageAllowedForMembers @@ -1007,7 +1017,7 @@ Loader { } }, Loader { - active: !root.editModeOn && delegate.hovered && !delegate.hideQuickActions + active: !root.editModeOn && delegate.hovered && !delegate.hideQuickActions && !root.isViewMemberMessagesePopup visible: active sourceComponent: StatusFlatRoundButton { objectName: "markAsUnreadButton" @@ -1048,9 +1058,9 @@ Loader { icon.name: "delete" type: StatusFlatRoundButton.Type.Tertiary tooltip.text: qsTr("Delete") - onClicked: { - messageStore.warnAndDeleteMessage(root.messageId) - } + onClicked: root.isViewMemberMessagesePopup + ? root.chatCommunitySectionModule.deleteCommunityMemberMessages(root.senderId, root.messageId, root.chatId) + : messageStore.warnAndDeleteMessage(root.messageId) } } ] diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index a5c9b74ecc..de236e7130 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -81,6 +81,7 @@ QtObject { signal openSendModal(string address) signal switchToCommunity(string communityId) signal switchToCommunitySettings(string communityId) + signal switchToCommunityChannelsView(string communityId) signal createCommunityPopupRequested(bool isDiscordImport) signal importCommunityPopupRequested() signal communityIntroPopupRequested(string communityId, string name, string introMessage, @@ -101,6 +102,7 @@ QtObject { signal openDeleteSavedAddressesPopup(var params) signal openShowQRPopup(var params) signal openSavedAddressActivityPopup(var params) + signal openCommunityMemberMessagesPopupRequested(var store, var chatCommunitySectionModule, var memberPubKey, var displayName) function openProfilePopup(publicKey, parentPopup, cb) { root.openProfilePopupRequested(publicKey, parentPopup, cb) diff --git a/vendor/status-go b/vendor/status-go index 78bf40994a..ad342c8887 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 78bf40994a482e5bf46c4c67ae4deb16065581f8 +Subproject commit ad342c8887b0cc6fd91cac73919f45ac951efca9