From b6b21c37449593630014d849d2d90fd9006d66dd Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Mon, 13 May 2024 15:58:55 -0400 Subject: [PATCH] Refactor out channel groups (#14202) Fixes #12595 Gets rid of the concept of channelGroups. Instead, we use the communities and chats directly, to keep the same concepts as status-go. --- .../main/activity_center/controller.nim | 5 +- .../modules/main/activity_center/module.nim | 46 ++-- .../modules/main/app_search/controller.nim | 9 +- src/app/modules/main/app_search/module.nim | 177 ++++++++------ .../chat_content/users/controller.nim | 9 +- .../modules/main/chat_section/controller.nim | 29 ++- .../main/chat_section/io_interface.nim | 3 +- src/app/modules/main/chat_section/module.nim | 68 +++--- src/app/modules/main/controller.nim | 14 +- src/app/modules/main/io_interface.nim | 3 +- src/app/modules/main/module.nim | 225 ++++++++++-------- .../modules/main/profile_section/module.nim | 2 +- .../notifications/controller.nim | 35 ++- .../notifications/io_interface.nim | 3 + .../profile_section/notifications/module.nim | 48 ++-- src/app_service/service/chat/async_tasks.nim | 15 +- src/app_service/service/chat/dto/chat.nim | 108 +-------- src/app_service/service/chat/service.nim | 225 +++--------------- .../service/community/dto/community.nim | 42 +--- src/app_service/service/community/service.nim | 45 ++-- src/backend/chat.nim | 11 +- vendor/status-go | 2 +- 22 files changed, 495 insertions(+), 629 deletions(-) diff --git a/src/app/modules/main/activity_center/controller.nim b/src/app/modules/main/activity_center/controller.nim index b9069aa766..c29f115c05 100644 --- a/src/app/modules/main/activity_center/controller.nim +++ b/src/app/modules/main/activity_center/controller.nim @@ -44,7 +44,7 @@ proc updateActivityGroupCounters*(self: Controller) = self.delegate.setActivityGroupCounters(counters) proc init*(self: Controller) = - self.events.once(chat_service.SIGNAL_CHANNEL_GROUPS_LOADED) do(e:Args): + self.events.once(chat_service.SIGNAL_ACTIVE_CHATS_LOADED) do(e:Args): # Only fectch activity center notification once channel groups are loaded, # since we need the chats to associate the notifications to self.activity_center_service.asyncActivityNotificationLoad() @@ -136,9 +136,6 @@ proc switchTo*(self: Controller, sectionId, chatId, messageId: string) = proc getChatDetails*(self: Controller, chatId: string): ChatDto = return self.chatService.getChatById(chatId) -proc getChannelGroups*(self: Controller): seq[ChannelGroupDto] = - return self.chatService.getChannelGroups() - proc getOneToOneChatNameAndImage*(self: Controller, chatId: string): tuple[name: string, image: string, largeImage: string] = return self.chatService.getOneToOneChatNameAndImage(chatId) diff --git a/src/app/modules/main/activity_center/module.nim b/src/app/modules/main/activity_center/module.nim index c4b861a283..653ffe1e73 100644 --- a/src/app/modules/main/activity_center/module.nim +++ b/src/app/modules/main/activity_center/module.nim @@ -7,6 +7,7 @@ import ../../shared_models/message_item as msg_item import ../../shared_models/message_item_qobject as msg_item_qobj import ../../shared_models/message_transaction_parameters_item import ../../../global/global_singleton +import ../../../global/app_sections_config as conf import ../../../core/eventemitter import ../../../../app_service/service/activity_center/service as activity_center_service import ../../../../app_service/service/contacts/service as contacts_service @@ -268,32 +269,33 @@ method switchTo*(self: Module, sectionId, chatId, messageId: string) = self.controller.switchTo(sectionId, chatId, messageId) method getDetails*(self: Module, sectionId: string, chatId: string): string = - let groups = self.controller.getChannelGroups() var jsonObject = newJObject() + if sectionId == singletonInstance.userProfile.getPubKey(): + jsonObject["sType"] = %* ChatSectionType.Personal + jsonObject["sName"] = %* conf.CHAT_SECTION_NAME + jsonObject["sImage"] = %* "" + jsonObject["sColor"] = %* "" + else: + # Community + let community = self.controller.getCommunityById(sectionId) - for g in groups: - if(g.id != sectionId): - continue + jsonObject["sType"] = %* ChatSectionType.Community + jsonObject["sName"] = %* community.name + jsonObject["sImage"] = %* community.images.thumbnail + jsonObject["sColor"] = %* community.color - jsonObject["sType"] = %* g.channelGroupType - jsonObject["sName"] = %* g.name - jsonObject["sImage"] = %* g.images.thumbnail - jsonObject["sColor"] = %* g.color + let c = self.controller.getChatDetails(chatId) + + var chatName = c.name + var chatImage = c.icon + if c.chatType == ChatType.OneToOne: + (chatName, chatImage) = self.controller.getOneToOneChatNameAndImage(c.id) - for c in g.chats: - if(c.id != chatId): - continue - - var chatName = c.name - var chatImage = c.icon - if(c.chatType == ChatType.OneToOne): - (chatName, chatImage) = self.controller.getOneToOneChatNameAndImage(c.id) - - jsonObject["cName"] = %* chatName - jsonObject["cImage"] = %* chatImage - jsonObject["cColor"] = %* c.color - jsonObject["cEmoji"] = %* c.emoji - return $jsonObject + jsonObject["cName"] = %* chatName + jsonObject["cImage"] = %* chatImage + jsonObject["cColor"] = %* c.color + jsonObject["cEmoji"] = %* c.emoji + return $jsonObject method getChatDetailsAsJson*(self: Module, chatId: string): string = let chatDto = self.controller.getChatDetails(chatId) diff --git a/src/app/modules/main/app_search/controller.nim b/src/app/modules/main/app_search/controller.nim index 45103ab724..7e6e203c9b 100644 --- a/src/app/modules/main/app_search/controller.nim +++ b/src/app/modules/main/app_search/controller.nim @@ -85,12 +85,15 @@ proc setSearchLocation*(self: Controller, location: string, subLocation: string) self.searchLocation = location self.searchSubLocation = subLocation -proc getChannelGroups*(self: Controller): seq[ChannelGroupDto] = - return self.chatService.getChannelGroups() - proc getCommunityById*(self: Controller, communityId: string): CommunityDto = return self.communityService.getCommunityById(communityId) +proc getJoinedAndSpectatedCommunities*(self: Controller): seq[CommunityDto] = + return self.communityService.getJoinedAndSpectatedCommunities() + +proc getChatsForPersonalSection*(self: Controller): seq[ChatDto] = + return self.chatService.getChatsForPersonalSection() + proc getChatDetailsForChatTypes*(self: Controller, types: seq[ChatType]): seq[ChatDto] = return self.chatService.getChatsOfChatTypes(types) diff --git a/src/app/modules/main/app_search/module.nim b/src/app/modules/main/app_search/module.nim index 8a78d25ea1..bb2b00ffd2 100644 --- a/src/app/modules/main/app_search/module.nim +++ b/src/app/modules/main/app_search/module.nim @@ -1,5 +1,5 @@ import NimQml -import json, strutils, chronicles +import json, strutils, chronicles, sequtils import io_interface import ../io_interface as delegate_interface import view, controller @@ -66,18 +66,8 @@ method viewDidLoad*(self: Module) = method getModuleAsVariant*(self: Module): QVariant = return self.viewVariant -proc buildLocationMenuForChannelGroup(self: Module, channelGroup: ChannelGroupDto): location_menu_item.Item = - let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community - - var item = location_menu_item.initItem( - channelGroup.id, - if (isCommunity): channelGroup.name else: SEARCH_MENU_LOCATION_CHAT_SECTION_NAME, - channelGroup.images.thumbnail, - icon=if (isCommunity): "" else: "chat", - channelGroup.color) - - var subItems: seq[location_menu_sub_item.SubItem] - for chatDto in channelGroup.chats: +proc getChatSubItems(self: Module, chats: seq[ChatDto]): seq[location_menu_sub_item.SubItem] = + for chatDto in chats: var chatName = chatDto.name var chatImage = chatDto.icon var colorHash: ColorHashDto = @[] @@ -95,18 +85,44 @@ proc buildLocationMenuForChannelGroup(self: Module, channelGroup: ChannelGroupDt chatDto.color, isOneToOneChat, colorId, - colorHash) - subItems.add(subItem) + colorHash + ) + result.add(subItem) +proc buildLocationMenuForCommunity(self: Module, community: CommunityDto): location_menu_item.Item = + var item = location_menu_item.initItem( + community.id, + community.name, + community.images.thumbnail, + icon="", + community.color + ) + + var subItems = self.getChatSubItems(community.chats) item.setSubItems(subItems) + return item method prepareLocationMenuModel*(self: Module) = var items: seq[location_menu_item.Item] - let channelGroups = self.controller.getChannelGroups() - for c in channelGroups: - items.add(self.buildLocationMenuForChannelGroup(c)) + # Personal Section + let myPubKey = singletonInstance.userProfile.getPubKey() + var personalItem = location_menu_item.initItem( + value = myPubKey, + text = SEARCH_MENU_LOCATION_CHAT_SECTION_NAME, + image = "", + icon = "chat", + ) + + var subItems = self.getChatSubItems(self.controller.getChatsForPersonalSection()) + personalItem.setSubItems(subItems) + items.add(personalItem) + + # Community sections + let communities = self.controller.getJoinedAndSpectatedCommunities() + for c in communities: + items.add(self.buildLocationMenuForCommunity(c)) self.view.locationMenuModel().setItems(items) @@ -146,80 +162,85 @@ method searchMessages*(self: Module, searchTerm: string) = self.controller.searchMessages(searchTerm) +proc getResultItemFromChats(self: Module, sectionId: string, chats: seq[ChatDto], sectionName: string): seq[result_item.Item] = + if (self.controller.searchSubLocation().len == 0 and self.controller.searchLocation().len == 0) or + self.controller.searchLocation() == sectionId: + + let searchTerm = self.controller.searchTerm().toLower + for chatDto in chats: + var chatName = chatDto.name + var chatImage = chatDto.icon + var colorHash: ColorHashDto = @[] + var colorId: int = 0 + let isOneToOneChat = chatDto.chatType == ChatType.OneToOne + if(isOneToOneChat): + (chatName, chatImage) = self.controller.getOneToOneChatNameAndImage(chatDto.id) + colorHash = self.controller.getColorHash(chatDto.id) + colorId = self.controller.getColorId(chatDto.id) + + var rawChatName = chatName + if(chatName.startsWith("@")): + rawChatName = chatName[1 ..^ 1] + + if rawChatName.toLower.contains(searchTerm): + let item = result_item.initItem( + chatDto.id, + content="", + time="", + titleId=chatDto.id, + title=chatName, + SEARCH_RESULT_CHANNELS_SECTION_NAME, + chatImage, + chatDto.color, + badgePrimaryText="", + badgeSecondaryText="", + chatImage, + chatDto.color, + false, + isOneToOneChat, + colorId, + colorHash) + + self.controller.addResultItemDetails(chatDto.id, sectionId, chatDto.id) + result.add(item) + method onSearchMessagesDone*(self: Module, messages: seq[MessageDto]) = var items: seq[result_item.Item] - var channels: seq[result_item.Item] - # Add Channel groups - let channelGroups = self.controller.getChannelGroups() + # Add Personal section chats + let myPubKey = singletonInstance.userProfile.getPubKey() + let personalItems = self.getResultItemFromChats(myPubKey, self.controller.getChatsForPersonalSection(), SEARCH_RESULT_CHATS_SECTION_NAME) + var channels = personalItems + + # Add Communities + let communities = self.controller.getJoinedAndSpectatedCommunities() + let searchTerm = self.controller.searchTerm().toLower - for channelGroup in channelGroups: - let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community - if(self.controller.searchLocation().len == 0 and - channelGroup.name.toLower.contains(searchTerm)): + for community in communities: + if self.controller.searchLocation().len == 0 and + community.name.toLower.contains(searchTerm): let item = result_item.initItem( - channelGroup.id, + community.id, content="", time="", - titleId=channelGroup.id, - title=channelGroup.name, - if (isCommunity): - SEARCH_RESULT_COMMUNITIES_SECTION_NAME - else: - SEARCH_RESULT_CHATS_SECTION_NAME, - channelGroup.images.thumbnail, - channelGroup.color, + titleId=community.id, + title=community.name, + SEARCH_RESULT_COMMUNITIES_SECTION_NAME, + community.images.thumbnail, + community.color, badgePrimaryText="", badgeSecondaryText="", - channelGroup.images.thumbnail, - channelGroup.color, + community.images.thumbnail, + community.color, badgeIsLetterIdenticon=false) - self.controller.addResultItemDetails(channelGroup.id, channelGroup.id) + self.controller.addResultItemDetails(community.id, community.id) items.add(item) # Add channels - if((self.controller.searchSubLocation().len == 0 and self.controller.searchLocation().len == 0) or - self.controller.searchLocation() == channelGroup.id): - for chatDto in channelGroup.chats: - var chatName = chatDto.name - var chatImage = chatDto.icon - var colorHash: ColorHashDto = @[] - var colorId: int = 0 - let isOneToOneChat = chatDto.chatType == ChatType.OneToOne - if(isOneToOneChat): - (chatName, chatImage) = self.controller.getOneToOneChatNameAndImage(chatDto.id) - colorHash = self.controller.getColorHash(chatDto.id) - colorId = self.controller.getColorId(chatDto.id) - - var rawChatName = chatName - if(chatName.startsWith("@")): - rawChatName = chatName[1 ..^ 1] - - if(rawChatName.toLower.contains(searchTerm)): - let item = result_item.initItem( - chatDto.id, - content="", - time="", - titleId=chatDto.id, - title=chatName, - if isCommunity: - SEARCH_RESULT_CHANNELS_SECTION_NAME - else: - SEARCH_RESULT_CHATS_SECTION_NAME, - chatImage, - chatDto.color, - badgePrimaryText="", - badgeSecondaryText="", - chatImage, - chatDto.color, - false, - isOneToOneChat, - colorId, - colorHash) - - self.controller.addResultItemDetails(chatDto.id, channelGroup.id, chatDto.id) - channels.add(item) + let communityChatItems = self.getResultItemFromChats(community.id, community.chats, SEARCH_RESULT_CHANNELS_SECTION_NAME) + if communityChatItems.len > 0: + channels = channels.concat(channels, communityChatItems) # Add channels in order as requested by the design items.add(channels) diff --git a/src/app/modules/main/chat_section/chat_content/users/controller.nim b/src/app/modules/main/chat_section/chat_content/users/controller.nim index b5a8393e57..e83879c640 100644 --- a/src/app/modules/main/chat_section/chat_content/users/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/users/controller.nim @@ -115,11 +115,18 @@ proc init*(self: Controller) = proc belongsToCommunity*(self: Controller): bool = self.belongsToCommunity +proc getMyCommunity*(self: Controller): CommunityDto = + return self.communityService.getCommunityById(self.sectionId) + proc getChat*(self: Controller): ChatDto = return self.chatService.getChatById(self.chatId) proc getChatMembers*(self: Controller): seq[ChatMember] = - return self.chatService.getChatById(self.chatId).members + if self.belongsToCommunity: + let myCommunity = self.getMyCommunity() + return myCommunity.getCommunityChat(self.chatId).members + else: + return self.chatService.getChatById(self.chatId).members proc getContactNameAndImage*(self: Controller, contactId: string): tuple[name: string, image: string, largeImage: string] = diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 442d4288d2..6143f09da0 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -455,9 +455,30 @@ proc getAllChats*(self: Controller, communityId: string): seq[ChatDto] = return self.communityService.getAllChats(communityId) proc getChatsAndBuildUI*(self: Controller) = - let channelGroup = self.chatService.getChannelGroupById(self.sectionId) + var chats: seq[ChatDto] + var community: CommunityDto + if self.isCommunity(): + community = self.getMyCommunity() + let normalChats = self.chatService.getChatsForCommunity(community.id) + + # TODO remove this once we do this refactor https://github.com/status-im/status-desktop/issues/14219 + var fullChats: seq[ChatDto] = @[] + for communityChat in community.chats: + for chat in normalChats: + if chat.id == communityChat.id: + var c = chat + c.updateMissingFields(communityChat) + fullChats.add(c) + break + chats = fullChats + else: + community = CommunityDto() + chats = self.chatService.getChatsForPersonalSection() + + # Build chat section with the preloaded community (empty community for personal chat) self.delegate.onChatsLoaded( - channelGroup, + community, + chats, self.events, self.settingsService, self.nodeConfigurationService, @@ -482,8 +503,8 @@ proc getChatDetailsForChatTypes*(self: Controller, types: seq[ChatType]): seq[Ch proc getChatDetailsByIds*(self: Controller, chatIds: seq[string]): seq[ChatDto] = return self.chatService.getChatsByIds(chatIds) -proc chatsWithCategoryHaveUnreadMessages*(self: Controller, communityId: string, categoryId: string): bool = - return self.chatService.chatsWithCategoryHaveUnreadMessages(communityId, categoryId) +proc categoryHasUnreadMessages*(self: Controller, communityId: string, categoryId: string): bool = + return self.communityService.categoryHasUnreadMessages(communityId, categoryId) proc getCommunityCategoryDetails*(self: Controller, communityId: string, categoryId: string): Category = return self.communityService.getCategoryById(communityId, categoryId) diff --git a/src/app/modules/main/chat_section/io_interface.nim b/src/app/modules/main/chat_section/io_interface.nim index e220fc6d45..c02ef55120 100644 --- a/src/app/modules/main/chat_section/io_interface.nim +++ b/src/app/modules/main/chat_section/io_interface.nim @@ -26,7 +26,8 @@ method load*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") method onChatsLoaded*(self: AccessInterface, - channelGroup: ChannelGroupDto, + community: CommunityDto, + chats: seq[ChatDto], events: UniqueUUIDEventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index fe8d7c6f4c..e29cd23194 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -57,7 +57,8 @@ type # Forward declaration proc buildChatSectionUI( self: Module, - channelGroup: ChannelGroupDto, + community: CommunityDto, + chats: seq[ChatDto], events: UniqueUUIDEventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, @@ -75,7 +76,7 @@ proc changeCanPostValues*(self: Module, chatId: string, canPostReactions, viewer proc addOrUpdateChat(self: Module, chat: ChatDto, - channelGroup: ChannelGroupDto, + community: CommunityDto, belongsToCommunity: bool, events: UniqueUUIDEventEmitter, settingsService: settings_service.Service, @@ -255,7 +256,7 @@ proc removeSubmodule(self: Module, chatId: string) = proc addCategoryItem(self: Module, category: Category, memberRole: MemberRole, communityId: string, insertIntoModel: bool = true): chat_item.Item = - let hasUnreadMessages = self.controller.chatsWithCategoryHaveUnreadMessages(communityId, category.id) + let hasUnreadMessages = self.controller.categoryHasUnreadMessages(communityId, category.id) result = chat_item.initItem( id = category.id, category.name, @@ -284,7 +285,8 @@ proc addCategoryItem(self: Module, category: Category, memberRole: MemberRole, c proc buildChatSectionUI( self: Module, - channelGroup: ChannelGroupDto, + community: CommunityDto, + chats: seq[ChatDto], events: UniqueUUIDEventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, @@ -299,11 +301,11 @@ proc buildChatSectionUI( let sectionLastOpenChat = singletonInstance.localAccountSensitiveSettings.getSectionLastOpenChat(self.controller.getMySectionId()) var items: seq[chat_item.Item] = @[] - for categoryDto in channelGroup.categories: + for categoryDto in community.categories: # Add items for the categories. We use a special type to identify categories - items.add(self.addCategoryItem(categoryDto, channelGroup.memberRole, channelGroup.id)) + items.add(self.addCategoryItem(categoryDto, community.memberRole, community.id)) - for chatDto in channelGroup.chats: + for chatDto in chats: var categoryPosition = -1 # Add an empty chat item that has the category info @@ -315,14 +317,14 @@ proc buildChatSectionUI( isActive = true if chatDto.categoryId != "": - for category in channelGroup.categories: + for category in community.categories: if category.id == chatDto.categoryId: categoryPosition = category.position break items.add(self.addOrUpdateChat( chatDto, - channelGroup, + community, belongsToCommunity = chatDto.communityId.len > 0, events, settingsService, @@ -392,7 +394,7 @@ proc reevaluateRequiresTokenPermissionToJoin(self: Module) = break self.view.setRequiresTokenPermissionToJoin(joinPermissionsChanged) -proc initCommunityTokenPermissionsModel(self: Module, channelGroup: ChannelGroupDto) = +proc initCommunityTokenPermissionsModel(self: Module) = self.rebuildCommunityTokenPermissionsModel() proc convertPubKeysToJson(self: Module, pubKeys: string): seq[string] = @@ -419,7 +421,8 @@ method load*(self: Module) = method onChatsLoaded*( self: Module, - channelGroup: ChannelGroupDto, + community: CommunityDto, + chats: seq[ChatDto], events: UniqueUUIDEventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, @@ -431,7 +434,7 @@ method onChatsLoaded*( sharedUrlsService: shared_urls_service.Service, ) = self.chatsLoaded = true - self.buildChatSectionUI(channelGroup, events, settingsService, nodeConfigurationService, + self.buildChatSectionUI(community, chats, events, settingsService, nodeConfigurationService, contactService, chatService, communityService, messageService, mailserversService, sharedUrlsService) if(not self.controller.isCommunity()): @@ -446,8 +449,8 @@ method onChatsLoaded*( requestToJoinState = RequestToJoinState.Requested self.view.setRequestToJoinState(requestToJoinState) - self.initCommunityTokenPermissionsModel(channelGroup) - self.onCommunityCheckAllChannelsPermissionsResponse(channelGroup.channelPermissions) + self.initCommunityTokenPermissionsModel() + self.onCommunityCheckAllChannelsPermissionsResponse(community.channelPermissions) self.controller.asyncCheckPermissionsToJoin() let activeChatId = self.controller.getActiveChatId() @@ -589,7 +592,7 @@ proc updateBadgeNotifications(self: Module, chat: ChatDto, hasUnreadMessages: bo self.chatContentModules[chatId].onNotificationsUpdated(hasUnreadMessages, unviewedMentionsCount) if chat.categoryId != "": - let hasUnreadMessages = self.controller.chatsWithCategoryHaveUnreadMessages(chat.communityId, chat.categoryId) + let hasUnreadMessages = self.controller.categoryHasUnreadMessages(chat.communityId, chat.categoryId) self.view.chatsModel().setCategoryHasUnreadMessages(chat.categoryId, hasUnreadMessages) self.updateParentBadgeNotifications() @@ -629,7 +632,7 @@ method chatsModel*(self: Module): chats_model.Model = proc addNewChat( self: Module, chatDto: ChatDto, - channelGroup: ChannelGroupDto, + community: CommunityDto, belongsToCommunity: bool, events: EventEmitter, settingsService: settings_service.Service, @@ -643,7 +646,7 @@ proc addNewChat( setChatAsActive: bool = true, insertIntoModel: bool = true, ): chat_item.Item = - let hasNotification =chatDto.unviewedMessagesCount > 0 + let hasNotification = chatDto.unviewedMessagesCount > 0 let notificationsCount = chatDto.unviewedMentionsCount var chatName = chatDto.name @@ -672,12 +675,11 @@ proc addNewChat( var memberRole = self.getUserMemberRole(chatDto.members) if chatDto.chatType != ChatType.PrivateGroupChat: - memberRole = channelGroup.memberRole + memberRole = community.memberRole if memberRole == MemberRole.None and len(chatDto.communityId) != 0: - memberRole = channelGroup.memberRole + memberRole = community.memberRole if memberRole == MemberRole.None: - let community = communityService.getCommunityById(chatDto.communityId) memberRole = community.memberRole var categoryOpened = true @@ -694,6 +696,16 @@ proc addNewChat( # preferable. Please fix-me in https://github.com/status-im/status-desktop/issues/14431 self.view.chatsModel().changeCategoryOpened(category.id, category.categoryOpened) + var canPostReactions = true + var hideIfPermissionsNotMet = false + var viewersCanPostReactions = true + if self.controller.isCommunity: + let communityChat = community.getCommunityChat(chatDto.id) + # Some properties are only available on CommunityChat (they are useless for normal chats) + canPostReactions = communityChat.canPostReactions + hideIfPermissionsNotMet = communityChat.hideIfPermissionsNotMet + viewersCanPostReactions = communityChat.viewersCanPostReactions + result = chat_item.initItem( chatDto.id, chatName, @@ -726,9 +738,9 @@ proc addNewChat( self.controller.checkChatHasPermissions(self.controller.getMySectionId(), chatDto.id) else: false, - canPostReactions = chatDto.canPostReactions, - viewersCanPostReactions = chatDto.viewersCanPostReactions, - hideIfPermissionsNotMet = chatDto.hideIfPermissionsNotMet, + canPostReactions = canPostReactions, + viewersCanPostReactions = viewersCanPostReactions, + hideIfPermissionsNotMet = hideIfPermissionsNotMet, viewOnlyPermissionsSatisfied = true, # will be updated in async call viewAndPostPermissionsSatisfied = true # will be updated in async call ) @@ -1382,7 +1394,7 @@ method setLoadingHistoryMessagesInProgress*(self: Module, isLoading: bool) = proc addOrUpdateChat(self: Module, chat: ChatDto, - channelGroup: ChannelGroupDto, + community: CommunityDto, belongsToCommunity: bool, events: UniqueUUIDEventEmitter, settingsService: settings_service.Service, @@ -1398,8 +1410,8 @@ proc addOrUpdateChat(self: Module, ): chat_item.Item = let sectionId = self.controller.getMySectionId() - if(belongsToCommunity and sectionId != chat.communityId or - not belongsToCommunity and sectionId != singletonInstance.userProfile.getPubKey()): + if belongsToCommunity and sectionId != chat.communityId or + not belongsToCommunity and sectionId != singletonInstance.userProfile.getPubKey(): return self.updateBadgeNotifications(chat, chat.unviewedMessagesCount > 0, chat.unviewedMentionsCount) @@ -1424,7 +1436,7 @@ proc addOrUpdateChat(self: Module, result = self.addNewChat( chat, - channelGroup, + community, belongsToCommunity, events.eventsEmitter(), settingsService, @@ -1456,7 +1468,7 @@ method addOrUpdateChat*(self: Module, ): chat_item.Item = result = self.addOrUpdateChat( chat, - ChannelGroupDto(), + CommunityDto(), belongsToCommunity, events, settingsService, diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 32099eec1c..9332268bf5 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -109,10 +109,8 @@ proc delete*(self: Controller) = discard proc init*(self: Controller) = - self.events.on(SIGNAL_CHANNEL_GROUPS_LOADED) do(e:Args): - let args = ChannelGroupsArgs(e) - self.delegate.onChannelGroupsLoaded( - args.channelGroups, + self.events.on(SIGNAL_ACTIVE_CHATS_LOADED) do(e:Args): + self.delegate.onChatsLoaded( self.events, self.settingsService, self.nodeConfigurationService, @@ -145,7 +143,7 @@ proc init*(self: Controller) = self.networksService, ) - self.events.on(SIGNAL_CHANNEL_GROUPS_LOADING_FAILED) do(e:Args): + self.events.on(SIGNAL_CHATS_LOADING_FAILED) do(e:Args): self.delegate.onChatsLoadingFailed() self.events.on(SIGNAL_ACTIVE_MAILSERVER_CHANGED) do(e:Args): @@ -489,9 +487,6 @@ proc init*(self: Controller) = proc isConnected*(self: Controller): bool = return self.nodeService.isConnected() -proc getChannelGroups*(self: Controller): seq[ChannelGroupDto] = - return self.chatService.getChannelGroups() - proc getActiveSectionId*(self: Controller): string = result = self.activeSectionId @@ -535,6 +530,9 @@ proc switchTo*(self: Controller, sectionId, chatId, messageId: string) = let data = ActiveSectionChatArgs(sectionId: sectionId, chatId: chatId, messageId: messageId) self.events.emit(SIGNAL_MAKE_SECTION_CHAT_ACTIVE, data) +proc getJoinedAndSpectatedCommunities*(self: Controller): seq[CommunityDto] = + return self.communityService.getJoinedAndSpectatedCommunities() + proc getCommunityById*(self: Controller, communityId: string): CommunityDto = return self.communityService.getCommunityById(communityId) diff --git a/src/app/modules/main/io_interface.nim b/src/app/modules/main/io_interface.nim index c810cefc1f..37a72d3e85 100644 --- a/src/app/modules/main/io_interface.nim +++ b/src/app/modules/main/io_interface.nim @@ -84,9 +84,8 @@ method chatSectionDidLoad*(self: AccessInterface) {.base.} = method communitySectionDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") -method onChannelGroupsLoaded*( +method onChatsLoaded*( self: AccessInterface, - channelGroups: seq[ChannelGroupDto], events: EventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index d0e8898d41..4d5dc438a2 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -91,7 +91,7 @@ type view: View viewVariant: QVariant controller: Controller - channelGroupModules: OrderedTable[string, chat_section_module.AccessInterface] + chatSectionModules: OrderedTable[string, chat_section_module.AccessInterface] events: EventEmitter urlsManager: UrlsManager keycardService: keycard_service.Service @@ -207,7 +207,7 @@ proc newModule*[T]( result.keychainService = keychainService # Submodules - result.channelGroupModules = initOrderedTable[string, chat_section_module.AccessInterface]() + result.chatSectionModules = initOrderedTable[string, chat_section_module.AccessInterface]() result.walletSectionModule = wallet_section_module.newModule( result, events, tokenService, collectibleService, currencyService, transactionService, walletAccountService, @@ -246,9 +246,9 @@ method delete*[T](self: Module[T]) = self.gifsModule.delete self.activityCenterModule.delete self.communitiesModule.delete - for cModule in self.channelGroupModules.values: + for cModule in self.chatSectionModules.values: cModule.delete - self.channelGroupModules.clear + self.chatSectionModules.clear self.walletSectionModule.delete self.browserSectionModule.delete self.appSearchModule.delete @@ -321,27 +321,24 @@ method onCommunityTokensDetailsLoaded[T](self: Module[T], communityId: string, ) self.view.model().setTokenItems(communityId, communityTokensItems) -proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): SectionItem = - let isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community - var communityDetails: CommunityDto +proc createCommunitySectionItem[T](self: Module[T], communityDetails: CommunityDto): SectionItem = var communityTokensItems: seq[TokenItem] - if (isCommunity): - communityDetails = self.controller.getCommunityById(channelGroup.id) - if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster: - self.controller.getCommunityTokensDetailsAsync(channelGroup.id) - # Get community members' revealed accounts - # We will update the model later when we finish loading the accounts - self.controller.asyncGetRevealedAccountsForAllMembers(channelGroup.id) + if communityDetails.memberRole == MemberRole.Owner or communityDetails.memberRole == MemberRole.TokenMaster: + self.controller.getCommunityTokensDetailsAsync(communityDetails.id) + + # Get community members' revealed accounts + # We will update the model later when we finish loading the accounts + self.controller.asyncGetRevealedAccountsForAllMembers(communityDetails.id) + + let (unviewedCount, notificationsCount) = self.controller.sectionUnreadMessagesAndMentionsCount(communityDetails.id) - let unviewedCount = channelGroup.unviewedMessagesCount - let notificationsCount = channelGroup.unviewedMentionsCount let hasNotification = unviewedCount > 0 or notificationsCount > 0 - let active = self.getActiveSectionId() == channelGroup.id # We must pass on if the current item section is currently active to keep that property as it is + let active = self.getActiveSectionId() == communityDetails.id # We must pass on if the current item section is currently active to keep that property as it is # Add members who were kicked from the community after the ownership change for auto-rejoin after they share addresses - var members = channelGroup.members + var members = communityDetails.members for requestForAutoRejoin in communityDetails.waitingForSharedAddressesRequestsToJoin: var chatMember = ChatMember() chatMember.id = requestForAutoRejoin.publicKey @@ -349,7 +346,6 @@ proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): chatMember.role = MemberRole.None members.add(chatMember) - var bannedMembers = newSeq[MemberItem]() for memberId, memberState in communityDetails.pendingAndBannedMembers.pairs: let state = memberState.toMembershipRequestState() @@ -360,32 +356,32 @@ proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): discard result = initItem( - channelGroup.id, - if isCommunity: SectionType.Community else: SectionType.Chat, - if isCommunity: channelGroup.name else: conf.CHAT_SECTION_NAME, - channelGroup.memberRole, - if isCommunity: communityDetails.isControlNode else: false, - channelGroup.description, - channelGroup.introMessage, - channelGroup.outroMessage, - channelGroup.images.thumbnail, - channelGroup.images.banner, - icon = if (isCommunity): "" else: conf.CHAT_SECTION_ICON, - channelGroup.color, - if isCommunity: communityDetails.tags else: "", + communityDetails.id, + sectionType = SectionType.Community, + communityDetails.name, + communityDetails.memberRole, + communityDetails.isControlNode, + communityDetails.description, + communityDetails.introMessage, + communityDetails.outroMessage, + communityDetails.images.thumbnail, + communityDetails.images.banner, + icon = "", + communityDetails.color, + communityDetails.tags, hasNotification, notificationsCount, active, enabled = true, - if (isCommunity): communityDetails.joined else: true, - if (isCommunity): communityDetails.canJoin else: true, - if (isCommunity): communityDetails.spectated else: false, - channelGroup.canManageUsers, - if (isCommunity): communityDetails.canRequestAccess else: true, - if (isCommunity): communityDetails.isMember else: true, - channelGroup.permissions.access, - channelGroup.permissions.ensOnly, - channelGroup.muted, + communityDetails.joined, + communityDetails.canJoin, + communityDetails.spectated, + communityDetails.canManageUsers, + communityDetails.canRequestAccess, + communityDetails.isMember, + communityDetails.permissions.access, + communityDetails.permissions.ensOnly, + communityDetails.muted, # members members.map(proc(member: ChatMember): MemberItem = let contactDetails = self.controller.getContactDetails(member.id) @@ -400,30 +396,30 @@ proc createChannelGroupItem[T](self: Module[T], channelGroup: ChannelGroupDto): result = self.createMemberItem(member.id, "", state, member.role) ), # pendingRequestsToJoin - if (isCommunity): communityDetails.pendingRequestsToJoin.map(x => pending_request_item.initItem( + communityDetails.pendingRequestsToJoin.map(x => pending_request_item.initItem( x.id, x.publicKey, x.chatId, x.communityId, x.state, x.our - )) else: @[], + )), communityDetails.settings.historyArchiveSupportEnabled, communityDetails.adminSettings.pinMessageAllMembersEnabled, bannedMembers, # pendingMemberRequests - if (isCommunity): communityDetails.pendingRequestsToJoin.map(proc(requestDto: CommunityMembershipRequestDto): MemberItem = + communityDetails.pendingRequestsToJoin.map(proc(requestDto: CommunityMembershipRequestDto): MemberItem = result = self.createMemberItem(requestDto.publicKey, requestDto.id, MembershipRequestState(requestDto.state), MemberRole.None) - ) else: @[], + ), # declinedMemberRequests - if (isCommunity): communityDetails.declinedRequestsToJoin.map(proc(requestDto: CommunityMembershipRequestDto): MemberItem = + communityDetails.declinedRequestsToJoin.map(proc(requestDto: CommunityMembershipRequestDto): MemberItem = result = self.createMemberItem(requestDto.publicKey, requestDto.id, MembershipRequestState(requestDto.state), MemberRole.None) - ) else: @[], - channelGroup.encrypted, + ), + communityDetails.encrypted, communityTokensItems, - channelGroup.pubsubTopic, - channelGroup.pubsubTopicKey, - channelGroup.shard.index, + communityDetails.pubsubTopic, + communityDetails.pubsubTopicKey, + communityDetails.shard.index, ) proc connectForNotificationsOnly[T](self: Module[T]) = @@ -623,9 +619,8 @@ method load*[T]( else: self.setActiveSection(activeSection) -method onChannelGroupsLoaded*[T]( +method onChatsLoaded*[T]( self: Module[T], - channelGroups: seq[ChannelGroupDto], events: EventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, @@ -643,17 +638,63 @@ method onChannelGroupsLoaded*[T]( self.chatsLoaded = true if not self.communityDataLoaded: return + + let myPubKey = singletonInstance.userProfile.getPubKey() + var activeSection: SectionItem var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection() if activeSectionId == "" or activeSectionId == conf.SETTINGS_SECTION_ID: - activeSectionId = singletonInstance.userProfile.getPubKey() + activeSectionId = myPubKey - for channelGroup in channelGroups: - self.channelGroupModules[channelGroup.id] = chat_section_module.newModule( + # Create personal chat section + self.chatSectionModules[myPubKey] = chat_section_module.newModule( + self, + events, + sectionId = myPubKey, + isCommunity = false, + settingsService, + nodeConfigurationService, + contactsService, + chatService, + communityService, + messageService, + mailserversService, + walletAccountService, + tokenService, + communityTokensService, + sharedUrlsService, + networkService + ) + let (unviewedMessagesCount, unviewedMentionsCount) = self.controller.sectionUnreadMessagesAndMentionsCount(myPubKey) + let personalChatSectionItem = initItem( + myPubKey, + sectionType = SectionType.Chat, + name = conf.CHAT_SECTION_NAME, + icon = conf.CHAT_SECTION_ICON, + hasNotification = unviewedMessagesCount > 0 or unviewedMentionsCount > 0, + notificationsCount = unviewedMentionsCount, + active = self.getActiveSectionId() == myPubKey, + enabled = true, + joined = true, + canJoin = true, + canRequestAccess = true, + isMember = true, + muted = false, + ) + self.view.model().addItem(personalChatSectionItem) + if activeSectionId == personalChatSectionItem.id: + activeSection = personalChatSectionItem + + self.chatSectionModules[myPubKey].load() + + let communities = self.controller.getJoinedAndSpectatedCommunities() + # Create Community sections + for community in communities: + self.chatSectionModules[community.id] = chat_section_module.newModule( self, events, - channelGroup.id, - isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community, + community.id, + isCommunity = true, settingsService, nodeConfigurationService, contactsService, @@ -667,12 +708,12 @@ method onChannelGroupsLoaded*[T]( sharedUrlsService, networkService ) - let channelGroupItem = self.createChannelGroupItem(channelGroup) - self.view.model().addItem(channelGroupItem) - if activeSectionId == channelGroupItem.id: - activeSection = channelGroupItem + let communitySectionItem = self.createCommunitySectionItem(community) + self.view.model().addItem(communitySectionItem) + if activeSectionId == communitySectionItem.id: + activeSection = communitySectionItem - self.channelGroupModules[channelGroup.id].load() + self.chatSectionModules[community.id].load() # Set active section if it is one of the channel sections if not activeSection.isEmpty(): @@ -705,8 +746,7 @@ method onCommunityDataLoaded*[T]( if not self.chatsLoaded: return - self.onChannelGroupsLoaded( - self.controller.getChannelGroups(), + self.onChatsLoaded( events, settingsService, nodeConfigurationService, @@ -729,7 +769,7 @@ proc checkIfModuleDidLoad [T](self: Module[T]) = if self.moduleLoaded: return - for cModule in self.channelGroupModules.values: + for cModule in self.chatSectionModules.values: if(not cModule.isLoaded()): return @@ -839,7 +879,7 @@ method setActiveSectionById*[T](self: Module[T], id: string) = self.setActiveSection(item) proc notifySubModulesAboutChange[T](self: Module[T], sectionId: string) = - for cModule in self.channelGroupModules.values: + for cModule in self.chatSectionModules.values: cModule.onActiveSectionChange(sectionId) # If there is a need other section may be notified the same way from here... @@ -888,17 +928,17 @@ method setCurrentUserStatus*[T](self: Module[T], status: StatusType) = self.controller.setCurrentUserStatus(status) proc getChatSectionModule*[T](self: Module[T]): chat_section_module.AccessInterface = - return self.channelGroupModules[singletonInstance.userProfile.getPubKey()] + return self.chatSectionModules[singletonInstance.userProfile.getPubKey()] method getChatSectionModuleAsVariant*[T](self: Module[T]): QVariant = return self.getChatSectionModule().getModuleAsVariant() method getCommunitySectionModule*[T](self: Module[T], communityId: string): QVariant = - if(not self.channelGroupModules.contains(communityId)): + if(not self.chatSectionModules.contains(communityId)): echo "main-module, unexisting community key: ", communityId return - return self.channelGroupModules[communityId].getModuleAsVariant() + return self.chatSectionModules[communityId].getModuleAsVariant() method rebuildChatSearchModel*[T](self: Module[T]) = var items: seq[chat_search_item.Item] = @[] @@ -985,13 +1025,13 @@ method communityJoined*[T]( networkService: network_service.Service, setActive: bool = false, ) = - if self.channelGroupModules.contains(community.id): + if self.chatSectionModules.contains(community.id): # The community is already spectated return var firstCommunityJoined = false - if (self.channelGroupModules.len == 1): # First one is personal chat section + if (self.chatSectionModules.len == 1): # First one is personal chat section firstCommunityJoined = true - self.channelGroupModules[community.id] = chat_section_module.newModule( + self.chatSectionModules[community.id] = chat_section_module.newModule( self, events, community.id, @@ -1009,10 +1049,9 @@ method communityJoined*[T]( sharedUrlsService, networkService ) - let channelGroup = community.toChannelGroupDto() - self.channelGroupModules[community.id].load() + self.chatSectionModules[community.id].load() - let communitySectionItem = self.createChannelGroupItem(channelGroup) + let communitySectionItem = self.createCommunitySectionItem(community) if (firstCommunityJoined): # If there are no other communities, add the first community after the Chat section in the model so that the order is respected self.view.model().addItem(communitySectionItem, @@ -1022,11 +1061,12 @@ method communityJoined*[T]( if setActive: self.setActiveSection(communitySectionItem) - if channelGroup.chats.len > 0: - self.channelGroupModules[community.id].setActiveItem(channelGroup.chats[0].id) + if(community.chats.len > 0): + let chatId = community.chats[0].id + self.chatSectionModules[community.id].setActiveItem(chatId) method communityLeft*[T](self: Module[T], communityId: string) = - if(not self.channelGroupModules.contains(communityId)): + if(not self.chatSectionModules.contains(communityId)): echo "main-module, unexisting community key to leave: ", communityId return @@ -1039,24 +1079,23 @@ method communityLeft*[T](self: Module[T], communityId: string) = self.setActiveSection(item) var moduleToDelete: chat_section_module.AccessInterface - discard self.channelGroupModules.pop(communityId, moduleToDelete) + discard self.chatSectionModules.pop(communityId, moduleToDelete) moduleToDelete.delete moduleToDelete = nil method communityEdited*[T]( self: Module[T], community: CommunityDto) = - if(not self.channelGroupModules.contains(community.id)): + if(not self.chatSectionModules.contains(community.id)): return - let channelGroup = community.toChannelGroupDto() - var channelGroupItem = self.createChannelGroupItem(channelGroup) + var communitySectionItem = self.createCommunitySectionItem(community) # We need to calculate the unread counts because the community update doesn't come with it let (unviewedMessagesCount, unviewedMentionsCount) = self.controller.sectionUnreadMessagesAndMentionsCount( - channelGroupItem.id + communitySectionItem.id ) - channelGroupItem.setHasNotification(unviewedMessagesCount > 0) - channelGroupItem.setNotificationsCount(unviewedMentionsCount) - self.view.editItem(channelGroupItem) + communitySectionItem.setHasNotification(unviewedMessagesCount > 0) + communitySectionItem.setNotificationsCount(unviewedMentionsCount) + self.view.editItem(communitySectionItem) method onCommunityMuted*[T]( self: Module[T], @@ -1595,8 +1634,8 @@ method activateStatusDeepLink*[T](self: Module[T], statusDeepLink: string) = return method onDeactivateChatLoader*[T](self: Module[T], sectionId: string, chatId: string) = - if (sectionId.len > 0 and self.channelGroupModules.contains(sectionId)): - self.channelGroupModules[sectionId].onDeactivateChatLoader(chatId) + if (sectionId.len > 0 and self.chatSectionModules.contains(sectionId)): + self.chatSectionModules[sectionId].onDeactivateChatLoader(chatId) method windowActivated*[T](self: Module[T]) = self.controller.slowdownArchivesImport() @@ -1646,12 +1685,12 @@ method checkIfAddressWasCopied*[T](self: Module[T], value: string) = self.addressWasShown(value) method openSectionChatAndMessage*[T](self: Module[T], sectionId: string, chatId: string, messageId: string) = - if sectionId in self.channelGroupModules: - self.channelGroupModules[sectionId].openCommunityChatAndScrollToMessage(chatId, messageId) + if sectionId in self.chatSectionModules: + self.chatSectionModules[sectionId].openCommunityChatAndScrollToMessage(chatId, messageId) method updateRequestToJoinState*[T](self: Module[T], sectionId: string, requestToJoinState: RequestToJoinState) = - if sectionId in self.channelGroupModules: - self.channelGroupModules[sectionId].updateRequestToJoinState(requestToJoinState) + if sectionId in self.chatSectionModules: + self.chatSectionModules[sectionId].updateRequestToJoinState(requestToJoinState) proc createMemberItem*[T](self: Module[T], memberId: string, requestId: string, state: MembershipRequestState, role: MemberRole): MemberItem = let contactDetails = self.controller.getContactDetails(memberId) diff --git a/src/app/modules/main/profile_section/module.nim b/src/app/modules/main/profile_section/module.nim index f86080998c..93a5862594 100644 --- a/src/app/modules/main/profile_section/module.nim +++ b/src/app/modules/main/profile_section/module.nim @@ -105,7 +105,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, result.devicesModule = devices_module.newModule(result, events, settingsService, devicesService) result.syncModule = sync_module.newModule(result, events, settingsService, nodeConfigurationService, mailserversService) result.wakuModule = waku_module.newModule(result, events, settingsService, nodeConfigurationService) - result.notificationsModule = notifications_module.newModule(result, events, settingsService, chatService, contactsService) + result.notificationsModule = notifications_module.newModule(result, events, settingsService, chatService, contactsService, communityService) result.ensUsernamesModule = ens_usernames_module.newModule( result, events, settingsService, ensService, walletAccountService, networkService, tokenService, keycardService ) diff --git a/src/app/modules/main/profile_section/notifications/controller.nim b/src/app/modules/main/profile_section/notifications/controller.nim index 08e2b54489..24a02c703e 100644 --- a/src/app/modules/main/profile_section/notifications/controller.nim +++ b/src/app/modules/main/profile_section/notifications/controller.nim @@ -17,23 +17,41 @@ type settingsService: settings_service.Service chatService: chat_service.Service contactService: contact_service.Service + communityService: community_service.Service + chatsLoaded: bool + communitiesLoaded: bool proc newController*(delegate: io_interface.AccessInterface, - events: EventEmitter, - settingsService: settings_service.Service, - chatService: chat_service.Service, - contactService: contact_service.Service): Controller = + events: EventEmitter, + settingsService: settings_service.Service, + chatService: chat_service.Service, + contactService: contact_service.Service, + communityService: community_service.Service, + ): Controller = result = Controller() result.delegate = delegate result.events = events result.settingsService = settingsService result.chatService = chatService result.contactService = contactService + result.communityService = communityService + result.chatsLoaded = false + result.communitiesLoaded = false proc delete*(self: Controller) = discard proc init*(self: Controller) = + self.events.on(SIGNAL_ACTIVE_CHATS_LOADED) do(e:Args): + self.chatsLoaded = true + if self.communitiesLoaded: + self.delegate.initModel() + + self.events.on(SIGNAL_COMMUNITY_DATA_LOADED) do(e:Args): + self.communitiesLoaded = true + if self.chatsLoaded: + self.delegate.initModel() + self.events.on(SIGNAL_COMMUNITY_JOINED) do(e:Args): let args = CommunityArgs(e) if(args.error.len > 0): @@ -95,11 +113,14 @@ proc setNotifSettingExemptions*(self: Controller, id: string, exemptions: Notifi proc removeNotifSettingExemptions*(self: Controller, id: string): bool = return self.settingsService.removeNotifSettingExemptions(id) -proc getChannelGroups*(self: Controller): seq[ChannelGroupDto] = - return self.chatService.getChannelGroups() +proc getChatsForPersonalSection*(self: Controller): seq[ChatDto] = + return self.chatService.getChatsForPersonalSection() proc getChatDetails*(self: Controller, chatId: string): ChatDto = return self.chatService.getChatById(chatId) - + proc getContactDetails*(self: Controller, id: string): ContactDetails = return self.contactService.getContactDetails(id) + +proc getJoinedAndSpectatedCommunities*(self: Controller): seq[CommunityDto] = + return self.communityService.getJoinedAndSpectatedCommunities() diff --git a/src/app/modules/main/profile_section/notifications/io_interface.nim b/src/app/modules/main/profile_section/notifications/io_interface.nim index c63f286a67..e7d47fe7b7 100644 --- a/src/app/modules/main/profile_section/notifications/io_interface.nim +++ b/src/app/modules/main/profile_section/notifications/io_interface.nim @@ -14,6 +14,9 @@ method load*(self: AccessInterface) {.base.} = method viewDidLoad*(self: AccessInterface) {.base.} = raise newException(ValueError, "No implementation available") +method initModel*(self: AccessInterface) {.base.} = + raise newException(ValueError, "No implementation available") + method isLoaded*(self: AccessInterface): bool {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/profile_section/notifications/module.nim b/src/app/modules/main/profile_section/notifications/module.nim index 77946b0905..21e2ab4931 100644 --- a/src/app/modules/main/profile_section/notifications/module.nim +++ b/src/app/modules/main/profile_section/notifications/module.nim @@ -8,6 +8,7 @@ import ../../../../core/eventemitter import ../../../../../app_service/service/settings/service as settings_service import ../../../../../app_service/service/chat/service as chat_service import ../../../../../app_service/service/contacts/service as contact_service +import ../../../../../app_service/service/community/service as community_service from ../../../../../app_service/service/community/dto/community import CommunityDto export io_interface @@ -24,15 +25,18 @@ type moduleLoaded: bool proc newModule*(delegate: delegate_interface.AccessInterface, - events: EventEmitter, - settingsService: settings_service.Service, - chatService: chat_service.Service, - contactService: contact_service.Service): Module = + events: EventEmitter, + settingsService: settings_service.Service, + chatService: chat_service.Service, + contactService: contact_service.Service, + communityService: community_service.Service, + ): Module = result = Module() result.delegate = delegate result.view = view.newView(result) result.viewVariant = newQVariant(result.view) - result.controller = controller.newController(result, events, settingsService, chatService, contactService) + result.controller = controller.newController(result, events, settingsService, chatService, contactService, + communityService) result.moduleLoaded = false method delete*(self: Module) = @@ -70,26 +74,34 @@ proc createChatItem(self: Module, chatDto: ChatDto): Item = return self.createItem(chatDto.id, chatName, chatImage, chatDto.color, chatDto.joined, itemType) -proc initModel(self: Module) = - let channelGroups = self.controller.getChannelGroups() +method initModel(self: Module) = var items: seq[Item] - for cg in channelGroups: - if cg.channelGroupType == ChannelGroupType.Community: - let item = self.createItem(cg.id, cg.name, cg.images.thumbnail, cg.color, joinedTimestamp = 0, item.Type.Community) - items.add(item) - elif cg.channelGroupType == ChannelGroupType.Personal: - for c in cg.chats: - if c.chatType != ChatType.OneToOne and c.chatType != ChatType.PrivateGroupChat: - continue - let item = self.createChatItem(c) - items.add(item) + # Add personal section + let personalChats = self.controller.getChatsForPersonalSection() + for c in personalChats: + if c.chatType != ChatType.OneToOne and c.chatType != ChatType.PrivateGroupChat: + continue + let item = self.createChatItem(c) + items.add(item) + + # Add communities + let communities = self.controller.getJoinedAndSpectatedCommunities() + for community in communities: + let item = self.createItem( + community.id, + community.name, + community.images.thumbnail, + community.color, + joinedTimestamp = 0, + item.Type.Community + ) + items.add(item) # Sort to get most recent first items.sort(comp, SortOrder.Descending) self.view.exemptionsModel().setItems(items) method viewDidLoad*(self: Module) = - self.initModel() self.moduleLoaded = true self.delegate.notificationsModuleDidLoad() diff --git a/src/app_service/service/chat/async_tasks.nim b/src/app_service/service/chat/async_tasks.nim index 1a93ee5a6c..2ae50da443 100644 --- a/src/app_service/service/chat/async_tasks.nim +++ b/src/app_service/service/chat/async_tasks.nim @@ -1,16 +1,17 @@ ################################################# -# Async get chats (channel groups) +# Async get chats ################################################# -type - AsyncGetChannelGroupsTaskArg = ref object of QObjectTaskArg -const asyncGetChannelGroupsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = - let arg = decode[AsyncGetChannelGroupsTaskArg](argEncoded) +type + AsyncGetActiveChatsTaskArg = ref object of QObjectTaskArg + +const asyncGetActiveChatsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} = + let arg = decode[AsyncGetActiveChatsTaskArg](argEncoded) try: - let response = status_chat.getChannelGroups() + let response = status_chat.getActiveChats() let responseJson = %*{ - "channelGroups": response.result, + "chats": response.result, "error": "", } arg.finish(responseJson) diff --git a/src/app_service/service/chat/dto/chat.nim b/src/app_service/service/chat/dto/chat.nim index 98a4c047fa..92af5a7967 100644 --- a/src/app_service/service/chat/dto/chat.nim +++ b/src/app_service/service/chat/dto/chat.nim @@ -16,7 +16,7 @@ type ChatType* {.pure.}= enum Timeline {.deprecated.} = 5, CommunityChat = 6 -type ChannelGroupType* {.pure.}= enum +type ChatSectionType* {.pure.}= enum Unknown = "unknown", Personal = "personal", Community = "community" @@ -93,35 +93,6 @@ type ChatDto* = object permissions*: Permission hideIfPermissionsNotMet*: bool -type ChannelGroupDto* = object - id*: string - channelGroupType*: ChannelGroupType - memberRole*: MemberRole - verified*: bool - name*: string - ensName*: string - description*: string - introMessage*: string - outroMessage*: string - chats*: seq[ChatDto] - categories*: seq[Category] - images*: Images - permissions*: Permission - members*: seq[ChatMember] - canManageUsers*: bool - color*: string - muted*: bool - historyArchiveSupportEnabled*: bool - pinMessageAllMembersEnabled*: bool - bannedMembersIds*: seq[string] - encrypted*: bool - unviewedMessagesCount*: int - unviewedMentionsCount*: int - channelPermissions*: CheckAllChannelsPermissionsResponseDto - pubsubTopic*: string - pubsubTopicKey*: string - shard*: Shard - type ClearedHistoryDto* = object chatId*: string clearedAt*: int @@ -324,74 +295,6 @@ proc toChatDto*(jsonObj: JsonNode): ChatDto = if (result.communityId != "" and not result.id.contains(result.communityId)): result.id = result.communityId & result.id -proc toChannelGroupDto*(jsonObj: JsonNode): ChannelGroupDto = - result = ChannelGroupDto() - - discard jsonObj.getProp("verified", result.verified) - discard jsonObj.getProp("memberRole", result.memberRole) - discard jsonObj.getProp("name", result.name) - discard jsonObj.getProp("description", result.description) - discard jsonObj.getProp("introMessage", result.introMessage) - discard jsonObj.getProp("outroMessage", result.outroMessage) - discard jsonObj.getProp("encrypted", result.encrypted) - discard jsonObj.getProp("unviewedMessagesCount", result.unviewedMessagesCount) - discard jsonObj.getProp("unviewedMentionsCount", result.unviewedMentionsCount) - - result.channelGroupType = ChannelGroupType.Unknown - var channelGroupTypeString: string - if (jsonObj.getProp("channelGroupType", channelGroupTypeString)): - result.channelGroupType = parseEnum[ChannelGroupType](channelGroupTypeString) - - var chatsObj: JsonNode - if(jsonObj.getProp("chats", chatsObj)): - for _, chatObj in chatsObj: - let chat = toChatDto(chatObj) - if (chat.chatType == ChatType.Public): - # Filter out public chats as we don't show them anymore - continue - result.chats.add(chat) - - var categoriesObj: JsonNode - if(jsonObj.getProp("categories", categoriesObj)): - for _, categoryObj in categoriesObj: - result.categories.add(toCategory(categoryObj)) - - var imagesObj: JsonNode - if(jsonObj.getProp("images", imagesObj)): - result.images = toImages(imagesObj) - - var permissionObj: JsonNode - if(jsonObj.getProp("permissions", permissionObj)): - result.permissions = toPermission(permissionObj) - - var membersObj: JsonNode - if(jsonObj.getProp("members", membersObj) and membersObj.kind == JObject): - for memberId, memberObj in membersObj: - result.members.add(toChannelMember(memberObj, memberId, joined = true)) - - var bannedMembersIdsObj: JsonNode - if(jsonObj.getProp("banList", bannedMembersIdsObj) and bannedMembersIdsObj.kind == JArray): - for bannedMemberId in bannedMembersIdsObj: - result.bannedMembersIds.add(bannedMemberId.getStr) - - discard jsonObj.getProp("canManageUsers", result.canManageUsers) - discard jsonObj.getProp("color", result.color) - discard jsonObj.getProp("muted", result.muted) - - var responseDto = CheckAllChannelsPermissionsResponseDto() - responseDto.channels = initTable[string, CheckChannelPermissionsResponseDto]() - result.channelPermissions = responseDto - var checkChannelPermissionResponsesObj: JsonNode - if(jsonObj.getProp("checkChannelPermissionResponses", checkChannelPermissionResponsesObj) and checkChannelPermissionResponsesObj.kind == JObject): - - for channelId, permissionResponse in checkChannelPermissionResponsesObj: - result.channelPermissions.channels[channelId] = permissionResponse.toCheckChannelPermissionsResponseDto() - - discard jsonObj.getProp("pubsubTopic", result.pubsubTopic) - discard jsonObj.getProp("pubsubTopicKey", result.pubsubTopicKey) - - result.shard = jsonObj.getShard() - # To parse Community chats to ChatDto, we need to add the commuity ID and type proc toChatDto*(jsonObj: JsonNode, communityId: string): ChatDto = result = jsonObj.toChatDto() @@ -433,3 +336,12 @@ proc updateMissingFields*(chatToUpdate: var ChatDto, oldChat: ChatDto) = chatToUpdate.viewersCanPostReactions = oldChat.viewersCanPostReactions chatToUpdate.categoryId = oldChat.categoryId chatToUpdate.members = oldChat.members + +proc isOneToOne*(c: ChatDto): bool = + return c.chatType == ChatType.OneToOne + +proc isPrivateGroupChat*(c: ChatDto): bool = + return c.chatType == ChatType.PrivateGroupChat + +proc isActivePersonalChat*(c: ChatDto): bool = + return c.active and (c.isOneToOne() or c.isPrivateGroupChat()) and c.communityId == "" diff --git a/src/app_service/service/chat/service.nim b/src/app_service/service/chat/service.nim index 0604368448..0a30b91e09 100644 --- a/src/app_service/service/chat/service.nim +++ b/src/app_service/service/chat/service.nim @@ -1,4 +1,4 @@ -import NimQml, Tables, json, sequtils, stew/shims/strformat, chronicles, os, std/algorithm, strutils, uuids, base64 +import NimQml, Tables, json, sequtils, stew/shims/strformat, chronicles, os, strutils, uuids, base64 import std/[times, os] import ../../../app/core/tasks/[qt, threadpool] @@ -30,12 +30,6 @@ include ../../../app/core/tasks/common include async_tasks type - ChannelGroupsArgs* = ref object of Args - channelGroups*: seq[ChannelGroupDto] - - ChannelGroupArgs* = ref object of Args - channelGroup*: ChannelGroupDto - ChatUpdateArgs* = ref object of Args chats*: seq[ChatDto] @@ -102,8 +96,8 @@ type error*: string # Signals which may be emitted by this service: -const SIGNAL_CHANNEL_GROUPS_LOADED* = "channelGroupsLoaded" -const SIGNAL_CHANNEL_GROUPS_LOADING_FAILED* = "channelGroupsLoadingFailed" +const SIGNAL_ACTIVE_CHATS_LOADED* = "activeChatsLoaded" +const SIGNAL_CHATS_LOADING_FAILED* = "chatsLoadingFailed" const SIGNAL_CHAT_UPDATE* = "chatUpdate" const SIGNAL_CHAT_LEFT* = "channelLeft" const SIGNAL_SENDING_FAILED* = "messageSendingFailed" @@ -131,7 +125,6 @@ QtObject: threadpool: ThreadPool events: EventEmitter chats: Table[string, ChatDto] # [chat_id, ChatDto] - channelGroups: OrderedTable[string, ChannelGroupDto] # [chatGroup_id, ChannelGroupDto] contactService: contact_service.Service proc delete*(self: Service) = @@ -148,12 +141,9 @@ QtObject: result.threadpool = threadpool result.contactService = contactService result.chats = initTable[string, ChatDto]() - result.channelGroups = initOrderedTable[string, ChannelGroupDto]() # Forward declarations proc updateOrAddChat*(self: Service, chat: ChatDto) - proc hydrateChannelGroups*(self: Service, data: JsonNode) - proc updateOrAddChannelGroup*(self: Service, channelGroup: ChannelGroupDto, isCommunityChannelGroup: bool = false) proc processMessengerResponse*(self: Service, response: RpcResponse[JsonNode]): (seq[ChatDto], seq[MessageDto]) proc doConnect(self: Service) = @@ -187,126 +177,63 @@ QtObject: for clearedHistoryDto in receivedData.clearedHistories: self.events.emit(SIGNAL_CHAT_HISTORY_CLEARED, ChatArgs(chatId: clearedHistoryDto.chatId)) - # Handling community updates - if (receivedData.communities.len > 0): - for community in receivedData.communities: - if community.joined: - self.updateOrAddChannelGroup(community.toChannelGroupDto(), isCommunityChannelGroup = true) - self.events.on(SIGNAL_CHAT_REQUEST_UPDATE_AFTER_SEND) do(e: Args): var args = RpcResponseArgs(e) discard self.processMessengerResponse(args.response) - proc getChannelGroups*(self: Service): seq[ChannelGroupDto] = - return toSeq(self.channelGroups.values) - - proc loadChannelGroupById*(self: Service, channelGroupId: string) = - try: - let response = status_chat.getChannelGroupById(channelGroupId) - self.hydrateChannelGroups(response.result) - except Exception as e: - error "error loadChannelGroupById: ", errorDescription = e.msg - - proc asyncGetChannelGroups*(self: Service) = - let arg = AsyncGetChannelGroupsTaskArg( - tptr: cast[ByteAddress](asyncGetChannelGroupsTask), + proc asyncGetActiveChat*(self: Service) = + let arg = AsyncGetActiveChatsTaskArg( + tptr: cast[ByteAddress](asyncGetActiveChatsTask), vptr: cast[ByteAddress](self.vptr), - slot: "onAsyncGetChannelGroupsResponse", + slot: "onAsyncGetActiveChatsResponse", ) self.threadpool.start(arg) - proc sortPersonnalChatAsFirst[T, D](x, y: (T, D)): int = - if (x[1].channelGroupType == Personal): return -1 - if (y[1].channelGroupType == Personal): return 1 - return 0 - - proc hydrateChannelGroups(self: Service, data: JsonNode) = - var chats: seq[ChatDto] = @[] - for (sectionId, section) in data.pairs: - var channelGroup = section.toChannelGroupDto() - channelGroup.id = sectionId - self.channelGroups[sectionId] = channelGroup - for (chatId, chat) in section["chats"].pairs: - chats.add(chat.toChatDto()) - - # Make the personal channelGroup the first one - self.channelGroups.sort(sortPersonnalChatAsFirst[string, ChannelGroupDto], SortOrder.Ascending) - - for chat in chats: + proc hydrateChats(self: Service, data: JsonNode) = + for chatJson in data: + let chat = chatJson.toChatDto() if chat.active and chat.chatType != chat_dto.ChatType.Unknown: - if chat.chatType == chat_dto.ChatType.Public: - # Deactivate old public chats - discard status_chat.deactivateChat(chat.id) - else: self.chats[chat.id] = chat - proc onAsyncGetChannelGroupsResponse*(self: Service, response: string) {.slot.} = + proc onAsyncGetActiveChatsResponse*(self: Service, response: string) {.slot.} = try: let rpcResponseObj = response.parseJson if (rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != ""): raise newException(CatchableError, rpcResponseObj{"error"}.getStr) - if(rpcResponseObj["channelGroups"].kind == JNull): - raise newException(RpcException, "No channel groups returned") + if rpcResponseObj["chats"].kind != JNull: + self.hydrateChats(rpcResponseObj["chats"]) - self.hydrateChannelGroups(rpcResponseObj["channelGroups"]) - self.events.emit(SIGNAL_CHANNEL_GROUPS_LOADED, ChannelGroupsArgs(channelGroups: self.getChannelGroups())) + self.events.emit(SIGNAL_ACTIVE_CHATS_LOADED, Args()) except Exception as e: let errDesription = e.msg - error "error get channel groups: ", errDesription - self.events.emit(SIGNAL_CHANNEL_GROUPS_LOADING_FAILED, Args()) + error "error get active chats: ", errDesription + self.events.emit(SIGNAL_CHATS_LOADING_FAILED, Args()) proc init*(self: Service) = self.doConnect() - self.asyncGetChannelGroups() + self.asyncGetActiveChat() proc hasChannel*(self: Service, chatId: string): bool = self.chats.hasKey(chatId) - - proc getChatIndex*(self: Service, channelGroupId, chatId: string): int = - var i = 0 - - if not self.channelGroups.contains(channelGroupId): - warn "unknown channel group", channelGroupId - return -1 - - for chat in self.channelGroups[channelGroupId].chats: - if (chat.id == chatId): - return i - i.inc() - return -1 - - proc chatsWithCategoryHaveUnreadMessages*(self: Service, communityId: string, categoryId: string): bool = - if communityId == "" or categoryId == "": - return false - - if not self.channelGroups.contains(communityId): - warn "unknown community", communityId - return false - - for chat in self.channelGroups[communityId].chats: - if chat.categoryId != categoryId: - continue - if (not chat.muted and chat.unviewedMessagesCount > 0) or chat.unviewedMentionsCount > 0: - return true - return false - - proc sectionUnreadMessagesAndMentionsCount*(self: Service, communityId: string): + proc sectionUnreadMessagesAndMentionsCount*(self: Service, sectionId: string): tuple[unviewedMessagesCount: int, unviewedMentionsCount: int] = - if communityId == "": - return - - if not self.channelGroups.contains(communityId): - warn "unknown community", communityId - return result.unviewedMentionsCount = 0 result.unviewedMessagesCount = 0 - for chat in self.channelGroups[communityId].chats: + let myPubKey = singletonInstance.userProfile.getPubKey() + var seactionIdToFind = sectionId + if sectionId == myPubKey: + # If the section is the personal one (ID == pubKey), then we set the seactionIdToFind to "" + # because personal chats have communityId == "" + seactionIdToFind = "" + for _, chat in self.chats: + if chat.communityId != seactionIdToFind: + continue result.unviewedMentionsCount += chat.unviewedMentionsCount # We count the unread messages if we are unmuted and it's not a mention, we want to show a badge on mentions if chat.unviewedMentionsCount == 0 and chat.muted: @@ -328,49 +255,6 @@ QtObject: self.chats[chat.id].categoryId = categoryId self.events.emit(SIGNAL_CHAT_ADDED_OR_UPDATED, ChatArgs(communityId: chat.communityId, chatId: chat.id)) - var channelGroupId = chat.communityId - if (channelGroupId == ""): - channelGroupId = singletonInstance.userProfile.getPubKey() - - if not self.channelGroups.contains(channelGroupId): - warn "unknown community for new channel update", channelGroupId - return - - let index = self.getChatIndex(channelGroupId, chat.id) - if (index == -1): - self.channelGroups[channelGroupId].chats.add(self.chats[chat.id]) - else: - 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 - - var chat = newChat - for previousChat in self.channelGroups[channelGroupId].chats: - if previousChat.id != newChat.id: - continue - chat.unviewedMessagesCount = previousChat.unviewedMessagesCount - chat.unviewedMentionsCount = previousChat.unviewedMentionsCount - chat.muted = previousChat.muted - chat.highlight = previousChat.highlight - break - return chat - - # Community channel groups have less info because they come from community signals - proc updateOrAddChannelGroup*(self: Service, channelGroup: ChannelGroupDto, isCommunityChannelGroup: bool = false) = - var newChannelGroup = channelGroup - if isCommunityChannelGroup and self.channelGroups.contains(channelGroup.id): - # 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 @@ -380,12 +264,6 @@ QtObject: self.updateOrAddChat(chat) self.events.emit(SIGNAL_CHAT_MEMBERS_CHANGED, ChatMembersChangedArgs(chatId: chat.id, members: chat.members)) - proc getChannelGroupById*(self: Service, channelGroupId: string): ChannelGroupDto = - if not self.channelGroups.contains(channelGroupId): - warn "Unknown channel group", channelGroupId - return - return self.channelGroups[channelGroupId] - proc parseChatResponse*(self: Service, response: RpcResponse[JsonNode]): (seq[ChatDto], seq[MessageDto]) = var chats: seq[ChatDto] = @[] var messages: seq[MessageDto] = @[] @@ -460,6 +338,12 @@ QtObject: proc getChatsOfChatTypes*(self: Service, types: seq[chat_dto.ChatType]): seq[ChatDto] = return self.getAllChats().filterIt(it.chatType in types) + proc getChatsForPersonalSection*(self: Service): seq[ChatDto] = + return self.getAllChats().filterIt(it.isActivePersonalChat()) + + proc getChatsForCommunity*(self: Service, communityId: string): seq[ChatDto] = + return self.getAllChats().filterIt(it.communityId == communityId) + proc getChatById*(self: Service, chatId: string, showWarning: bool = true): ChatDto = if(not self.chats.contains(chatId)): if (showWarning): @@ -527,11 +411,6 @@ QtObject: discard status_chat.deactivateChat(chatId, preserveHistory = chat.chatType == chat_dto.ChatType.OneToOne) - var channelGroupId = chat.communityId - if (channelGroupId == ""): - channelGroupId = singletonInstance.userProfile.getPubKey() - - self.channelGroups[channelGroupId].chats.delete(self.getChatIndex(channelGroupId, chatId)) self.chats.del(chatId) self.events.emit(SIGNAL_CHAT_LEFT, ChatArgs(chatId: chatId)) except Exception as e: @@ -734,20 +613,11 @@ QtObject: var parsedImage = imageJson.parseJson parsedImage["imagePath"] = %singletonInstance.utils.formatImagePath(parsedImage["imagePath"].getStr) let response = status_chat.editChat(communityID, chatID, name, color, $parsedImage) - if (not response.error.isNil): - let msg = response.error.message & " chatId=" & chatId - error "error while editing group chat details", msg - return - let resultedChat = response.result.toChatDto() + discard self.processMessengerResponse(response) - var chat = self.chats[chatID] - chat.name = name - chat.color = color - chat.icon = resultedChat.icon - self.updateOrAddChat(chat) - - self.events.emit(SIGNAL_GROUP_CHAT_DETAILS_UPDATED, ChatUpdateDetailsArgs(id: chatID, newName: name, newColor: color, newImage: resultedChat.icon)) + let chat = self.chats[chatID] + self.events.emit(SIGNAL_GROUP_CHAT_DETAILS_UPDATED, ChatUpdateDetailsArgs(id: chatID, newName: name, newColor: color, newImage: chat.icon)) except Exception as e: error "error while updating group chat: ", msg = e.msg @@ -781,25 +651,6 @@ QtObject: except Exception as e: error "error while creating group chat", msg = e.msg - proc getMembers*(self: Service, communityID, chatId: string): seq[ChatMember] = - try: - var realChatId = chatId.replace(communityID, "") - let response = status_chat.getMembers(communityID, realChatId) - if response.result.kind == JNull: - # No members. Could be a public chat - return - let myPubkey = singletonInstance.userProfile.getPubKey() - result = @[] - for (id, memberObj) in response.result.pairs: - var member = toChatMember(memberObj, id) - # Make yourself as the first result - if (id == myPubkey): - result.insert(member) - else: - result.add(member) - except Exception as e: - error "error while getting members", msg = e.msg, communityID, chatId - proc updateUnreadMessage*(self: Service, chatID: string, messagesCount:int, messagesWithMentionsCount:int) = var chat = self.getChatById(chatID) if chat.id == "": @@ -842,7 +693,7 @@ QtObject: let communityId = rpcResponseObj{"communityId"}.getStr() let chatId = rpcResponseObj{"chatId"}.getStr() let checkChannelPermissionsResponse = rpcResponseObj["response"]["result"].toCheckChannelPermissionsResponseDto() - self.channelGroups[communityId].channelPermissions.channels[chatId] = checkChannelPermissionsResponse + self.events.emit(SIGNAL_CHECK_CHANNEL_PERMISSIONS_RESPONSE, CheckChannelPermissionsResponseArgs(communityId: communityId, chatId: chatId, checkChannelPermissionsResponse: checkChannelPermissionsResponse)) except Exception as e: let errMsg = e.msg @@ -870,7 +721,7 @@ QtObject: raise newException(RpcException, error.message) let checkAllChannelsPermissionsResponse = rpcResponseObj["response"]["result"].toCheckAllChannelsPermissionsResponseDto() - self.channelGroups[communityId].channelPermissions = checkAllChannelsPermissionsResponse + # TODO save it self.events.emit(SIGNAL_CHECK_ALL_CHANNELS_PERMISSIONS_RESPONSE, CheckAllChannelsPermissionsResponseArgs(communityId: communityId, checkAllChannelsPermissionsResponse: checkAllChannelsPermissionsResponse)) except Exception as e: let errMsg = e.msg diff --git a/src/app_service/service/community/dto/community.nim b/src/app_service/service/community/dto/community.nim index 01c92628b6..21b9e73bbb 100644 --- a/src/app_service/service/community/dto/community.nim +++ b/src/app_service/service/community/dto/community.nim @@ -548,39 +548,6 @@ proc getBannedMembersIds*(self: CommunityDto): seq[string] = bannedIds.add(memberId) return bannedIds -proc toChannelGroupDto*(communityDto: CommunityDto): ChannelGroupDto = - ChannelGroupDto( - id: communityDto.id, - channelGroupType: ChannelGroupType.Community, - name: communityDto.name, - images: communityDto.images, - chats: communityDto.chats, - categories: communityDto.categories, - # Community doesn't have an ensName yet. Add this when it is added in status-go - # ensName: communityDto.ensName, - memberRole: communityDto.memberRole, - verified: communityDto.verified, - description: communityDto.description, - introMessage: communityDto.introMessage, - outroMessage: communityDto.outroMessage, - color: communityDto.color, - # tags: communityDto.tags, NOTE: do we need tags here? - permissions: communityDto.permissions, - members: communityDto.members.map(m => ChatMember( - id: m.id, - joined: true, - role: m.role - )), - canManageUsers: communityDto.canManageUsers, - muted: communityDto.muted, - historyArchiveSupportEnabled: communityDto.settings.historyArchiveSupportEnabled, - bannedMembersIds: communityDto.getBannedMembersIds(), - encrypted: communityDto.encrypted, - shard: communityDto.shard, - pubsubTopic: communityDto.pubsubTopic, - pubsubTopicKey: communityDto.pubsubTopicKey, - ) - proc parseCommunitiesSettings*(response: JsonNode): seq[CommunitySettingsDto] = result = map(response["result"].getElems(), proc(x: JsonNode): CommunitySettingsDto = x.toCommunitySettingsDto()) @@ -628,15 +595,20 @@ proc toMembersRevealedAccounts*(membersRevealedAccountsObj: JsonNode): MembersRe for (pubkey, revealedAccountsObj) in membersRevealedAccountsObj.pairs: result[pubkey] = revealedAccountsObj.toRevealedAccounts() -proc getCommunityChats*(self: CommunityDto, chatsIds: seq[string]): seq[ChatDto] = +proc getCommunityChats*(self: CommunityDto, chatIds: seq[string]): seq[ChatDto] = var chats: seq[ChatDto] = @[] - for chatId in chatsIds: + for chatId in chatIds: for communityChat in self.chats: if chatId == communityChat.id: chats.add(communityChat) break return chats +proc getCommunityChat*(self: CommunityDto, chatId: string): ChatDto = + let chats = self.getCommunityChats(@[chatId]) + if chats.len > 0: + return chats[0] + proc isOwner*(self: CommunityDto): bool = return self.memberRole == MemberRole.Owner diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index 6205d58831..324f44403a 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -306,10 +306,10 @@ QtObject: result.communityMetrics = initTable[string, CommunityMetricsDto]() result.communityInfoRequests = initTable[string, Time]() - proc getFilteredJoinedCommunities(self: Service): Table[string, CommunityDto] = + proc getFilteredJoinedAndSpectatedCommunities(self: Service): Table[string, CommunityDto] = result = initTable[string, CommunityDto]() for communityId, community in self.communities.pairs: - if community.joined: + if community.joined or community.spectated: result[communityId] = community proc getFilteredCuratedCommunities(self: Service): Table[string, CommunityDto] = @@ -650,17 +650,6 @@ QtObject: chat.emoji != prevChat.emoji or chat.viewersCanPostReactions != prevChat.viewersCanPostReactions or chat.hideIfPermissionsNotMet != prevChat.hideIfPermissionsNotMet: var updatedChat = chat - - # TODO improve this in https://github.com/status-im/status-desktop/issues/12595 - # Currently, status-go only sends canPostReactions on app start (getChannelGroups) - # so here, we need to imply it. If viewersCanPostReactions is true, then everyone can post reactions - # admins can also always post reactions - if chat.viewersCanPostReactions or - (not chat.viewersCanPostReactions and community.memberRole != MemberRole.None): - updatedChat.canPostReactions = true - elif not chat.viewersCanPostReactions and community.memberRole == MemberRole.None: - updatedChat.canPostReactions = false - self.chatService.updateOrAddChat(updatedChat) # we have to update chats stored in the chat service. let data = CommunityChatArgs(chat: updatedChat) @@ -845,7 +834,7 @@ QtObject: if self.communities.hasKey(settings.id): self.communities[settings.id].settings = settings - # Non approver requests to join for all communities + # Non approved requests to join for all communities let nonAprrovedRequestsToJoinObj = responseObj["nonAprrovedRequestsToJoin"] if nonAprrovedRequestsToJoinObj{"result"}.kind != JNull: @@ -874,8 +863,8 @@ QtObject: proc getCommunityTags*(self: Service): string = return self.communityTags - proc getJoinedCommunities*(self: Service): seq[CommunityDto] = - return toSeq(self.getFilteredJoinedCommunities().values) + proc getJoinedAndSpectatedCommunities*(self: Service): seq[CommunityDto] = + return toSeq(self.getFilteredJoinedAndSpectatedCommunities().values) proc getAllCommunities*(self: Service): seq[CommunityDto] = return toSeq(self.communities.values) @@ -1056,13 +1045,9 @@ QtObject: updatedCommunity.settings = communitySettings self.communities[communityId] = updatedCommunity - self.chatService.loadChannelGroupById(communityId) let ownerTokenNotification = self.activityCenterService.getNotificationForTypeAndCommunityId(notification.ActivityCenterNotificationType.OwnerTokenReceived, communityId) - self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[updatedCommunity])) - self.events.emit(SIGNAL_COMMUNITY_SPECTATED, CommunityArgs(community: updatedCommunity, fromUserAction: true, isPendingOwnershipRequest: (ownerTokenNotification != nil))) - for k, chat in updatedCommunity.chats: var fullChatId = chat.id if not chat.id.startsWith(communityId): @@ -1077,6 +1062,9 @@ QtObject: # TODO find a way to populate missing infos like the color self.chatService.updateOrAddChat(chatDto) self.messageService.asyncLoadInitialMessagesForChat(fullChatId) + + self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[updatedCommunity])) + self.events.emit(SIGNAL_COMMUNITY_SPECTATED, CommunityArgs(community: updatedCommunity, fromUserAction: true, isPendingOwnershipRequest: (ownerTokenNotification != nil))) except Exception as e: error "Error joining the community", msg = e.msg result = fmt"Error joining the community: {e.msg}" @@ -1225,8 +1213,6 @@ QtObject: community.settings = communitySettings # add this to the communities list and communitiesSettings self.communities[community.id] = community - # add new community channel group and chats to chat service - self.chatService.updateOrAddChannelGroup(community.toChannelGroupDto()) for chat in community.chats: self.chatService.updateOrAddChat(chat) @@ -2495,3 +2481,18 @@ QtObject: self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community])) except Exception as e: error "error promoting self to control node", msg = e.msg + + proc categoryHasUnreadMessages*(self: Service, communityId: string, categoryId: string): bool = + if communityId == "" or categoryId == "": + return false + + if not self.communities.contains(communityId): + warn "unknown community", communityId + return false + + for chat in self.communities[communityId].chats: + if chat.categoryId != categoryId: + continue + if (not chat.muted and chat.unviewedMessagesCount > 0) or chat.unviewedMentionsCount > 0: + return true + return false diff --git a/src/backend/chat.nim b/src/backend/chat.nim index a61a94b5b7..9231d0de40 100644 --- a/src/backend/chat.nim +++ b/src/backend/chat.nim @@ -35,13 +35,9 @@ proc saveChat*( } ]) -proc getChannelGroups*(): RpcResponse[JsonNode] = +proc getActiveChats*(): RpcResponse[JsonNode] = let payload = %* [] - result = callPrivateRPC("chat_getChannelGroups", payload) - -proc getChannelGroupById*(channelGroupId: string): RpcResponse[JsonNode] = - let payload = %* [channelGroupId] - result = callPrivateRPC("chat_getChannelGroupByID", payload) + result = callPrivateRPC("activeChats".prefix, payload) proc createOneToOneChat*(chatId: string, ensName: string = ""): RpcResponse[JsonNode] = let communityId = "" @@ -146,9 +142,6 @@ proc createGroupChatFromInvitation*(groupName: string, chatId: string, adminPK: let payload = %* [groupName, chatId, adminPK] result = callPrivateRPC("createGroupChatFromInvitation".prefix, payload) -proc getMembers*(communityId, chatId: string): RpcResponse[JsonNode] = - result = callPrivateRPC("chat_getMembers", %* [communityId, chatId]) - proc editChat*(communityID: string, chatID: string, name: string, color: string, imageJson: string): RpcResponse[JsonNode] = let croppedImage = newCroppedImage(imageJson) let payload = %* [communityID, chatID, name, color, croppedImage] diff --git a/vendor/status-go b/vendor/status-go index dcc93dee96..8f50b578d1 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit dcc93dee965a39d9b4ee9dca79f546c497baa77e +Subproject commit 8f50b578d1378c1e43bfa9645910d5e690b8c98b