Reapply "refactor(members): use a single members list for public community chats (#16301)"

This reverts commit 7d8f4edc81.
This commit is contained in:
Patryk Osmaczko 2024-10-14 14:06:54 +02:00
parent 7d8f4edc81
commit 68c25f7b51
14 changed files with 164 additions and 68 deletions

View File

@ -354,7 +354,7 @@ QtObject:
self.missingEncryptionKeyChanged()
proc requiresPermissionsChanged(self: ChatDetails) {.signal.}
proc getRequiresPermissions(self: ChatDetails): bool {.slot.} =
proc getRequiresPermissions*(self: ChatDetails): bool {.slot.} =
return self.requiresPermissions
QtProperty[bool] requiresPermissions:
read = getRequiresPermissions

View File

@ -401,7 +401,11 @@ method onCommunityChannelEdited*(self: Module, chatDto: ChatDto) =
self.view.chatDetails.setName(chatDto.name)
self.view.chatDetails.setIcon(chatDto.icon)
self.view.chatDetails.setMissingEncryptionKey(chatDto.missingEncryptionKey)
self.view.chatDetails.setRequiresPermissions(chatDto.tokenGated)
if self.view.chatDetails.getRequiresPermissions() != chatDto.tokenGated:
# The channel permission status changed. Update the member list
self.view.chatDetails.setRequiresPermissions(chatDto.tokenGated)
self.usersModule.updateMembersList()
self.messagesModule.updateChatFetchMoreMessages()
self.messagesModule.updateChatIdentifier()

View File

