From e281ec38717df5cd8b4cc8c5f35f63293cb7fa77 Mon Sep 17 00:00:00 2001 From: Eric Mastro Date: Thu, 24 Jun 2021 17:21:45 +1000 Subject: [PATCH] fix(communities): re-joining of left communities MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes: #2649. Upon receipt of status-go signals which included communities that have been left (`joined: false`), those communities were being rejoined automatically when they should not have been. fix(communities): Invitation bubble button state updates The community state inside of the invitation bubble was not reactive to any community actions (such as joining, leaving, updating). In addition, requesting to join a community changed the button’s text to “Pending”, but upon approval, the button’s state was not updating. The component was setting an observed community in the Component.onCompleted event, which was occurring for all invitation bubbles, but because the community wasn’t bound correctly to the bubble, once a bubble with a different community was encountered, the community in context of the bubble wasn’t updated and instead used a local copy. Once the community was bound correctly (to be reactive), the states started working correctly. The invitation bubble has been simplied so that it has states instead of using lots of if/else statements inside of the property bindings. This simplified the component’s logic for things like onClick action and made it a lot easier to read and modify. --- src/app/chat/views/communities.nim | 36 +++- src/app/chat/views/community_list.nim | 20 ++- src/status/libstatus/chat.nim | 7 +- .../MessageComponents/InvitationBubble.qml | 162 ++++++++++++------ 4 files changed, 154 insertions(+), 71 deletions(-) diff --git a/src/app/chat/views/communities.nim b/src/app/chat/views/communities.nim index cac0314e4f..d6fbe17fd2 100644 --- a/src/app/chat/views/communities.nim +++ b/src/app/chat/views/communities.nim @@ -174,28 +174,35 @@ QtObject: proc communityAdded*(self: CommunitiesView, communityId: string) {.signal.} + proc observedCommunityChanged*(self: CommunitiesView) {.signal.} + proc communityChanged*(self: CommunitiesView, communityId: string) {.signal.} + proc addCommunityToList*(self: CommunitiesView, community: Community) = let communityCheck = self.communityList.getCommunityById(community.id) if (communityCheck.id == ""): self.communityList.addCommunityItemToList(community) self.communityAdded(community.id) + self.communityChanged(community.id) else: self.communityList.replaceCommunity(community) + self.communityChanged(community.id) if (self.activeCommunity.active and self.activeCommunity.communityItem.id == community.id): self.activeCommunity.setCommunityItem(community) if (self.observedCommunity.communityItem.id == community.id): self.observedCommunity.setCommunityItem(community) + self.observedCommunityChanged() - if (community.joined == true): + if (community.joined == true and community.isMember == true): let joinedCommunityCheck = self.joinedCommunityList.getCommunityById(community.id) if (joinedCommunityCheck.id == ""): self.joinedCommunityList.addCommunityItemToList(community) else: self.joinedCommunityList.replaceCommunity(community) - elif (community.isMember == true): - discard self.joinCommunity(community.id, false) + self.joinedCommunitiesChanged() + + if (community.isMember == true): var i = 0 for communityRequest in self.myCommunityRequests: if (communityRequest.communityId == community.id): @@ -203,6 +210,9 @@ QtObject: self.myCommunityRequests.delete(i, i) break i = i + 1 + # TODO: handle membership request rejection + # @cammellos mentioned this would likely changed in Communities Phase 3, so + # no need to polish now. proc isCommunityRequestPending*(self: CommunitiesView, communityId: string): bool {.slot.} = for communityRequest in self.myCommunityRequests: @@ -277,8 +287,6 @@ QtObject: error "Error creating the category", msg = e.msg result = fmt"Error creating the category: {e.msg}" - proc observedCommunityChanged*(self: CommunitiesView) {.signal.} - proc setObservedCommunity*(self: CommunitiesView, communityId: string) {.slot.} = if(communityId == ""): return var community = self.communityList.getCommunityById(communityId) @@ -303,9 +311,12 @@ QtObject: if (communityId == self.activeCommunity.communityItem.id): self.activeCommunity.setActive(false) self.joinedCommunityList.removeCommunityItemFromList(communityId) + self.joinedCommunitiesChanged() var updatedCommunity = self.communityList.getCommunityById(communityId) updatedCommunity.joined = false self.communityList.replaceCommunity(updatedCommunity) + self.communitiesChanged() + self.communityChanged(communityId) except Exception as e: error "Error leaving the community", msg = e.msg result = fmt"Error leaving the community: {e.msg}" @@ -367,10 +378,21 @@ QtObject: except Exception as e: error "Error requesting to join the community", msg = e.msg + proc removeMembershipRequest(self: CommunitiesView, requestId: string, accepted: bool) = + var i = 0 + for request in self.myCommunityRequests: + if (request.id == requestId): + self.myCommunityRequests.delete(i, i) + let name = self.getCommunityNameById(request.communityId) + self.membershipRequestChanged(request.communityId, name, accepted) + break + i = i + 1 + self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId) + proc acceptRequestToJoinCommunity*(self: CommunitiesView, requestId: string): string {.slot.} = try: self.status.chat.acceptRequestToJoinCommunity(requestId) - self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId) + self.removeMembershipRequest(requestId, true) except Exception as e: error "Error accepting request to join the community", msg = e.msg return "Error accepting request to join the community" @@ -379,7 +401,7 @@ QtObject: proc declineRequestToJoinCommunity*(self: CommunitiesView, requestId: string): string {.slot.} = try: self.status.chat.declineRequestToJoinCommunity(requestId) - self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId) + self.removeMembershipRequest(requestId, false) except Exception as e: error "Error declining request to join the community", msg = e.msg return "Error declining request to join the community" diff --git a/src/app/chat/views/community_list.nim b/src/app/chat/views/community_list.nim index 2f52533d67..18aa938b17 100644 --- a/src/app/chat/views/community_list.nim +++ b/src/app/chat/views/community_list.nim @@ -1,8 +1,11 @@ -import NimQml, Tables, chronicles -import ../../../status/chat/chat -import ../../../status/status -import ../../../status/accounts -import strutils +import # std libs + NimQml, Tables, json, strutils + +import # vendor libs + chronicles, json_serialization + +import # status-desktop libs + ../../../status/chat/chat, ../../../status/status, ../../../status/accounts type CommunityRoles {.pure.} = enum @@ -117,6 +120,11 @@ QtObject: else: result = newQVariant("") of CommunityRoles.CommunityColor: result = newQVariant(communityItem.communityColor) + + proc getCommunityByIdJson(self: CommunityList, communityId: string): string {.slot.} = + for community in self.communities: + if (community.id == communityId): + result = Json.encode(community) method roleNames(self: CommunityList): Table[int, string] = { @@ -196,4 +204,4 @@ QtObject: if idx == -1: return community.categories.delete(idx) let index = self.communities.findIndexById(communityId) - self.communities[index] = community \ No newline at end of file + self.communities[index] = community diff --git a/src/status/libstatus/chat.nim b/src/status/libstatus/chat.nim index 1539647b7c..0466ed7978 100644 --- a/src/status/libstatus/chat.nim +++ b/src/status/libstatus/chat.nim @@ -315,7 +315,7 @@ proc createCommunity*(name: string, description: string, access: int, ensOnly: b if rpcResult{"error"} != nil: let error = Json.decode($rpcResult{"error"}, RpcError) - raise newException(RpcException, "Error editing community channel: " & error.message) + raise newException(RpcException, "Error creating community: " & error.message) if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull: result = rpcResult["result"]["communities"][0].toCommunity() @@ -338,7 +338,7 @@ proc editCommunity*(communityId: string, name: string, description: string, acce if rpcResult{"error"} != nil: let error = Json.decode($rpcResult{"error"}, RpcError) - raise newException(RpcException, "Error editing community channel: " & error.message) + raise newException(RpcException, "Error editing community: " & error.message) if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull: result = rpcResult["result"]["communities"][0].toCommunity() @@ -481,8 +481,7 @@ proc requestToJoinCommunity*(communityId: string, ensName: string): seq[Communit }]).parseJSON() var communityRequests: seq[CommunityMembershipRequest] = @[] - - if rpcResult{"result"}{"requestsToJoinCommunity"}.kind != JNull: + if rpcResult{"result"}{"requestsToJoinCommunity"} != nil and rpcResult{"result"}{"requestsToJoinCommunity"}.kind != JNull: for jsonCommunityReqest in rpcResult["result"]["requestsToJoinCommunity"]: communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest()) diff --git a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/InvitationBubble.qml b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/InvitationBubble.qml index b475b4785d..e7ba3f6458 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/InvitationBubble.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn/MessageComponents/InvitationBubble.qml @@ -17,12 +17,28 @@ Item { height: childrenRect.height width: rectangleBubbleLoader.width - Component.onCompleted: { - chatsModel.communities.setObservedCommunity(root.communityId) - - root.invitedCommunity = chatsModel.communities.observedCommunity + function getCommunity() { + let community = JSON.parse(chatsModel.communities.list.getCommunityByIdJson(communityId)); + if (community) { + community.nbMembers = community.members.length; + } + return community } + Component.onCompleted: { + root.invitedCommunity = getCommunity() + } + + Connections { + target: chatsModel.communities + onCommunityChanged: function (communityId) { + if (communityId === root.communityId) { + root.invitedCommunity = getCommunity() + } + } + } + + Loader { id: rectangleBubbleLoader active: !!invitedCommunity @@ -32,6 +48,8 @@ Item { sourceComponent: Component { Rectangle { id: rectangleBubble + property alias button: joinBtn + property bool isPendingRequest: chatsModel.communities.isCommunityRequestPending(communityId) width: 270 height: childrenRect.height + Style.current.halfPadding radius: 16 @@ -39,6 +57,74 @@ Item { border.color: Style.current.border border.width: 1 + states: [ + State { + name: "requiresEns" + when: invitedCommunity.ensOnly && !profileModel.profile.ensVerified + PropertyChanges { + target: joinBtn + text: qsTr("Membership requires an ENS username") + enabled: false + } + }, + State { + name: "inviteOnly" + when: invitedCommunity.access === Constants.communityChatInvitationOnlyAccess + PropertyChanges { + target: joinBtn + text: qsTr("You need to be invited") + enabled: false + } + }, + State { + name: "pending" + when: invitedCommunity.access === Constants.communityChatOnRequestAccess && + rectangleBubble.isPendingRequest + PropertyChanges { + target: joinBtn + text: qsTr("Pending") + enabled: false + } + }, + State { + name: "joined" + when: invitedCommunity.joined && invitedCommunity.isMember + PropertyChanges { + target: joinBtn + text: qsTr("View") + } + }, + State { + name: "requestToJoin" + when: invitedCommunity.access === Constants.communityChatOnRequestAccess && + // !invitedCommunity.joined && !invitedCommunity.isMember + invitedCommunity.canRequestAccess + PropertyChanges { + target: joinBtn + text: qsTr("Request to join") + + } + }, + State { + name: "unjoined" + when: invitedCommunity.access === Constants.communityChatOnRequestAccess && + invitedCommunity.isMember + PropertyChanges { + target: joinBtn + text: qsTr("Join") + } + } + ] + + Connections { + target: chatsModel.communities + onMembershipRequestChanged: function(communityId, communityName, requestAccepted) { + if (communityId === root.communityId) { + rectangleBubble.isPendingRequest = false + } + } + } + // TODO add check if verified StyledText { id: title @@ -58,10 +144,18 @@ Item { StyledText { id: invitedYou - text: isCurrentUser ? + text: { + if (chatsModel.channelView.activeChannel.chatType === Constants.chatTypeOneToOne) { + return isCurrentUser ? qsTr("You invited %1 to join a community").arg(chatsModel.userNameOrAlias(chatsModel.channelView.activeChannel.id)) - //% "%1 invited you to join a community" - : qsTrId("-1-invited-you-to-join-a-community").arg(displayUserName) + //% "%1 invited you to join a community" + : qsTrId("-1-invited-you-to-join-a-community").arg(displayUserName) + } else { + return isCurrentUser ? + qsTr("You shared a community") + : qsTr("A community has been shared") + } + } anchors.top: title.bottom anchors.topMargin: 4 anchors.left: parent.left @@ -127,69 +221,29 @@ Item { } StatusButton { - property int access: invitedCommunity.access - property bool isPendingRequest: { - if (invitedCommunity.access !== Constants.communityChatOnRequestAccess) { - return false - } - return chatsModel.communities.isCommunityRequestPending(communityId) - } id: joinBtn type: "secondary" anchors.top: sep2.bottom width: parent.width height: 44 - enabled: { - if (invitedCommunity.ensOnly && !profileModel.profile.ensVerified) { - return false - } - if (joinBtn.access === Constants.communityChatInvitationOnlyAccess || isPendingRequest) { - return false - } - - return true - } - text: { - if (invitedCommunity.ensOnly && !profileModel.profile.ensVerified) { - return qsTr("Membership requires an ENS username") - } - if (invitedCommunity.canJoin) { - return qsTr("Join") - } - if (invitedCommunity.joined || invitedCommunity.isMember) { - return qsTr("View") - } - if (isPendingRequest) { - return qsTr("Pending") - } - - switch(joinBtn.access) { - case Constants.communityChatPublicAccess: return qsTr("Join") - case Constants.communityChatInvitationOnlyAccess: return qsTr("You need to be invited"); - case Constants.communityChatOnRequestAccess: return qsTr("Request to join") - default: return qsTr("Unknown community"); - } - } + enabled: true + text: qsTr("Unsupported state") onClicked: { let error - if (invitedCommunity.joined || invitedCommunity.isMember) { + if (rectangleBubble.state === "joined") { chatsModel.communities.setActiveCommunity(communityId); return + } else if (rectangleBubble.state === "unjoined") { + error = chatsModel.communities.joinCommunity(communityId, true) } - - if (joinBtn.access === Constants.communityChatOnRequestAccess) { + else if (rectangleBubble.state === "requestToJoin") { error = chatsModel.communities.requestToJoinCommunity(communityId, profileModel.profile.ensVerified ? profileModel.profile.username : "") if (!error) { - enabled = false - text = qsTr("Pending") + rectangleBubble.isPendingRequest = chatsModel.communities.isCommunityRequestPending(communityId) } - } else { - error = chatsModel.communities.joinCommunity(communityId, true) - enabled = false - text = qsTr("Joined") } if (error) {