refactor(@desktop/members): do not request members list each time chat/channel changed or member added/removed

This commit is contained in:
mprakhov 2023-01-25 23:25:01 +02:00 committed by Mykhailo Prakhov
parent 1523c9f2a0
commit b7c4c7b582
9 changed files with 81 additions and 110 deletions

View File

@ -52,21 +52,17 @@ proc handleCommunityOnlyConnections(self: Controller) =
if (args.communityId == self.sectionId): if (args.communityId == self.sectionId):
self.delegate.onChatMembersAdded(@[args.pubKey]) self.delegate.onChatMembersAdded(@[args.pubKey])
self.events.on(SIGNAL_COMMUNITIES_UPDATE) do(e:Args): self.events.on(SIGNAL_COMMUNITY_MEMBERS_CHANGED) do(e:Args):
let args = CommunitiesArgs(e) let args = CommunityMembersArgs(e)
for community in args.communities: if args.communityId != self.sectionId:
if (community.id != self.sectionId): return
continue
# If we didn't join the community, all members in the list will have status self.delegate.onMembersChanged(args.members)
# joined=false. No need to try add them to the model
if community.isMember:
let membersPubKeys = community.members.map(x => x.id)
self.delegate.onChatMembersAddedOrRemoved(membersPubKeys)
self.events.on(SIGNAL_COMMUNITY_MEMBER_REMOVED) do(e: Args): self.events.on(SIGNAL_COMMUNITY_MEMBER_REMOVED) do(e: Args):
let args = CommunityMemberArgs(e) let args = CommunityMemberArgs(e)
if (args.communityId == self.sectionId): if (args.communityId == self.sectionId):
self.delegate.onChatMemberRemoved(args.pubKey) self.delegate.onChatMemberRemoved(args.pubKey)
proc init*(self: Controller) = proc init*(self: Controller) =
# Events that are needed for all chats because of mentions # Events that are needed for all chats because of mentions
@ -121,11 +117,10 @@ proc init*(self: Controller) =
if (args.chatId == self.chatId): if (args.chatId == self.chatId):
self.delegate.onChatMembersAdded(args.ids) self.delegate.onChatMembersAdded(args.ids)
self.events.on(SIGNAL_CHAT_UPDATE) do(e: Args): self.events.on(SIGNAL_CHAT_MEMBERS_CHANGED) do(e: Args):
var args = ChatUpdateArgs(e) var args = ChatMembersChangedArgs(e)
for chat in args.chats: if (args.chatId == self.chatId):
if (chat.id == self.chatId): self.delegate.onMembersChanged(args.members)
self.delegate.onChatUpdated(chat)
self.events.on(SIGNAL_CHAT_MEMBER_REMOVED) do(e: Args): self.events.on(SIGNAL_CHAT_MEMBER_REMOVED) do(e: Args):
let args = ChatMemberRemovedArgs(e) let args = ChatMemberRemovedArgs(e)
@ -145,18 +140,10 @@ proc getChat*(self: Controller): ChatDto =
return self.chatService.getChatById(self.chatId) return self.chatService.getChatById(self.chatId)
proc getChatMembers*(self: Controller): seq[ChatMember] = proc getChatMembers*(self: Controller): seq[ChatMember] =
var communityId = "" if self.belongsToCommunity:
if (self.belongsToCommunity): return self.communityService.getCommunityById(self.sectionId).members
communityId = self.sectionId
return self.chatService.getMembers(communityId, self.chatId) return self.chatService.getChatById(self.chatId).members
proc getMembersPublicKeys*(self: Controller): seq[string] =
if(self.belongsToCommunity):
let communityDto = self.communityService.getCommunityById(self.sectionId)
return communityDto.members.map(x => x.id)
else:
let chatDto = self.getChat()
return chatDto.members.map(x => x.id)
proc getContactNameAndImage*(self: Controller, contactId: string): proc getContactNameAndImage*(self: Controller, contactId: string):
tuple[name: string, image: string, largeImage: string] = tuple[name: string, image: string, largeImage: string] =

