From 12569d795f95a116e91cd33dd1663240b085ad62 Mon Sep 17 00:00:00 2001 From: Mykhailo Prakhov Date: Thu, 22 Feb 2024 12:01:01 +0100 Subject: [PATCH] feat(@desktop/community): allow owner delete all messages during the ban and ban/unban AC notifications (#13653) --- .../core/signals/remote_signals/messages.nim | 16 +++- .../chat_section/chat_content/controller.nim | 14 +++- .../chat_content/input_area/controller.nim | 28 +++---- .../chat_content/messages/controller.nim | 15 ++-- .../chat_content/messages/io_interface.nim | 5 +- .../chat_content/messages/module.nim | 32 ++++---- .../modules/main/chat_section/controller.nim | 4 +- .../main/chat_section/io_interface.nim | 2 +- src/app/modules/main/chat_section/module.nim | 4 +- src/app/modules/main/chat_section/view.nim | 4 +- .../modules/shared_models/message_model.nim | 4 +- .../activity_center/dto/notification.nim | 10 ++- src/app_service/service/chat/dto/chat.nim | 4 +- src/app_service/service/chat/service.nim | 21 ++--- .../service/community/async_tasks.nim | 6 +- src/app_service/service/community/service.nim | 3 +- src/app_service/service/message/service.nim | 53 ++++++++----- src/backend/chat.nim | 6 +- src/backend/communities.nim | 5 +- ui/app/AppLayouts/Chat/stores/RootStore.qml | 4 +- .../panels/MembersSettingsPanel.qml | 10 +-- .../Communities/popups/KickBanPopup.qml | 55 ++++++++++--- .../Communities/views/CommunityColumnView.qml | 2 + .../views/CommunitySettingsView.qml | 2 +- .../popups/ActivityCenterPopup.qml | 30 +++++++ .../stores/ActivityCenterStore.qml | 4 +- .../ActivityNotificationCommunityBanUnban.qml | 78 +++++++++++++++++++ vendor/status-go | 2 +- 28 files changed, 305 insertions(+), 118 deletions(-) create mode 100644 ui/app/mainui/activitycenter/views/ActivityNotificationCommunityBanUnban.qml diff --git a/src/app/core/signals/remote_signals/messages.nim b/src/app/core/signals/remote_signals/messages.nim index 6065154a6c..5701be966d 100644 --- a/src/app/core/signals/remote_signals/messages.nim +++ b/src/app/core/signals/remote_signals/messages.nim @@ -1,4 +1,4 @@ -import json, chronicles +import json, chronicles, tables import base @@ -28,7 +28,8 @@ type MessageSignal* = ref object of Signal membershipRequests*: seq[CommunityMembershipRequestDto] activityCenterNotifications*: seq[ActivityCenterNotificationDto] statusUpdates*: seq[StatusUpdateDto] - deletedMessages*: seq[RemovedMessageDto] + removedMessages*: seq[RemovedMessageDto] + deletedMessages*: Table[string, seq[string]] removedChats*: seq[string] currentStatus*: seq[StatusUpdateDto] settings*: seq[SettingsFieldDto] @@ -117,7 +118,15 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal = if e.contains("removedMessages"): for jsonRemovedMessage in e["removedMessages"]: - signal.deletedMessages.add(jsonRemovedMessage.toRemovedMessageDto()) + signal.removedMessages.add(jsonRemovedMessage.toRemovedMessageDto()) + + if e.contains("deletedMessages"): + let deletedMessagesObj = e["deletedMessages"] + for chatId, messageIdsArrayJson in deletedMessagesObj: + if not signal.deletedMessages.hasKey(chatId): + signal.deletedMessages[chatId] = @[] + for messageId in messageIdsArrayJson: + signal.deletedMessages[chatId].add(messageId.getStr()) if e.contains("removedChats"): for removedChatID in e["removedChats"]: @@ -161,6 +170,5 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal = if e.contains("updatedProfileShowcases"): for jsonProfileShowcase in e["updatedProfileShowcases"]: signal.updatedProfileShowcases.add(jsonProfileShowcase.toProfileShowcaseDto()) - result = signal diff --git a/src/app/modules/main/chat_section/chat_content/controller.nim b/src/app/modules/main/chat_section/chat_content/controller.nim index d8a042b6c3..2f91617fc2 100644 --- a/src/app/modules/main/chat_section/chat_content/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/controller.nim @@ -1,5 +1,4 @@ -import NimQml -import json +import NimQml, tables, json import io_interface import ../../../../../app_service/service/settings/service as settings_service @@ -153,13 +152,20 @@ proc init*(self: Controller) = self.delegate.onMutualContactChanged() self.delegate.onContactDetailsUpdated(args.contactId) - self.events.on(SIGNAL_MESSAGE_DELETION) do(e: Args): - let args = MessageDeletedArgs(e) + self.events.on(SIGNAL_MESSAGE_REMOVED) do(e: Args): + let args = MessageRemovedArgs(e) if(self.chatId != args.chatId): return # remove from pinned messages model self.delegate.onUnpinMessage(args.messageId) + self.events.on(SIGNAL_MESSAGES_DELETED) do(e: Args): + let args = MessagesDeletedArgs(e) + if self.chatId in args.deletedMessages: + for deletedMessage in args.deletedMessages[self.chatId]: + # delete from pinned messages model + self.delegate.onUnpinMessage(deletedMessage) + self.events.on(SIGNAL_COMMUNITY_CHANNEL_EDITED) do(e:Args): let args = CommunityChatArgs(e) if(args.chat.communityId != self.sectionId or args.chat.id != self.chatId): diff --git a/src/app/modules/main/chat_section/chat_content/input_area/controller.nim b/src/app/modules/main/chat_section/chat_content/input_area/controller.nim index 61e6e30cfe..9961e4e411 100644 --- a/src/app/modules/main/chat_section/chat_content/input_area/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/input_area/controller.nim @@ -131,7 +131,7 @@ proc getChatId*(self: Controller): string = proc belongsToCommunity*(self: Controller): bool = return self.belongsToCommunity - + proc setLinkPreviewEnabledForThisMessage*(self: Controller, enabled: bool) = self.linkPreviewCurrentMessageSetting = if enabled: UrlUnfurlingMode.Enabled else: UrlUnfurlingMode.Disabled self.delegate.setAskToEnableLinkPreview(false) @@ -142,18 +142,18 @@ proc resetLinkPreviews(self: Controller) = self.linkPreviewCurrentMessageSetting = self.linkPreviewPersistentSetting self.delegate.setAskToEnableLinkPreview(false) -proc sendImages*(self: Controller, - imagePathsAndDataJson: string, - msg: string, - replyTo: string, +proc sendImages*(self: Controller, + imagePathsAndDataJson: string, + msg: string, + replyTo: string, preferredUsername: string = "", linkPreviews: seq[LinkPreview]): string = self.resetLinkPreviews() self.chatService.sendImages( - self.chatId, - imagePathsAndDataJson, - msg, - replyTo, + self.chatId, + imagePathsAndDataJson, + msg, + replyTo, preferredUsername, linkPreviews ) @@ -165,10 +165,10 @@ proc sendChatMessage*(self: Controller, preferredUsername: string = "", linkPreviews: seq[LinkPreview]) = self.resetLinkPreviews() - self.chatService.sendChatMessage(self.chatId, - msg, - replyTo, - contentType, + self.chatService.sendChatMessage(self.chatId, + msg, + replyTo, + contentType, preferredUsername, linkPreviews ) @@ -288,7 +288,7 @@ proc asyncUnfurlUrls(self: Controller, urls: seq[string]) = proc asyncUnfurlUnknownUrls(self: Controller, urls: seq[string]) = let newUrls = self.linkPreviewCache.unknownUrls(urls) self.asyncUnfurlUrls(newUrls) - + proc linkPreviewsFromCache*(self: Controller, urls: seq[string]): Table[string, LinkPreview] = return self.linkPreviewCache.linkPreviews(urls) diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller.nim b/src/app/modules/main/chat_section/chat_content/messages/controller.nim index 144fbedbd3..da80f571a8 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller.nim @@ -1,4 +1,4 @@ -import chronicles, uuids, times +import chronicles, uuids, times, tables import io_interface import ../../../../../../app/global/global_singleton @@ -182,11 +182,16 @@ proc init*(self: Controller) = self.events.on(SIGNAL_LOGGEDIN_USER_IMAGE_CHANGED) do(e: Args): self.delegate.updateContactDetails(singletonInstance.userProfile.getPubKey()) - self.events.on(SIGNAL_MESSAGE_DELETION) do(e: Args): - let args = MessageDeletedArgs(e) + self.events.on(SIGNAL_MESSAGE_REMOVED) do(e: Args): + let args = MessageRemovedArgs(e) if(self.chatId != args.chatId): return - self.delegate.onMessageDeleted(args.messageId, args.deletedBy) + self.delegate.onMessageRemoved(args.messageId, args.deletedBy) + + self.events.on(SIGNAL_MESSAGES_DELETED) do(e: Args): + let args = MessagesDeletedArgs(e) + if self.chatId in args.deletedMessages: + self.delegate.onMessagesDeleted(args.deletedMessages[self.chatId]) self.events.on(SIGNAL_MESSAGE_EDITED) do(e: Args): let args = MessageEditedArgs(e) @@ -296,7 +301,7 @@ proc deleteMessage*(self: Controller, messageId: string) = proc editMessage*(self: Controller, messageId: string, contentType: int, updatedMsg: string) = self.messageService.editMessage(messageId, contentType, updatedMsg) - + proc getSearchedMessageId*(self: Controller): string = return self.searchedMessageId diff --git a/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim b/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim index 012c329bfe..d536206458 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/io_interface.nim @@ -123,7 +123,10 @@ method getNumberOfPinnedMessages*(self: AccessInterface): int {.base.} = method deleteMessage*(self: AccessInterface, messageId: string) {.base.} = raise newException(ValueError, "No implementation available") -method onMessageDeleted*(self: AccessInterface, messageId, deletedBy: string) {.base.} = +method onMessageRemoved*(self: AccessInterface, messageId, removedBy: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onMessagesDeleted*(self: AccessInterface, messageIds: seq[string]) {.base.} = raise newException(ValueError, "No implementation available") method editMessage*(self: AccessInterface, messageId: string, contentType: int, updatedMsg: string) {.base.} = 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 ca57cc2ccd..1acda5a774 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 @@ -378,7 +378,7 @@ proc currentUserWalletContainsAddress(self: Module, address: string): bool = return false method reevaluateViewLoadingState*(self: Module) = - self.view.setLoading(not self.initialMessagesLoaded or + self.view.setLoading(not self.initialMessagesLoaded or not self.firstUnseenMessageState.initialized or self.firstUnseenMessageState.fetching or self.view.getMessageSearchOngoing()) @@ -564,16 +564,20 @@ method updateContactDetails*(self: Module, contactId: string) = method deleteMessage*(self: Module, messageId: string) = self.controller.deleteMessage(messageId) -method onMessageDeleted*(self: Module, messageId, deletedBy: string) = - var deletedByValue = deletedBy - if deletedBy == "": - # deletedBy is empty if it was deleted by the sender +method onMessageRemoved*(self: Module, messageId, removedBy: string) = + var removedByValue = removedBy + if removedBy == "": + # removedBy is empty if it was removed by the sender let messageItem = self.view.model().getItemWithMessageId(messageId) if messageItem.id == "": return - deletedByValue = messageItem.senderId - var deletedByContactDetails = self.controller.getContactDetails(deletedByValue) - self.view.model().messageDeleted(messageId, deletedByValue, deletedByContactDetails) + removedByValue = messageItem.senderId + var removedByContactDetails = self.controller.getContactDetails(removedByValue) + self.view.model().messageRemoved(messageId, removedByValue, removedByContactDetails) + +method onMessagesDeleted*(self: Module, messageIds: seq[string]) = + for messageId in messageIds: + self.view.model().removeItem(messageId) method editMessage*(self: Module, messageId: string, contentType: int, updatedMsg: string) = self.controller.editMessage(messageId, contentType, updatedMsg) @@ -730,7 +734,7 @@ proc updateItemsByAlbum(self: Module, items: var seq[Item], message: MessageDto) for j in 0 ..< item.albumMessageIds.len: if item.albumMessageIds[j] == message.id: return true - + var albumImages = item.albumMessageImages var albumMessagesIds = item.albumMessageIds albumMessagesIds.add(message.id) @@ -770,21 +774,21 @@ proc updateLinkPreviewsContacts(self: Module, item: Item, requestFromMailserver: if not requestFromMailserver: continue - + debug "updateLinkPreviewsContacts: contact not found, requesting from mailserver", contactId item.linkPreviewModel.onContactDataRequested(contactId) self.controller.requestContactInfo(contactId) - + proc updateLinkPreviewsCommunities(self: Module, item: Item, requestFromMailserver: bool) = for communityId, url in item.linkPreviewModel.getCommunityLinks().pairs: let community = self.controller.getCommunityById(communityId) - + if community.id != "": item.linkPreviewModel.setCommunityInfo(community) - + if not requestFromMailserver: continue - + debug "updateLinkPreviewsCommunites: requesting from mailserver", communityId let urlData = self.controller.parseSharedUrl(url) item.linkPreviewModel.onCommunityInfoRequested(communityId) diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 0192fcf1d7..5d3d723834 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -600,8 +600,8 @@ proc leaveCommunity*(self: Controller) = proc removeUserFromCommunity*(self: Controller, pubKey: string) = self.communityService.asyncRemoveUserFromCommunity(self.sectionId, pubKey) -proc banUserFromCommunity*(self: Controller, pubKey: string) = - self.communityService.asyncBanUserFromCommunity(self.sectionId, pubKey) +proc banUserFromCommunity*(self: Controller, pubKey: string, deleteAllMessages: bool) = + self.communityService.asyncBanUserFromCommunity(self.sectionId, pubKey, deleteAllMessages) proc unbanUserFromCommunity*(self: Controller, pubKey: string) = self.communityService.asyncUnbanUserFromCommunity(self.sectionId, pubKey) diff --git a/src/app/modules/main/chat_section/io_interface.nim b/src/app/modules/main/chat_section/io_interface.nim index 2a42385615..eb8bb757a6 100644 --- a/src/app/modules/main/chat_section/io_interface.nim +++ b/src/app/modules/main/chat_section/io_interface.nim @@ -279,7 +279,7 @@ method leaveCommunity*(self: AccessInterface) {.base.} = method removeUserFromCommunity*(self: AccessInterface, pubKey: string) {.base.} = raise newException(ValueError, "No implementation available") -method banUserFromCommunity*(self: AccessInterface, pubKey: string) {.base.} = +method banUserFromCommunity*(self: AccessInterface, pubKey: string, deleteAllMessages: bool) {.base.} = raise newException(ValueError, "No implementation available") method editCommunity*(self: AccessInterface, name: string, description, introMessage, outroMessage: string, diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index fbbd06414e..29e566de6a 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -1097,8 +1097,8 @@ method leaveCommunity*(self: Module) = method removeUserFromCommunity*(self: Module, pubKey: string) = self.controller.removeUserFromCommunity(pubKey) -method banUserFromCommunity*(self: Module, pubKey: string) = - self.controller.banUserFromCommunity(pubkey) +method banUserFromCommunity*(self: Module, pubKey: string, deleteAllMessages: bool) = + self.controller.banUserFromCommunity(pubkey, deleteAllMessages) method unbanUserFromCommunity*(self: Module, pubKey: string) = self.controller.unbanUserFromCommunity(pubkey) diff --git a/src/app/modules/main/chat_section/view.nim b/src/app/modules/main/chat_section/view.nim index 1cc6a0eb63..9954e79909 100644 --- a/src/app/modules/main/chat_section/view.nim +++ b/src/app/modules/main/chat_section/view.nim @@ -298,8 +298,8 @@ QtObject: proc removeUserFromCommunity*(self: View, pubKey: string) {.slot.} = self.delegate.removeUserFromCommunity(pubKey) - proc banUserFromCommunity*(self: View, pubKey: string) {.slot.} = - self.delegate.banUserFromCommunity(pubKey) + proc banUserFromCommunity*(self: View, pubKey: string, deleteAllMessages: bool) {.slot.} = + self.delegate.banUserFromCommunity(pubKey, deleteAllMessages) proc editCommunity*(self: View, name: string, description: string, introMessage: string, outroMessage: string, access: int, color: string, tags: string, logoJsonData: string, bannerJsonData: string, historyArchiveSupportEnabled: bool, diff --git a/src/app/modules/shared_models/message_model.nim b/src/app/modules/shared_models/message_model.nim index d4790dad11..e605d6f5a3 100644 --- a/src/app/modules/shared_models/message_model.nim +++ b/src/app/modules/shared_models/message_model.nim @@ -498,7 +498,7 @@ QtObject: self.countChanged() self.updateMessagesWhenQuotedMessageDeleted(messageId) - proc messageDeleted*(self: Model, messageId: string, deletedBy: string, deletedByContactDetails: ContactDetails) = + proc messageRemoved*(self: Model, messageId: string, deletedBy: string, deletedByContactDetails: ContactDetails) = let i = self.findIndexForMessageId(messageId) if(i == -1): return @@ -846,7 +846,7 @@ QtObject: albumImages.add(messageImage) item.albumMessageImages = albumImages item.albumMessageIds = albumMessagesIds - + let index = self.createIndex(i, 0, nil) defer: index.delete self.dataChanged(index, index, @[ModelRole.AlbumMessageImages.int]) diff --git a/src/app_service/service/activity_center/dto/notification.nim b/src/app_service/service/activity_center/dto/notification.nim index e64ab91118..89300f91b5 100644 --- a/src/app_service/service/activity_center/dto/notification.nim +++ b/src/app_service/service/activity_center/dto/notification.nim @@ -30,6 +30,8 @@ type ActivityCenterNotificationType* {.pure.}= enum ShareAccounts = 18 CommunityTokenReceived = 19 FirstCommunityTokenReceived = 20 + CommunityBanned = 21 + CommunityUnbanned = 22 type ActivityCenterGroup* {.pure.}= enum All = 0, @@ -174,7 +176,9 @@ proc activityCenterNotificationTypesByGroup*(group: ActivityCenterGroup) : seq[i ActivityCenterNotificationType.OwnershipLost.int, ActivityCenterNotificationType.ShareAccounts.int, ActivityCenterNotificationType.CommunityTokenReceived.int, - ActivityCenterNotificationType.FirstCommunityTokenReceived.int + ActivityCenterNotificationType.FirstCommunityTokenReceived.int, + ActivityCenterNotificationType.CommunityBanned.int, + ActivityCenterNotificationType.CommunityUnbanned.int ] of ActivityCenterGroup.Mentions: return @[ActivityCenterNotificationType.Mention.int] @@ -186,7 +190,9 @@ proc activityCenterNotificationTypesByGroup*(group: ActivityCenterGroup) : seq[i ActivityCenterNotificationType.CommunityInvitation.int, ActivityCenterNotificationType.CommunityRequest.int, ActivityCenterNotificationType.CommunityMembershipRequest.int, - ActivityCenterNotificationType.CommunityKicked.int + ActivityCenterNotificationType.CommunityKicked.int, + ActivityCenterNotificationType.CommunityBanned.int, + ActivityCenterNotificationType.CommunityUnbanned.int ] of ActivityCenterGroup.Admin: return @[ActivityCenterNotificationType.CommunityMembershipRequest.int] diff --git a/src/app_service/service/chat/dto/chat.nim b/src/app_service/service/chat/dto/chat.nim index 0ef321556f..544e3d8bc3 100644 --- a/src/app_service/service/chat/dto/chat.nim +++ b/src/app_service/service/chat/dto/chat.nim @@ -240,9 +240,9 @@ proc toChannelMember*(jsonObj: JsonNode, memberId: string, joined: bool): ChatMe if(jsonObj.getProp("roles", rolesObj)): for roleObj in rolesObj: roles.add(roleObj.getInt) - + result.role = MemberRole.None - if roles.contains(MemberRole.Owner.int): + if roles.contains(MemberRole.Owner.int): result.role = MemberRole.Owner elif roles.contains(MemberRole.Admin.int): result.role = MemberRole.Admin diff --git a/src/app_service/service/chat/service.nim b/src/app_service/service/chat/service.nim index 4eca16c031..ad8486a9d2 100644 --- a/src/app_service/service/chat/service.nim +++ b/src/app_service/service/chat/service.nim @@ -108,7 +108,7 @@ const SIGNAL_CHAT_UPDATE* = "chatUpdate" const SIGNAL_CHAT_LEFT* = "channelLeft" const SIGNAL_SENDING_FAILED* = "messageSendingFailed" const SIGNAL_SENDING_SUCCESS* = "messageSendingSuccess" -const SIGNAL_MESSAGE_DELETED* = "messageDeleted" +const SIGNAL_MESSAGE_REMOVE* = "messageRemove" const SIGNAL_CHAT_MUTED* = "chatMuted" const SIGNAL_CHAT_UNMUTED* = "chatUnmuted" const SIGNAL_CHAT_HISTORY_CLEARED* = "chatHistoryCleared" @@ -271,7 +271,7 @@ QtObject: return i i.inc() return -1 - + proc chatsWithCategoryHaveUnreadMessages*(self: Service, communityId: string, categoryId: string): bool = if communityId == "" or categoryId == "": return false @@ -336,7 +336,7 @@ QtObject: self.channelGroups[channelGroupId].chats[index] = self.chats[chat.id] proc updateMissingFieldsInCommunityChat(self: Service, channelGroupId: string, newChat: ChatDto): ChatDto = - + if not self.channelGroups.contains(channelGroupId): warn "unknown channel group", channelGroupId return @@ -359,11 +359,11 @@ QtObject: # We need to update missing fields in the chats seq before saving let newChats = channelGroup.chats.mapIt(self.updateMissingFieldsInCommunityChat(channelGroup.id, it)) newChannelGroup.chats = newChats - + self.channelGroups[channelGroup.id] = newChannelGroup for chat in newChannelGroup.chats: self.updateOrAddChat(chat) - + proc updateChannelMembers*(self: Service, channel: ChatDto) = if not self.chats.hasKey(channel.id): return @@ -412,7 +412,8 @@ QtObject: proc processUpdateForTransaction*(self: Service, messageId: string, response: RpcResponse[JsonNode]) = var (chats, _) = self.processMessageUpdateAfterSend(response) - self.events.emit(SIGNAL_MESSAGE_DELETED, MessageArgs(id: messageId, channel: chats[0].id)) + # TODO: Signal is not handled anywhere + self.events.emit(SIGNAL_MESSAGE_REMOVE, MessageArgs(id: messageId, channel: chats[0].id)) proc emitUpdate(self: Service, response: RpcResponse[JsonNode]) = var (chats, _) = self.parseChatResponse(response) @@ -502,10 +503,10 @@ QtObject: error "Error deleting channel", chatId, msg = e.msg return - proc sendImages*(self: Service, - chatId: string, - imagePathsAndDataJson: string, - msg: string, + proc sendImages*(self: Service, + chatId: string, + imagePathsAndDataJson: string, + msg: string, replyTo: string, preferredUsername: string = "", linkPreviews: seq[LinkPreview] = @[]): string = diff --git a/src/app_service/service/community/async_tasks.nim b/src/app_service/service/community/async_tasks.nim index 96c59cebfc..a03f626ba5 100644 --- a/src/app_service/service/community/async_tasks.nim +++ b/src/app_service/service/community/async_tasks.nim @@ -110,6 +110,7 @@ type AsyncCommunityMemberActionTaskArg = ref object of QObjectTaskArg communityId: string pubKey: string + deleteAllMessages: bool const asyncRemoveUserFromCommunityTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncCommunityMemberActionTaskArg](argEncoded) @@ -131,14 +132,15 @@ const asyncRemoveUserFromCommunityTask: Task = proc(argEncoded: string) {.gcsafe const asyncBanUserFromCommunityTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = let arg = decode[AsyncCommunityMemberActionTaskArg](argEncoded) try: - let response = status_go.banUserFromCommunity(arg.communityId, arg.pubKey) + let response = status_go.banUserFromCommunity(arg.communityId, arg.pubKey, arg.deleteAllMessages) let tpl: tuple[communityId: string, pubKey: string, response: RpcResponse[JsonNode], error: string] = (arg.communityId, arg.pubKey, response, "") arg.finish(tpl) except Exception as e: arg.finish(%* { "error": e.msg, "communityId": arg.communityId, - "pubKey": arg.pubKey + "pubKey": arg.pubKey, + "deleteAllMessages": arg.deleteAllMessages }) const asyncUnbanUserFromCommunityTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index 4552bb9cb2..9a199bf87d 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -2059,13 +2059,14 @@ QtObject: ) self.threadpool.start(arg) - proc asyncBanUserFromCommunity*(self: Service, communityId, pubKey: string) = + proc asyncBanUserFromCommunity*(self: Service, communityId, pubKey: string, deleteAllMessages: bool) = let arg = AsyncCommunityMemberActionTaskArg( tptr: cast[ByteAddress](asyncBanUserFromCommunityTask), vptr: cast[ByteAddress](self.vptr), slot: "onAsyncCommunityMemberActionCompleted", communityId: communityId, pubKey: pubKey, + deleteAllMessages: deleteAllMessages ) self.threadpool.start(arg) diff --git a/src/app_service/service/message/service.nim b/src/app_service/service/message/service.nim index 70cfe284a7..32ae9a89ef 100644 --- a/src/app_service/service/message/service.nim +++ b/src/app_service/service/message/service.nim @@ -54,7 +54,8 @@ const SIGNAL_MESSAGES_MARKED_AS_READ* = "messagesMarkedAsRead" const SIGNAL_MESSAGE_REACTION_ADDED* = "messageReactionAdded" const SIGNAL_MESSAGE_REACTION_REMOVED* = "messageReactionRemoved" const SIGNAL_MESSAGE_REACTION_FROM_OTHERS* = "messageReactionFromOthers" -const SIGNAL_MESSAGE_DELETION* = "messageDeleted" +const SIGNAL_MESSAGE_REMOVED* = "messageRemoved" +const SIGNAL_MESSAGES_DELETED* = "messagesDeleted" const SIGNAL_MESSAGE_DELIVERED* = "messageDelivered" const SIGNAL_MESSAGE_EDITED* = "messageEdited" const SIGNAL_ENVELOPE_SENT* = "envelopeSent" @@ -111,11 +112,14 @@ type reactionId*: string reactionFrom*: string - MessageDeletedArgs* = ref object of Args + MessageRemovedArgs* = ref object of Args chatId*: string messageId*: string deletedBy*: string + MessagesDeletedArgs* = ref object of Args + deletedMessages*: Table[string, seq[string]] + MessageDeliveredArgs* = ref object of Args chatId*: string messageId*: string @@ -348,10 +352,14 @@ QtObject: self.numOfPinnedMessagesPerChat[chatId] = self.getNumOfPinnedMessages(chatId) - 1 self.events.emit(SIGNAL_MESSAGE_UNPINNED, data) - proc handleDeletedMessagesUpdate(self: Service, deletedMessages: seq[RemovedMessageDto]) = - for dm in deletedMessages: - let data = MessageDeletedArgs(chatId: dm.chatId, messageId: dm.messageId, deletedBy: dm.deletedBy) - self.events.emit(SIGNAL_MESSAGE_DELETION, data) + proc handleRemovedMessagesUpdate(self: Service, removedMessages: seq[RemovedMessageDto]) = + for rm in removedMessages: + 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) + self.events.emit(SIGNAL_MESSAGES_DELETED, data) proc handleEmojiReactionsUpdate(self: Service, emojiReactions: seq[ReactionDto]) = for r in emojiReactions: @@ -416,6 +424,9 @@ QtObject: # Handling pinned messages updates if (receivedData.pinnedMessages.len > 0): self.handlePinnedMessagesUpdate(receivedData.pinnedMessages) + # Handling removed messages updates + if (receivedData.removedMessages.len > 0): + self.handleRemovedMessagesUpdate(receivedData.removedMessages) # Handling deleted messages updates if (receivedData.deletedMessages.len > 0): self.handleDeletedMessagesUpdate(receivedData.deletedMessages) @@ -430,12 +441,12 @@ QtObject: self.events.on(SignalType.DiscordCommunityImportFinished.event) do(e: Args): var receivedData = DiscordCommunityImportFinishedSignal(e) self.handleMessagesReload(receivedData.communityId) - + self.events.on(SignalType.DiscordChannelImportFinished.event) do(e: Args): var receivedData = DiscordChannelImportFinishedSignal(e) self.resetMessageCursor(receivedData.channelId) self.asyncLoadMoreMessagesForChat(receivedData.channelId) - + self.events.on(SIGNAL_CHAT_LEFT) do(e: Args): var chatArg = ChatArgs(e) self.resetMessageCursor(chatArg.chatId) @@ -637,7 +648,7 @@ QtObject: messageId: responseObj["messageId"].getStr, error: responseObj["error"].getStr, ) - + if signalData.error == "": signalData.message = responseObj["message"].toMessageDto() @@ -942,7 +953,7 @@ QtObject: if responseObj.kind != JObject: warn "expected response is not a json object", methodName = "onAsyncUnfurlUrlsFinished" return - + let errMessage = responseObj["error"].getStr if errMessage != "": error "asyncUnfurlUrls failed", errMessage @@ -1057,28 +1068,28 @@ proc deleteMessage*(self: Service, messageId: string) = try: let response = status_go.deleteMessageAndSend(messageId) - var deletesMessagesObj: JsonNode - if(not response.result.getProp("removedMessages", deletesMessagesObj) or deletesMessagesObj.kind != JArray): - error "error: ", procName="deleteMessage", errDesription = "no messages deleted or it's not an array" + var removesMessagesObj: JsonNode + if(not response.result.getProp("removedMessages", removesMessagesObj) or removesMessagesObj.kind != JArray): + error "error: ", procName="removeMessage", errDesription = "no messages remove or it's not an array" return - let deletedMessagesArr = deletesMessagesObj.getElems() - if(deletedMessagesArr.len == 0): # an array is returned - error "error: ", procName="deleteMessage", errDesription = "array has no message to delete" + let removedMessagesArr = removesMessagesObj.getElems() + if(removedMessagesArr.len == 0): # an array is returned + error "error: ", procName="removeMessage", errDesription = "array has no message to remove" return - let deletedMessageObj = deletedMessagesArr[0] + let removedMessageObj = removedMessagesArr[0] var chat_Id, message_Id: string - if not deletedMessageObj.getProp("chatId", chat_Id) or not deletedMessageObj.getProp("messageId", message_Id): - error "error: ", procName="deleteMessage", errDesription = "there is no set chat id or message id in response" + if not removedMessageObj.getProp("chatId", chat_Id) or not removedMessageObj.getProp("messageId", message_Id): + error "error: ", procName="removeMessage", errDesription = "there is no set chat id or message id in response" return - let data = MessageDeletedArgs( + let data = MessageRemovedArgs( chatId: chat_Id, messageId: message_Id, deletedBy: singletonInstance.userProfile.getPubKey(), ) - self.events.emit(SIGNAL_MESSAGE_DELETION, data) + self.events.emit(SIGNAL_MESSAGE_REMOVED, data) except Exception as e: error "error: ", procName="deleteMessage", errName = e.name, errDesription = e.msg diff --git a/src/backend/chat.nim b/src/backend/chat.nim index 1d6ed423eb..e6586d228f 100644 --- a/src/backend/chat.nim +++ b/src/backend/chat.nim @@ -86,9 +86,9 @@ proc sendChatMessage*( } ]) -proc sendImages*(chatId: string, - images: var seq[string], - msg: string, +proc sendImages*(chatId: string, + images: var seq[string], + msg: string, replyTo: string, preferredUsername: string, linkPreviews: seq[LinkPreview], diff --git a/src/backend/communities.nim b/src/backend/communities.nim index 9d5a5cdaca..15500cf3d4 100644 --- a/src/backend/communities.nim +++ b/src/backend/communities.nim @@ -460,10 +460,11 @@ proc declineRequestToJoinCommunity*(requestId: string): RpcResponse[JsonNode] {. "id": requestId }]) -proc banUserFromCommunity*(communityId: string, pubKey: string): RpcResponse[JsonNode] {.raises: [Exception].} = +proc banUserFromCommunity*(communityId: string, pubKey: string, deleteAllMessages: bool): RpcResponse[JsonNode] {.raises: [Exception].} = return callPrivateRPC("banUserFromCommunity".prefix, %*[{ "communityId": communityId, - "user": pubKey + "user": pubKey, + "deleteAllMessages": deleteAllMessages, }]) proc unbanUserFromCommunity*(communityId: string, pubKey: string): RpcResponse[JsonNode] {.raises: [Exception].} = diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 28096ef479..f94bdbca32 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -327,8 +327,8 @@ QtObject { chatCommunitySectionModule.removeUserFromCommunity(pubKey); } - function banUserFromCommunity(pubKey) { - chatCommunitySectionModule.banUserFromCommunity(pubKey); + function banUserFromCommunity(pubKey, deleteAllMessages) { + chatCommunitySectionModule.banUserFromCommunity(pubKey, deleteAllMessages); } function unbanUserFromCommunity(pubKey) { diff --git a/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml b/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml index a56c261601..80e896dd12 100644 --- a/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml +++ b/ui/app/AppLayouts/Communities/panels/MembersSettingsPanel.qml @@ -22,7 +22,7 @@ SettingsPage { signal membershipRequestsClicked() signal kickUserClicked(string id) - signal banUserClicked(string id) + signal banUserClicked(string id, bool deleteAllMessages) signal unbanUserClicked(string id) signal acceptRequestToJoin(string id) signal declineRequestToJoin(string id) @@ -193,11 +193,7 @@ SettingsPage { communityName: root.communityName - onAccepted: { - if (mode === KickBanPopup.Mode.Kick) - root.kickUserClicked(userId) - else - root.banUserClicked(userId) - } + onBanUserClicked: root.banUserClicked(userId, deleteAllMessages) + onKickUserClicked: root.kickUserClicked(userId) } } diff --git a/ui/app/AppLayouts/Communities/popups/KickBanPopup.qml b/ui/app/AppLayouts/Communities/popups/KickBanPopup.qml index 8c9ef88726..c3f92c4734 100644 --- a/ui/app/AppLayouts/Communities/popups/KickBanPopup.qml +++ b/ui/app/AppLayouts/Communities/popups/KickBanPopup.qml @@ -1,9 +1,11 @@ import QtQuick 2.15 import QtQml.Models 2.15 +import QtQuick.Layouts 1.15 import StatusQ.Controls 0.1 import StatusQ.Core 0.1 import StatusQ.Popups.Dialog 0.1 +import StatusQ.Components 0.1 import utils 1.0 @@ -15,6 +17,9 @@ StatusDialog { property string communityName: "" property int mode: KickBanPopup.Mode.Kick + signal banUserClicked(bool deleteAllMessages) + signal kickUserClicked() + enum Mode { Kick, Ban } @@ -25,17 +30,40 @@ StatusDialog { ? qsTr("Kick %1").arg(root.username) : qsTr("Ban %1").arg(root.username) - contentItem: StatusBaseText { + contentItem: ColumnLayout { anchors.centerIn: parent - font.pixelSize: Style.current.primaryTextFontSize - wrapMode: Text.Wrap - text: root.mode === KickBanPopup.Mode.Kick - ? qsTr("Are you sure you kick %1 from %2?") - .arg(root.username).arg(root.communityName) - : qsTr("Are you sure you ban %1 from %2?") - .arg(root.username).arg(root.communityName) - } + StatusBaseText { + Layout.fillWidth: true + Layout.fillHeight: true + + font.pixelSize: Style.current.primaryTextFontSize + wrapMode: Text.Wrap + + text: root.mode === KickBanPopup.Mode.Kick + ? qsTr("Are you sure you want to kick %1 from %2?") + .arg(root.username).arg(root.communityName) + : qsTr("Are you sure you want to ban %1 from %2? This means that they will be kicked from this community and banned from re-joining.") + .arg(root.username).arg(root.communityName) + } + + RowLayout { + visible: root.mode === KickBanPopup.Mode.Ban + + StatusBaseText { + Layout.fillWidth: true + + text: qsTr("Delete all messages posted by the user") + font.pixelSize: Style.current.primaryTextFontSize + } + + StatusSwitch { + id: deleteAllMessagesSwitch + + checked: false + } + } + } footer: StatusDialogFooter { rightButtons: ObjectModel { @@ -51,14 +79,17 @@ StatusDialog { ? "CommunityMembers_KickModal_KickButton" : "CommunityMembers_BanModal_BanButton" - text: root.mode === KickBanPopup.Mode.Kick ? qsTr("Kick") - : qsTr("Ban") + text: root.mode === KickBanPopup.Mode.Kick ? qsTr("Kick %1").arg(root.username) + : qsTr("Ban %1").arg(root.username) type: StatusBaseButton.Type.Danger onClicked: { - root.accept() + root.mode === KickBanPopup.Mode.Kick ? root.kickUserClicked() + : root.banUserClicked(deleteAllMessagesSwitch.checked) root.close() } } } } + + onClosed: deleteAllMessagesSwitch.checked = false } diff --git a/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml b/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml index 479383a4f4..3cec4a8bfd 100644 --- a/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunityColumnView.qml @@ -72,6 +72,8 @@ Item { property bool invitationPending: root.store.isMyCommunityRequestPending(communityData.id) property bool joiningCommunityInProgress: false + + onShowJoinButtonChanged: invitationPending = root.store.isMyCommunityRequestPending(communityData.id) } ColumnHeaderPanel { diff --git a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml index 640941ac85..01d0099420 100644 --- a/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml +++ b/ui/app/AppLayouts/Communities/views/CommunitySettingsView.qml @@ -273,7 +273,7 @@ StatusSectionLayout { communityName: root.community.name onKickUserClicked: root.rootStore.removeUserFromCommunity(id) - onBanUserClicked: root.rootStore.banUserFromCommunity(id) + onBanUserClicked: root.rootStore.banUserFromCommunity(id, deleteAllMessages) onUnbanUserClicked: root.rootStore.unbanUserFromCommunity(id) onAcceptRequestToJoin: root.rootStore.acceptRequestToJoinCommunity(id, root.community.id) onDeclineRequestToJoin: root.rootStore.declineRequestToJoinCommunity(id, root.community.id) diff --git a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml index c53b136563..5b6dcda2d3 100644 --- a/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml +++ b/ui/app/mainui/activitycenter/popups/ActivityCenterPopup.qml @@ -140,6 +140,10 @@ Popup { return ownerTokenReceivedNotificationComponent case ActivityCenterStore.ActivityCenterNotificationType.ShareAccounts: return shareAccountsNotificationComponent + case ActivityCenterStore.ActivityCenterNotificationType.CommunityBanned: + return communityBannedNotificationComponent + case ActivityCenterStore.ActivityCenterNotificationType.CommunityUnbanned: + return communityUnbannedNotificationComponent default: return null } @@ -236,6 +240,32 @@ Popup { } } + Component { + id: communityBannedNotificationComponent + + ActivityNotificationCommunityBanUnban { + banned: true + filteredIndex: parent.filteredIndex + notification: parent.notification + store: root.store + activityCenterStore: root.activityCenterStore + onCloseActivityCenter: root.close() + } + } + + Component { + id: communityUnbannedNotificationComponent + + ActivityNotificationCommunityBanUnban { + banned: false + filteredIndex: parent.filteredIndex + notification: parent.notification + store: root.store + activityCenterStore: root.activityCenterStore + onCloseActivityCenter: root.close() + } + } + Component { id: contactRemovedComponent diff --git a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml index 17ab185dd4..a913c2d12b 100644 --- a/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml +++ b/ui/app/mainui/activitycenter/stores/ActivityCenterStore.qml @@ -38,7 +38,9 @@ QtObject { OwnershipDeclined = 17, ShareAccounts = 18, CommunityTokenReceived = 19, - FirstCommunityTokenReceived = 20 + FirstCommunityTokenReceived = 20, + CommunityBanned = 21, + CommunityUnbanned = 22 } enum ActivityCenterReadType { diff --git a/ui/app/mainui/activitycenter/views/ActivityNotificationCommunityBanUnban.qml b/ui/app/mainui/activitycenter/views/ActivityNotificationCommunityBanUnban.qml new file mode 100644 index 0000000000..23c8d87eeb --- /dev/null +++ b/ui/app/mainui/activitycenter/views/ActivityNotificationCommunityBanUnban.qml @@ -0,0 +1,78 @@ +import QtQuick 2.14 +import QtQuick.Layouts 1.14 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Components 0.1 +import StatusQ.Controls 0.1 + +import shared 1.0 +import shared.panels 1.0 +import shared.controls 1.0 +import utils 1.0 + +import "../controls" + +ActivityNotificationBase { + id: root + property bool banned: true + + bodyComponent: RowLayout { + width: parent.width + height: 50 + readonly property var community: notification ? + root.store.getCommunityDetailsAsJson(notification.communityId) : + null + + StatusSmartIdenticon { + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignTop + Layout.leftMargin: Style.current.padding + Layout.topMargin: 2 + + asset { + width: 24 + height: width + name: "communities" + color: root.banned ? "red" : "green" + bgWidth: 40 + bgHeight: 40 + bgColor: Theme.palette.getColor(asset.color, 0.1) + } + } + + StatusBaseText { + text: root.banned ? qsTr("You were banned from") : qsTr("You've been unbanned from") + Layout.alignment: Qt.AlignVCenter + font.italic: true + color: Theme.palette.baseColor1 + } + + CommunityBadge { + communityName: community ? community.name : "" + communityImage: community ? community.image : "" + communityColor: community ? community.color : "black" + onCommunityNameClicked: root.store.setActiveCommunity(notification.communityId) + Layout.alignment: Qt.AlignVCenter + Layout.maximumWidth: 190 + } + + Item { + Layout.fillWidth: true + } + } + + ctaComponent: root.banned ? undefined : visitCommunityCta + + Component { + id: visitCommunityCta + StatusLinkText { + text: qsTr("Visit Community") + onClicked: { + root.store.setActiveCommunity(notification.communityId) + root.closeActivityCenter() + } + } + } +} diff --git a/vendor/status-go b/vendor/status-go index 9b17fd6673..3959948c4c 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 9b17fd66734f810d465a1e463451260c0c0fd762 +Subproject commit 3959948c4c5ab560ae528c2d331241f2cc94fed1