@ -21,9 +21,6 @@ type
communityService: community_service.Service
messageService: message_service.Service
# Forward declaration
proc getChat*(self: Controller): ChatDto
proc newController*(
delegate: io_interface.AccessInterface, events: EventEmitter, sectionId: string, chatId: string,
belongsToCommunity: bool, isUsersListAvailable: bool, contactService: contact_service.Service,
@ -63,7 +60,7 @@ proc init*(self: Controller) =
self.delegate.loggedInUserImageChanged()
# Events only for the user list, so not needed in one to one chats
if(self.isUsersListAvailable):
if self.isUsersListAvailable:
self.events.on(SIGNAL_CONTACT_UNTRUSTWORTHY) do(e: Args):
var args = TrustArgs(e)
self.delegate.contactUpdated(args.publicKey)
@ -118,19 +115,11 @@ proc belongsToCommunity*(self: Controller): bool =
proc getMyCommunity*(self: Controller): CommunityDto =
return self.communityService.getCommunityById(self.sectionId)
proc getChat*(self: Controller): ChatDto =
return self.chatService.getChatById(self.chatId)
proc getMyChatId*(self: Controller): string =
return self.chatId
proc getChatMembers*(self: Controller): seq[ChatMember] =
if self.belongsToCommunity:
let myCommunity = self.getMyCommunity()
# TODO: when a new channel is added, chat may arrive earlier and we have no up to date community yet
# see log here: https://github.com/status-im/status-desktop/issues/14442#issuecomment-2120756598
# should be resolved in https://github.com/status-im/status-desktop/issues/14219
let members = myCommunity.getCommunityChat(self.chatId).members
if members.len > 0:
return members
return self.chatService.getChatById(self.chatId).members
proc getMyChat*(self: Controller): ChatDto =
return self.chatService.getChatById(self.chatId)
proc getContactNameAndImage*(self: Controller, contactId: string):
tuple[name: string, image: string, largeImage: string] =

View File

@ -20,6 +20,9 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method getUsersListVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method onNewMessagesLoaded*(self: AccessInterface, messages: seq[MessageDto]) {.base.} =
raise newException(ValueError, "No implementation available")
@ -59,5 +62,5 @@ method addGroupMembers*(self: AccessInterface, pubKeys: seq[string]) {.base.} =
method removeGroupMembers*(self: AccessInterface, pubKeys: seq[string]) {.base.} =
raise newException(ValueError, "No implementation available")
method updateMembersList*(self: AccessInterface) {.base.} =
method updateMembersList*(self: AccessInterface, membersToReset: seq[ChatMember] = @[]) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -4,13 +4,13 @@ import view, controller
import ../../../../shared_models/[member_model, member_item]
import ../../../../../global/global_singleton
import ../../../../../core/eventemitter
import ../../../../../../app_service/common/conversion
import ../../../../../../app_service/common/types
import ../../../../../../app_service/service/contacts/dto/contacts
import ../../../../../../app_service/service/contacts/service as contact_service
import ../../../../../../app_service/service/chat/service as chat_service
import ../../../../../../app_service/service/community/service as community_service
import ../../../../../../app_service/service/message/service as message_service
from ../../../../../../app_service/common/conversion import intToEnum
export io_interface
@ -20,15 +20,17 @@ type
viewVariant: QVariant
controller: Controller
moduleLoaded: bool
isPublicCommunityChannel: bool
isSectionMemberList: bool
# Forward declaration
proc processChatMember(self: Module, member: ChatMember): MemberItem
proc processChatMember(self: Module, member: ChatMember, reset: bool = false): tuple[doAdd: bool, memberItem: MemberItem]
proc newModule*(
events: EventEmitter, sectionId: string, chatId: string,
belongsToCommunity: bool, isUsersListAvailable: bool, contactService: contact_service.Service,
chatService: chat_service.Service, communityService: community_service.Service,
messageService: message_service.Service,
messageService: message_service.Service, isSectionMemberList: bool = false,
): Module =
result = Module()
result.view = view.newView(result)
@ -38,6 +40,8 @@ proc newModule*(
contactService, chatService, communityService, messageService,
)
result.moduleLoaded = false
result.isPublicCommunityChannel = false
result.isSectionMemberList = isSectionMemberList
method delete*(self: Module) =
self.controller.delete
@ -59,7 +63,12 @@ method viewDidLoad*(self: Module) =
method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
method getUsersListVariant*(self: Module): QVariant =
self.view.getModel()
method contactNicknameChanged*(self: Module, publicKey: string) =
if self.isPublicCommunityChannel:
return
let contactDetails = self.controller.getContactDetails(publicKey)
self.view.model().setName(
publicKey,
@ -69,11 +78,15 @@ method contactNicknameChanged*(self: Module, publicKey: string) =
)
method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto]) =
if self.isPublicCommunityChannel:
return
for s in statusUpdates:
var status = toOnlineStatus(s.statusType)
self.view.model().setOnlineStatus(s.publicKey, status)
method contactUpdated*(self: Module, publicKey: string) =
if self.isPublicCommunityChannel:
return
let contactDetails = self.controller.getContactDetails(publicKey)
let isMe = publicKey == singletonInstance.userProfile.getPubKey()
self.view.model().updateItem(
@ -90,37 +103,43 @@ method contactUpdated*(self: Module, publicKey: string) =
)
method userProfileUpdated*(self: Module) =
if self.isPublicCommunityChannel:
return
self.contactUpdated(singletonInstance.userProfile.getPubKey())
method loggedInUserImageChanged*(self: Module) =
if self.isPublicCommunityChannel:
return
self.view.model().setIcon(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getIcon())
# This function either removes the member if it is no longer part of the community,
# does nothing if the member is already in the model or creates the MemberItem
proc processChatMember(self: Module, member: ChatMember): MemberItem =
proc processChatMember(self: Module, member: ChatMember, reset: bool = false): tuple[doAdd: bool, memberItem: MemberItem] =
result.doAdd = false
if member.id == "":
return
if not self.controller.belongsToCommunity() and not member.joined:
if not reset and not self.controller.belongsToCommunity() and not member.joined:
if self.view.model().isContactWithIdAdded(member.id):
# Member is no longer joined
self.view.model().removeItemById(member.id)
return
if self.view.model().isContactWithIdAdded(member.id):
if not reset and self.view.model().isContactWithIdAdded(member.id):
return
let isMe = member.id == singletonInstance.userProfile.getPubKey()
let contactDetails = self.controller.getContactDetails(member.id)
var status = OnlineStatus.Online
if (isMe):
if isMe:
let currentUserStatus = intToEnum(singletonInstance.userProfile.getCurrentUserStatus(), StatusType.Unknown)
status = toOnlineStatus(currentUserStatus)
else:
let statusUpdateDto = self.controller.getStatusForContact(member.id)
status = toOnlineStatus(statusUpdateDto.statusType)
return initMemberItem(
result.doAdd = true
result.memberItem = initMemberItem(
pubKey = member.id,
displayName = contactDetails.dto.displayName,
ensName = contactDetails.dto.name,
@ -136,38 +155,45 @@ proc processChatMember(self: Module, member: ChatMember): MemberItem =
memberRole = member.role,
joined = member.joined,
isUntrustworthy = contactDetails.dto.trustStatus == TrustStatus.Untrustworthy,
)
)
method onChatMembersAdded*(self: Module, ids: seq[string]) =
if self.isPublicCommunityChannel:
return
var memberItems: seq[MemberItem] = @[]
for memberId in ids:
let item = self.processChatMember(ChatMember(id: memberId, role: MemberRole.None, joined: true))
if item.pubKey != "":
let (doAdd, item) = self.processChatMember(ChatMember(id: memberId, role: MemberRole.None, joined: true))
if doAdd:
memberItems.add(item)
self.view.model().addItems(memberItems)
method onChatMemberRemoved*(self: Module, id: string) =
if self.isPublicCommunityChannel:
return
self.view.model().removeItemById(id)
method onMembersChanged*(self: Module, members: seq[ChatMember]) =
if self.isPublicCommunityChannel:
return
let modelIDs = self.view.model().getItemIds()
let membersAdded = filter(members, member => not modelIDs.contains(member.id))
let membersRemoved = filter(modelIDs, id => not members.any(member => member.id == id))
var memberItems: seq[MemberItem] = @[]
for member in membersAdded:
let item = self.processChatMember(member)
if item.pubKey != "":
let (doAdd, item) = self.processChatMember(member)
if doAdd:
memberItems.add(item)
self.view.model().addItems(memberItems)
for id in membersRemoved:
self.onChatMemberRemoved(id)
method onChatMemberUpdated*(self: Module, publicKey: string, memberRole: MemberRole, joined: bool) =
if self.isPublicCommunityChannel:
return
let contactDetails = self.controller.getContactDetails(publicKey)
let isMe = publicKey == singletonInstance.userProfile.getPubKey()
self.view.model().updateItem(
@ -191,13 +217,45 @@ method addGroupMembers*(self: Module, pubKeys: seq[string]) =
method removeGroupMembers*(self: Module, pubKeys: seq[string]) =
self.controller.removeGroupMembers(pubKeys)
method updateMembersList*(self: Module) =
let members = self.controller.getChatMembers()
method updateMembersList*(self: Module, membersToReset: seq[ChatMember] = @[]) =
let reset = membersToReset.len > 0
var members: seq[ChatMember]
if reset:
members = membersToReset
else:
if self.controller.belongsToCommunity():
let myCommunity = self.controller.getMyCommunity()
if self.isSectionMemberList:
members = myCommunity.members
else:
# TODO: when a new channel is added, chat may arrive earlier and we have no up to date community yet
# see log here: https://github.com/status-im/status-desktop/issues/14442#issuecomment-2120756598
# should be resolved in https://github.com/status-im/status-desktop/issues/11694
let myChatId = self.controller.getMyChatId()
let chat = myCommunity.getCommunityChat(myChatId)
if not chat.tokenGated:
# No need to get the members, this channel is not encrypted and can use the section member list
self.isPublicCommunityChannel = true
return
self.isPublicCommunityChannel = false
if chat.members.len > 0:
members = chat.members
else:
# The channel now has a permisison, but the re-eval wasn't performed yet. Show all members for now
members = myCommunity.members
if members.len == 0:
members = self.controller.getMyChat().members
var memberItems: seq[MemberItem] = @[]
for member in members:
let item = self.processChatMember(member)
if item.pubKey != "":
let (doAdd, item) = self.processChatMember(member, reset)
if doAdd:
memberItems.add(item)
self.view.model().addItems(memberItems)
if reset:
self.view.model().setItems(memberItems)
else:
self.view.model().addItems(memberItems)

View File

@ -33,10 +33,10 @@ QtObject:
proc modelChanged*(self: View) {.signal.}
proc getModel(self: View): QVariant {.slot.} =
proc getModel*(self: View): QVariant {.slot.} =
return self.modelVariant
QtProperty[QVariant]model:
QtProperty[QVariant] model:
read = getModel
notify = modelChanged

View File

@ -342,6 +342,11 @@ proc init*(self: Controller) =
if args.communityId == self.sectionId:
self.delegate.onSectionMutedChanged()
self.events.on(SIGNAL_COMMUNITY_MEMBERS_CHANGED) do(e: Args):
let args = CommunityMembersArgs(e)
if args.communityId == self.sectionId:
self.delegate.updateCommunityMemberList(args.members)
self.events.on(SIGNAL_CONTACT_NICKNAME_CHANGED) do(e: Args):
var args = ContactArgs(e)
self.delegate.onContactDetailsUpdated(args.contactId)

View File

@ -46,6 +46,12 @@ method isLoaded*(self: AccessInterface): bool {.base.} =
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method updateCommunityMemberList*(self: AccessInterface, members: seq[ChatMember]) {.base.} =
raise newException(ValueError, "No implementation available")
method getSectionMemberList*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method onActiveSectionChange*(self: AccessInterface, sectionId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -17,6 +17,7 @@ import ../../shared_models/token_criteria_model
import ../../shared_models/token_permission_chat_list_model
import chat_content/module as chat_content_module
import chat_content/users/module as users_module
import ../../../global/global_singleton
import ../../../core/eventemitter
@ -53,6 +54,7 @@ type
chatContentModules: OrderedTable[string, chat_content_module.AccessInterface]
moduleLoaded: bool
chatsLoaded: bool
membersListModule: users_module.AccessInterface
# Forward declaration
proc buildChatSectionUI(
@ -121,6 +123,11 @@ proc newModule*(
result.chatsLoaded = false
result.chatContentModules = initOrderedTable[string, chat_content_module.AccessInterface]()
if isCommunity:
result.membersListModule = users_module.newModule(events, sectionId, chatId = "", isCommunity,
isUsersListAvailable = true, contactService, chatService, communityService, messageService, isSectionMemberList = true)
else:
result.membersListModule = nil
proc currentUserWalletContainsAddress(self: Module, address: string): bool =
if (address.len == 0):
@ -218,6 +225,8 @@ method delete*(self: Module) =
for cModule in self.chatContentModules.values:
cModule.delete
self.chatContentModules.clear
if self.membersListModule != nil:
self.membersListModule.delete
method isCommunity*(self: Module): bool =
return self.controller.isCommunity()
@ -440,6 +449,10 @@ method onChatsLoaded*(
self.buildChatSectionUI(community, chats, events, settingsService, nodeConfigurationService,
contactService, chatService, communityService, messageService, mailserversService, sharedUrlsService)
# Generate members list
if self.membersListModule != nil:
self.membersListModule.load()
if(not self.controller.isCommunity()):
# we do this only in case of chat section (not in case of communities)
self.initContactRequestsModel()
@ -480,6 +493,12 @@ proc checkIfModuleDidLoad(self: Module) =
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method getSectionMemberList*(self: Module): QVariant =
return self.membersListModule.getUsersListVariant()
method updateCommunityMemberList*(self: Module, members: seq[ChatMember]) =
self.membersListModule.updateMembersList(members)
method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad()

View File

@ -572,4 +572,12 @@ QtObject:
if self.communityMemberReevaluationStatus == value:
return
self.communityMemberReevaluationStatus = value
self.communityMemberReevaluationStatusChanged()
self.communityMemberReevaluationStatusChanged()
proc membersModelChanged*(self: View) {.signal.}
proc getMembersModel(self: View): QVariant {.slot.} =
return self.delegate.getSectionMemberList()
QtProperty[QVariant] membersModel:
read = getMembersModel
notify = membersModelChanged

View File

@ -509,10 +509,7 @@ QtObject:
self.events.emit(SIGNAL_COMMUNITY_CATEGORY_NAME_EDITED,
CommunityCategoryArgs(communityId: community.id, category: category))
self.events.emit(SIGNAL_COMMUNITY_MEMBERS_CHANGED,
CommunityMembersArgs(communityId: community.id, members: community.members))
proc communityTokensChanged(self: Service, community: CommunityDto, prev_community: CommunityDto): bool =
proc communityTokensChanged(community: CommunityDto, prev_community: CommunityDto): bool =
let communityTokens = community.communityTokensMetadata
let prevCommunityTokens = prev_community.communityTokensMetadata
# checking length is sufficient - communityTokensMetadata list can only extend
@ -536,6 +533,17 @@ QtObject:
let prevCommunity = self.communities[community.id]
# If there's settings without `id` it means the original
# signal didn't include actual communitySettings, hence we
# assign the settings we already have, otherwise we risk our
# settings to be overridden with wrong defaults.
if community.settings.id == "":
community.settings = prevCommunity.settings
# Save the updated community before calling events, because some events triggers look ups on the new community
# We will save again at the end if other properties were updated
self.saveUpdatedCommunity(community)
try:
let currOwner = community.findOwner()
let prevOwner = prevCommunity.findOwner()
@ -551,23 +559,15 @@ QtObject:
let response = tokens_backend.registerLostOwnershipNotification(community.id)
checkAndEmitACNotificationsFromResponse(self.events, response.result{"activityCenterNotifications"})
if self.communityTokensChanged(community, prevCommunity):
if communityTokensChanged(community, prevCommunity):
self.events.emit(SIGNAL_COMMUNITY_TOKENS_CHANGED, nil)
# If there's settings without `id` it means the original
# signal didn't include actual communitySettings, hence we
# assign the settings we already have, otherwise we risk our
# settings to be overridden with wrong defaults.
if community.settings.id == "":
community.settings = prevCommunity.settings
var deletedCategories: seq[string] = @[]
# category was added
if(community.categories.len > prevCommunity.categories.len):
for category in community.categories:
if findIndexById(category.id, prevCommunity.categories) == -1:
self.communities[community.id].categories.add(category)
let chats = self.getChatsInCategory(community, category.id)
self.events.emit(SIGNAL_COMMUNITY_CATEGORY_CREATED,
@ -665,13 +665,12 @@ QtObject:
# members list was changed
if community.members != prevCommunity.members:
self.events.emit(SIGNAL_COMMUNITY_MEMBERS_CHANGED,
CommunityMembersArgs(communityId: community.id, members: community.members))
CommunityMembersArgs(communityId: community.id, members: community.members))
# token metadata was added
if community.communityTokensMetadata.len > prevCommunity.communityTokensMetadata.len:
for tokenMetadata in community.communityTokensMetadata:
if findIndexBySymbol(tokenMetadata.symbol, prevCommunity.communityTokensMetadata) == -1:
self.communities[community.id].communityTokensMetadata.add(tokenMetadata)
self.events.emit(SIGNAL_COMMUNITY_TOKEN_METADATA_ADDED,
CommunityTokenMetadataArgs(communityId: community.id,
tokenMetadata: tokenMetadata))
@ -680,16 +679,14 @@ QtObject:
if community.tokenPermissions.len > prevCommunity.tokenPermissions.len:
for id, tokenPermission in community.tokenPermissions:
if not prevCommunity.tokenPermissions.hasKey(id):
self.communities[community.id].tokenPermissions[id] = tokenPermission
self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED,
CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: tokenPermission))
CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: tokenPermission))
elif community.tokenPermissions.len < prevCommunity.tokenPermissions.len:
for id, prvTokenPermission in prevCommunity.tokenPermissions:
if not community.tokenPermissions.hasKey(id):
self.communities[community.id].tokenPermissions.del(id)
self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED,
CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: prvTokenPermission))
CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: prvTokenPermission))
else:
for id, tokenPermission in community.tokenPermissions:
if not prevCommunity.tokenPermissions.hasKey(id):
@ -725,16 +722,13 @@ QtObject:
break
if permissionUpdated:
self.communities[community.id].tokenPermissions[id] = tokenPermission
self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED,
CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: tokenPermission))
let wasJoined = self.communities[community.id].joined
self.saveUpdatedCommunity(community)
let wasJoined = prevCommunity.joined
# If the community was not joined before but is now, we signal it
if(not wasJoined and community.joined and community.isMember):
if not wasJoined and community.joined and community.isMember:
self.events.emit(SIGNAL_COMMUNITY_JOINED, CommunityArgs(community: community, fromUserAction: false))
self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community]))