View File

@ -37,10 +37,7 @@ method userProfileUpdated*(self: AccessInterface) {.base.} =
method loggedInUserImageChanged*(self: AccessInterface) {.base.} = method loggedInUserImageChanged*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method addChatMember*(self: AccessInterface, member: ChatMember) {.base.} = method onMembersChanged*(self: AccessInterface, members: seq[ChatMember]) {.base.} =
raise newException(ValueError, "No implementation available")
method onChatMembersAddedOrRemoved*(self: AccessInterface, ids: seq[string]) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onChatMembersAdded*(self: AccessInterface, ids: seq[string]) {.base.} = method onChatMembersAdded*(self: AccessInterface, ids: seq[string]) {.base.} =
@ -49,18 +46,12 @@ method onChatMembersAdded*(self: AccessInterface, ids: seq[string]) {.base.} =
method onChatMemberRemoved*(self: AccessInterface, ids: string) {.base.} = method onChatMemberRemoved*(self: AccessInterface, ids: string) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onChatUpdated*(self: AccessInterface, chat: ChatDto) {.base.} =
raise newException(ValueError, "No implementation available")
method onChatMemberUpdated*(self: AccessInterface, id: string, admin: bool, joined: bool) {.base.} = method onChatMemberUpdated*(self: AccessInterface, id: string, admin: bool, joined: bool) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method viewDidLoad*(self: AccessInterface) {.base.} = method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getMembersPublicKeys*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method addGroupMembers*(self: AccessInterface, pubKeys: seq[string]) {.base.} = method addGroupMembers*(self: AccessInterface, pubKeys: seq[string]) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -22,7 +22,7 @@ type
moduleLoaded: bool moduleLoaded: bool
# Forward declaration # Forward declaration
method addChatMember*(self: Module, member: ChatMember) method addChatMember(self: Module, member: ChatMember)
proc newModule*( proc newModule*(
events: EventEmitter, sectionId: string, chatId: string, events: EventEmitter, sectionId: string, chatId: string,
@ -96,7 +96,7 @@ method userProfileUpdated*(self: Module) =
method loggedInUserImageChanged*(self: Module) = method loggedInUserImageChanged*(self: Module) =
self.view.model().setIcon(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getIcon()) self.view.model().setIcon(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getIcon())
method addChatMember*(self: Module, member: ChatMember) = method addChatMember(self: Module, member: ChatMember) =
if member.id == "": if member.id == "":
return return
@ -136,43 +136,21 @@ method addChatMember*(self: Module, member: ChatMember) =
isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy
)) ))
method onChatMembersAdded*(self: Module, ids: seq[string]) = method onChatMembersAdded*(self: Module, ids: seq[string]) =
if ids.len() == 0: for memberId in ids:
return self.addChatMember(ChatMember(id: memberId, admin: false, joined: true, roles: @[]))
let members = self.controller.getChatMembers()
for id in ids:
for member in members:
if (member.id == id):
self.addChatMember(member)
method onChatUpdated*(self: Module, chat: ChatDto) =
let members = self.controller.getChatMembers()
for member in chat.members:
for existingMember in members:
if existingMember.id == member.id:
self.addChatMember(existingMember)
if chat.members.len > 0:
let ids = self.view.model.getItemIds()
for id in ids:
var found = false
for member in chat.members:
if (member.id == id):
found = true
break
if (not found):
self.view.model().removeItemById(id)
method onChatMemberRemoved*(self: Module, id: string) = method onChatMemberRemoved*(self: Module, id: string) =
self.view.model().removeItemById(id) self.view.model().removeItemById(id)
method onChatMembersAddedOrRemoved*(self: Module, ids: seq[string]) = method onMembersChanged*(self: Module, members: seq[ChatMember]) =
let modelIDs = self.view.model().getItemIds() let modelIDs = self.view.model().getItemIds()
let membersAdded = filter(ids, id => not modelIDs.contains(id)) let membersAdded = filter(members, member => not modelIDs.contains(member.id))
let membersRemoved = filter(modelIDs, id => not ids.contains(id)) let membersRemoved = filter(modelIDs, id => not members.any(member => member.id == id))
for member in membersAdded:
self.addChatMember(member)
self.onChatMembersAdded(membersAdded)
for id in membersRemoved: for id in membersRemoved:
self.onChatMemberRemoved(id) self.onChatMemberRemoved(id)
@ -193,10 +171,6 @@ method onChatMemberUpdated*(self: Module, publicKey: string, admin: bool, joined
isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy, isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy,
) )
method getMembersPublicKeys*(self: Module): string =
let publicKeys = self.controller.getMembersPublicKeys()
return publicKeys.join(" ")
method addGroupMembers*(self: Module, pubKeys: seq[string]) = method addGroupMembers*(self: Module, pubKeys: seq[string]) =
self.controller.addGroupMembers(pubKeys) self.controller.addGroupMembers(pubKeys)

View File

@ -40,9 +40,6 @@ QtObject:
read = getModel read = getModel
notify = modelChanged notify = modelChanged
proc getMembersPublicKeys*(self: View): string {.slot.} =
return self.delegate.getMembersPublicKeys()
proc temporaryModelChanged*(self: View) {.signal.} proc temporaryModelChanged*(self: View) {.signal.}
proc getTemporaryModel(self: View): QVariant {.slot.} = proc getTemporaryModel(self: View): QVariant {.slot.} =

View File

@ -126,7 +126,7 @@ method getCommunityItem(self: Module, c: CommunityDto): SectionItem =
c.permissions.access, c.permissions.access,
c.permissions.ensOnly, c.permissions.ensOnly,
c.muted, c.muted,
c.members.map(proc(member: Member): MemberItem = c.members.map(proc(member: ChatMember): MemberItem =
result = self.createMemberItem(member.id, "")), result = self.createMemberItem(member.id, "")),
historyArchiveSupportEnabled = c.settings.historyArchiveSupportEnabled, historyArchiveSupportEnabled = c.settings.historyArchiveSupportEnabled,
bannedMembers = c.bannedMembersIds.map(proc(bannedMemberId: string): MemberItem = bannedMembers = c.bannedMembersIds.map(proc(bannedMemberId: string): MemberItem =

View File

@ -172,27 +172,37 @@ proc toCategory*(jsonObj: JsonNode): Category =
discard jsonObj.getProp("name", result.name) discard jsonObj.getProp("name", result.name)
discard jsonObj.getProp("position", result.position) discard jsonObj.getProp("position", result.position)
proc toChatMember*(jsonObj: JsonNode): ChatMember = proc toChatMember*(jsonObj: JsonNode, memberId: string): ChatMember =
# Parse status-go "Member" type
# Mapping this DTO is not strightforward since only keys are used for id
result = ChatMember() result = ChatMember()
discard jsonObj.getProp("id", result.id) result.id = memberId
discard jsonObj.getProp("admin", result.admin) discard jsonObj.getProp("admin", result.admin)
discard jsonObj.getProp("joined", result.joined) discard jsonObj.getProp("joined", result.joined)
var rolesObj: JsonNode var rolesObj: JsonNode
if(jsonObj.getProp("roles", rolesObj) and rolesObj.kind == JArray): if(jsonObj.getProp("roles", rolesObj) and rolesObj.kind == JArray):
for role in rolesObj: for role in rolesObj:
result.roles.add(role.getInt) result.roles.add(role.getInt)
# channel group members json contains only roles proc toGroupChatMember*(jsonObj: JsonNode): ChatMember =
# fill admin info by checking roles # parse status-go "ChatMember" type
if (not jsonObj.contains("admin")): result = ChatMember()
result.admin = isMemberAdmin(result.roles) discard jsonObj.getProp("id", result.id)
discard jsonObj.getProp("admin", result.admin)
result.joined = true
proc toChatMember(jsonObj: JsonNode, memberId: string): ChatMember = proc toChannelMember*(jsonObj: JsonNode, memberId: string, joined: bool): ChatMember =
# Parse status-go "CommunityMember" type
# Mapping this DTO is not strightforward since only keys are used for id. We # Mapping this DTO is not strightforward since only keys are used for id. We
# handle it a bit different. # handle it a bit different.
result = jsonObj.toChatMember() result = ChatMember()
result.id = memberId result.id = memberId
var rolesObj: JsonNode
if(jsonObj.getProp("roles", rolesObj)):
for roleObj in rolesObj:
result.roles.add(roleObj.getInt)
result.joined = joined
result.admin = isMemberAdmin(result.roles)
proc toChatDto*(jsonObj: JsonNode): ChatDto = proc toChatDto*(jsonObj: JsonNode): ChatDto =
result = ChatDto() result = ChatDto()
@ -242,9 +252,11 @@ proc toChatDto*(jsonObj: JsonNode): ChatDto =
var membersObj: JsonNode var membersObj: JsonNode
if(jsonObj.getProp("members", membersObj)): if(jsonObj.getProp("members", membersObj)):
if(membersObj.kind == JArray): if(membersObj.kind == JArray):
# during group chat updates
for memberObj in membersObj: for memberObj in membersObj:
result.members.add(toChatMember(memberObj)) result.members.add(toGroupChatMember(memberObj))
elif(membersObj.kind == JObject): elif(membersObj.kind == JObject):
# on a startup, chat/channel creation
for memberId, memberObj in membersObj: for memberId, memberObj in membersObj:
result.members.add(toChatMember(memberObj, memberId)) result.members.add(toChatMember(memberObj, memberId))
@ -292,7 +304,7 @@ proc toChannelGroupDto*(jsonObj: JsonNode): ChannelGroupDto =
var membersObj: JsonNode var membersObj: JsonNode
if(jsonObj.getProp("members", membersObj) and membersObj.kind == JObject): if(jsonObj.getProp("members", membersObj) and membersObj.kind == JObject):
for memberId, memberObj in membersObj: for memberId, memberObj in membersObj:
result.members.add(toChatMember(memberObj, memberId)) result.members.add(toChannelMember(memberObj, memberId, joined = true))
var bannedMembersIdsObj: JsonNode var bannedMembersIdsObj: JsonNode
if(jsonObj.getProp("banList", bannedMembersIdsObj) and bannedMembersIdsObj.kind == JArray): if(jsonObj.getProp("banList", bannedMembersIdsObj) and bannedMembersIdsObj.kind == JArray):

View File

@ -73,6 +73,10 @@ type
chatId*: string chatId*: string
id*: string id*: string
ChatMembersChangedArgs* = ref object of Args
chatId*: string
members*: seq[ChatMember]
ChatMemberUpdatedArgs* = ref object of Args ChatMemberUpdatedArgs* = ref object of Args
chatId*: string chatId*: string
id*: string id*: string
@ -92,6 +96,7 @@ const SIGNAL_CHAT_HISTORY_CLEARED* = "chatHistoryCleared"
const SIGNAL_CHAT_RENAMED* = "chatRenamed" const SIGNAL_CHAT_RENAMED* = "chatRenamed"
const SIGNAL_GROUP_CHAT_DETAILS_UPDATED* = "groupChatDetailsUpdated" const SIGNAL_GROUP_CHAT_DETAILS_UPDATED* = "groupChatDetailsUpdated"
const SIGNAL_CHAT_MEMBERS_ADDED* = "chatMemberAdded" const SIGNAL_CHAT_MEMBERS_ADDED* = "chatMemberAdded"
const SIGNAL_CHAT_MEMBERS_CHANGED* = "chatMembersChanged"
const SIGNAL_CHAT_MEMBER_REMOVED* = "chatMemberRemoved" const SIGNAL_CHAT_MEMBER_REMOVED* = "chatMemberRemoved"
const SIGNAL_CHAT_MEMBER_UPDATED* = "chatMemberUpdated" const SIGNAL_CHAT_MEMBER_UPDATED* = "chatMemberUpdated"
const SIGNAL_CHAT_SWITCH_TO_OR_CREATE_1_1_CHAT* = "switchToOrCreateOneToOneChat" const SIGNAL_CHAT_SWITCH_TO_OR_CREATE_1_1_CHAT* = "switchToOrCreateOneToOneChat"
@ -128,7 +133,13 @@ QtObject:
for chatDto in receivedData.chats: for chatDto in receivedData.chats:
if (chatDto.active): if (chatDto.active):
chats.add(chatDto) chats.add(chatDto)
# Handling members update
if self.chats.hasKey(chatDto.id) and self.chats[chatDto.id].members != chatDto.members:
self.events.emit(SIGNAL_CHAT_MEMBERS_CHANGED, ChatMembersChangedArgs(chatId: chatDto.id, members: chatDto.members))
self.updateOrAddChat(chatDto) self.updateOrAddChat(chatDto)
self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgs(messages: receivedData.messages, chats: chats)) self.events.emit(SIGNAL_CHAT_UPDATE, ChatUpdateArgs(messages: receivedData.messages, chats: chats))
if (receivedData.clearedHistories.len > 0): if (receivedData.clearedHistories.len > 0):
@ -613,8 +624,7 @@ QtObject:
let myPubkey = singletonInstance.userProfile.getPubKey() let myPubkey = singletonInstance.userProfile.getPubKey()
result = @[] result = @[]
for (id, memberObj) in response.result.pairs: for (id, memberObj) in response.result.pairs:
var member = toChatMember(memberObj) var member = toChatMember(memberObj, id)
member.id = id
# Make yourself as the first result # Make yourself as the first result
if (id == myPubkey): if (id == myPubkey):
result.insert(member) result.insert(member)

View File

@ -13,20 +13,6 @@ type RequestToJoinType* {.pure.}= enum
Accepted = 3, Accepted = 3,
Canceled = 4 Canceled = 4
type Member* = object
id*: string
roles*: seq[int]
proc toMember*(jsonObj: JsonNode, memberId: string): Member =
# Mapping this DTO is not strightforward since only keys are used for id. We
# handle it a bit different.
result = Member()
result.id = memberId
var rolesObj: JsonNode
if(jsonObj.getProp("roles", rolesObj)):
for roleObj in rolesObj:
result.roles.add(roleObj.getInt)
type CommunityMembershipRequestDto* = object type CommunityMembershipRequestDto* = object
id*: string id*: string
publicKey*: string publicKey*: string
@ -58,7 +44,7 @@ type CommunityDto* = object
categories*: seq[Category] categories*: seq[Category]
images*: Images images*: Images
permissions*: Permission permissions*: Permission
members*: seq[Member] members*: seq[ChatMember]
canRequestAccess*: bool canRequestAccess*: bool
canManageUsers*: bool canManageUsers*: bool
canJoin*: bool canJoin*: bool
@ -159,6 +145,7 @@ proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
discard jsonObj.getProp("introMessage", result.introMessage) discard jsonObj.getProp("introMessage", result.introMessage)
discard jsonObj.getProp("outroMessage", result.outroMessage) discard jsonObj.getProp("outroMessage", result.outroMessage)
discard jsonObj.getProp("encrypted", result.encrypted) discard jsonObj.getProp("encrypted", result.encrypted)
discard jsonObj.getProp("isMember", result.isMember)
var chatsObj: JsonNode var chatsObj: JsonNode
if(jsonObj.getProp("chats", chatsObj)): if(jsonObj.getProp("chats", chatsObj)):
@ -185,7 +172,8 @@ proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
var membersObj: JsonNode var membersObj: JsonNode
if(jsonObj.getProp("members", membersObj) and membersObj.kind == JObject): if(jsonObj.getProp("members", membersObj) and membersObj.kind == JObject):
for memberId, memberObj in membersObj: for memberId, memberObj in membersObj:
result.members.add(toMember(memberObj, memberId)) # Do not display members list until the user became a community member
result.members.add(toChannelMember(memberObj, memberId, joined = result.isMember))
var tagsObj: JsonNode var tagsObj: JsonNode
if(jsonObj.getProp("tags", tagsObj)): if(jsonObj.getProp("tags", tagsObj)):
@ -204,7 +192,6 @@ proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
discard jsonObj.getProp("color", result.color) discard jsonObj.getProp("color", result.color)
discard jsonObj.getProp("requestedToJoinAt", result.requestedToJoinAt) discard jsonObj.getProp("requestedToJoinAt", result.requestedToJoinAt)
discard jsonObj.getProp("isMember", result.isMember)
discard jsonObj.getProp("muted", result.muted) discard jsonObj.getProp("muted", result.muted)
proc toCommunityMembershipRequestDto*(jsonObj: JsonNode): CommunityMembershipRequestDto = proc toCommunityMembershipRequestDto*(jsonObj: JsonNode): CommunityMembershipRequestDto =
@ -267,7 +254,8 @@ proc toChannelGroupDto*(communityDto: CommunityDto): ChannelGroupDto =
members: communityDto.members.map(m => ChatMember( members: communityDto.members.map(m => ChatMember(
id: m.id, id: m.id,
joined: true, joined: true,
admin: isMemberAdmin(m.roles) admin: isMemberAdmin(m.roles),
roles: m.roles
)), )),
canManageUsers: communityDto.canManageUsers, canManageUsers: communityDto.canManageUsers,
muted: communityDto.muted, muted: communityDto.muted,

View File

@ -67,6 +67,11 @@ type
communityId*: string communityId*: string
pubKey*: string pubKey*: string
CommunityMembersArgs* = ref object of Args
communityId*: string
members*: seq[ChatMember]
isMember*: bool
CommunityMutedArgs* = ref object of Args CommunityMutedArgs* = ref object of Args
communityId*: string communityId*: string
muted*: bool muted*: bool
@ -118,6 +123,7 @@ const SIGNAL_COMMUNITY_CATEGORY_REORDERED* = "communityCategoryReordered"
const SIGNAL_COMMUNITY_CHANNEL_CATEGORY_CHANGED* = "communityChannelCategoryChanged" const SIGNAL_COMMUNITY_CHANNEL_CATEGORY_CHANGED* = "communityChannelCategoryChanged"
const SIGNAL_COMMUNITY_MEMBER_APPROVED* = "communityMemberApproved" const SIGNAL_COMMUNITY_MEMBER_APPROVED* = "communityMemberApproved"
const SIGNAL_COMMUNITY_MEMBER_REMOVED* = "communityMemberRemoved" const SIGNAL_COMMUNITY_MEMBER_REMOVED* = "communityMemberRemoved"
const SIGNAL_COMMUNITY_MEMBERS_CHANGED* = "communityMembersChanged"
const SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY* = "newRequestToJoinCommunity" const SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY* = "newRequestToJoinCommunity"
const SIGNAL_REQUEST_TO_JOIN_COMMUNITY_CANCELED* = "requestToJoinCommunityCanceled" const SIGNAL_REQUEST_TO_JOIN_COMMUNITY_CANCELED* = "requestToJoinCommunityCanceled"
const SIGNAL_CURATED_COMMUNITY_FOUND* = "curatedCommunityFound" const SIGNAL_CURATED_COMMUNITY_FOUND* = "curatedCommunityFound"
@ -446,6 +452,12 @@ QtObject:
let data = CommunityChatArgs(chat: updatedChat) let data = CommunityChatArgs(chat: updatedChat)
self.events.emit(SIGNAL_COMMUNITY_CHANNEL_EDITED, data) self.events.emit(SIGNAL_COMMUNITY_CHANNEL_EDITED, data)
# members list was changed
if community.members != prev_community.members:
self.events.emit(SIGNAL_COMMUNITY_MEMBERS_CHANGED,
CommunityMembersArgs(communityId: community.id, members: community.members, isMember: community.isMember))
self.allCommunities[community.id] = community self.allCommunities[community.id] = community
self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community])) self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community]))