View File

@ -68,7 +68,7 @@ proc `$`(self: Images): string =
]"""
proc `$`*(self: ContactsDto): string =
result = fmt"""ContactDto(
result = fmt"""ContactsDto(
id: {self.id},
name: {self.name},
ensVerified: {self.ensVerified},

View File

@ -15,7 +15,7 @@ proc `==`*(l, r: EnsUsernameDto): bool =
return l.chainId == r.chainid and l.username == r.username
proc `$`*(self: EnsUsernameDto): string =
result = fmt"""ContactDto(
result = fmt"""EnsUsernameDto(
chainId: {self.chainId},
username: {self.username},
txType: {self.txType},

View File

@ -179,7 +179,17 @@ StatusSectionLayout {
store: root.rootStore
label: qsTr("Members")
communityMemberReevaluationStatus: root.rootStore.communityMemberReevaluationStatus
usersModel: root.chatContentModule && root.chatContentModule.usersModule ? root.chatContentModule.usersModule.model : null
usersModel: {
if (!root.chatContentModule || !root.chatContentModule.chatDetails) {
return null
}
let isFullCommunityList = !root.chatContentModule.chatDetails.requiresPermissions
if (root.chatContentModule.chatDetails.belongsToCommunity && isFullCommunityList) {
// Community channel with no permisisons. We can use the section's membersModel
return root.rootStore.chatCommunitySectionModule.membersModel
}
return root.chatContentModule.usersModule ? root.chatContentModule.usersModule.model : null
}
}
}