fix(communities): re-joining of left communities

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.
This commit is contained in:
Eric Mastro 2021-06-24 17:21:45 +10:00 committed by Iuri Matias
parent 433698b252
commit e281ec3871
4 changed files with 154 additions and 71 deletions

View File

@ -174,28 +174,35 @@ QtObject:
proc communityAdded*(self: CommunitiesView, communityId: string) {.signal.} 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) = proc addCommunityToList*(self: CommunitiesView, community: Community) =
let communityCheck = self.communityList.getCommunityById(community.id) let communityCheck = self.communityList.getCommunityById(community.id)
if (communityCheck.id == ""): if (communityCheck.id == ""):
self.communityList.addCommunityItemToList(community) self.communityList.addCommunityItemToList(community)
self.communityAdded(community.id) self.communityAdded(community.id)
self.communityChanged(community.id)
else: else:
self.communityList.replaceCommunity(community) self.communityList.replaceCommunity(community)
self.communityChanged(community.id)
if (self.activeCommunity.active and self.activeCommunity.communityItem.id == community.id): if (self.activeCommunity.active and self.activeCommunity.communityItem.id == community.id):
self.activeCommunity.setCommunityItem(community) self.activeCommunity.setCommunityItem(community)
if (self.observedCommunity.communityItem.id == community.id): if (self.observedCommunity.communityItem.id == community.id):
self.observedCommunity.setCommunityItem(community) 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) let joinedCommunityCheck = self.joinedCommunityList.getCommunityById(community.id)
if (joinedCommunityCheck.id == ""): if (joinedCommunityCheck.id == ""):
self.joinedCommunityList.addCommunityItemToList(community) self.joinedCommunityList.addCommunityItemToList(community)
else: else:
self.joinedCommunityList.replaceCommunity(community) self.joinedCommunityList.replaceCommunity(community)
elif (community.isMember == true): self.joinedCommunitiesChanged()
discard self.joinCommunity(community.id, false)
if (community.isMember == true):
var i = 0 var i = 0
for communityRequest in self.myCommunityRequests: for communityRequest in self.myCommunityRequests:
if (communityRequest.communityId == community.id): if (communityRequest.communityId == community.id):
@ -203,6 +210,9 @@ QtObject:
self.myCommunityRequests.delete(i, i) self.myCommunityRequests.delete(i, i)
break break
i = i + 1 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.} = proc isCommunityRequestPending*(self: CommunitiesView, communityId: string): bool {.slot.} =
for communityRequest in self.myCommunityRequests: for communityRequest in self.myCommunityRequests:
@ -277,8 +287,6 @@ QtObject:
error "Error creating the category", msg = e.msg error "Error creating the category", msg = e.msg
result = fmt"Error creating the category: {e.msg}" result = fmt"Error creating the category: {e.msg}"
proc observedCommunityChanged*(self: CommunitiesView) {.signal.}
proc setObservedCommunity*(self: CommunitiesView, communityId: string) {.slot.} = proc setObservedCommunity*(self: CommunitiesView, communityId: string) {.slot.} =
if(communityId == ""): return if(communityId == ""): return
var community = self.communityList.getCommunityById(communityId) var community = self.communityList.getCommunityById(communityId)
@ -303,9 +311,12 @@ QtObject:
if (communityId == self.activeCommunity.communityItem.id): if (communityId == self.activeCommunity.communityItem.id):
self.activeCommunity.setActive(false) self.activeCommunity.setActive(false)
self.joinedCommunityList.removeCommunityItemFromList(communityId) self.joinedCommunityList.removeCommunityItemFromList(communityId)
self.joinedCommunitiesChanged()
var updatedCommunity = self.communityList.getCommunityById(communityId) var updatedCommunity = self.communityList.getCommunityById(communityId)
updatedCommunity.joined = false updatedCommunity.joined = false
self.communityList.replaceCommunity(updatedCommunity) self.communityList.replaceCommunity(updatedCommunity)
self.communitiesChanged()
self.communityChanged(communityId)
except Exception as e: except Exception as e:
error "Error leaving the community", msg = e.msg error "Error leaving the community", msg = e.msg
result = fmt"Error leaving the community: {e.msg}" result = fmt"Error leaving the community: {e.msg}"
@ -367,10 +378,21 @@ QtObject:
except Exception as e: except Exception as e:
error "Error requesting to join the community", msg = e.msg 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.} = proc acceptRequestToJoinCommunity*(self: CommunitiesView, requestId: string): string {.slot.} =
try: try:
self.status.chat.acceptRequestToJoinCommunity(requestId) self.status.chat.acceptRequestToJoinCommunity(requestId)
self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId) self.removeMembershipRequest(requestId, true)
except Exception as e: except Exception as e:
error "Error accepting request to join the community", msg = e.msg error "Error accepting request to join the community", msg = e.msg
return "Error accepting request to join the community" return "Error accepting request to join the community"
@ -379,7 +401,7 @@ QtObject:
proc declineRequestToJoinCommunity*(self: CommunitiesView, requestId: string): string {.slot.} = proc declineRequestToJoinCommunity*(self: CommunitiesView, requestId: string): string {.slot.} =
try: try:
self.status.chat.declineRequestToJoinCommunity(requestId) self.status.chat.declineRequestToJoinCommunity(requestId)
self.activeCommunity.communityMembershipRequestList.removeCommunityMembershipRequestItemFromList(requestId) self.removeMembershipRequest(requestId, false)
except Exception as e: except Exception as e:
error "Error declining request to join the community", msg = e.msg error "Error declining request to join the community", msg = e.msg
return "Error declining request to join the community" return "Error declining request to join the community"

View File

@ -1,8 +1,11 @@
import NimQml, Tables, chronicles import # std libs
import ../../../status/chat/chat NimQml, Tables, json, strutils
import ../../../status/status
import ../../../status/accounts import # vendor libs
import strutils chronicles, json_serialization
import # status-desktop libs
../../../status/chat/chat, ../../../status/status, ../../../status/accounts
type type
CommunityRoles {.pure.} = enum CommunityRoles {.pure.} = enum
@ -118,6 +121,11 @@ QtObject:
result = newQVariant("") result = newQVariant("")
of CommunityRoles.CommunityColor: result = newQVariant(communityItem.communityColor) 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] = method roleNames(self: CommunityList): Table[int, string] =
{ {
CommunityRoles.Name.int:"name", CommunityRoles.Name.int:"name",

View File

@ -315,7 +315,7 @@ proc createCommunity*(name: string, description: string, access: int, ensOnly: b
if rpcResult{"error"} != nil: if rpcResult{"error"} != nil:
let error = Json.decode($rpcResult{"error"}, RpcError) 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: if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull:
result = rpcResult["result"]["communities"][0].toCommunity() result = rpcResult["result"]["communities"][0].toCommunity()
@ -338,7 +338,7 @@ proc editCommunity*(communityId: string, name: string, description: string, acce
if rpcResult{"error"} != nil: if rpcResult{"error"} != nil:
let error = Json.decode($rpcResult{"error"}, RpcError) 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: if rpcResult{"result"} != nil and rpcResult{"result"}.kind != JNull:
result = rpcResult["result"]["communities"][0].toCommunity() result = rpcResult["result"]["communities"][0].toCommunity()
@ -481,8 +481,7 @@ proc requestToJoinCommunity*(communityId: string, ensName: string): seq[Communit
}]).parseJSON() }]).parseJSON()
var communityRequests: seq[CommunityMembershipRequest] = @[] var communityRequests: seq[CommunityMembershipRequest] = @[]
if rpcResult{"result"}{"requestsToJoinCommunity"} != nil and rpcResult{"result"}{"requestsToJoinCommunity"}.kind != JNull:
if rpcResult{"result"}{"requestsToJoinCommunity"}.kind != JNull:
for jsonCommunityReqest in rpcResult["result"]["requestsToJoinCommunity"]: for jsonCommunityReqest in rpcResult["result"]["requestsToJoinCommunity"]:
communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest()) communityRequests.add(jsonCommunityReqest.toCommunityMembershipRequest())

View File

@ -17,11 +17,27 @@ Item {
height: childrenRect.height height: childrenRect.height
width: rectangleBubbleLoader.width width: rectangleBubbleLoader.width
Component.onCompleted: { function getCommunity() {
chatsModel.communities.setObservedCommunity(root.communityId) let community = JSON.parse(chatsModel.communities.list.getCommunityByIdJson(communityId));
if (community) {
root.invitedCommunity = chatsModel.communities.observedCommunity 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 { Loader {
id: rectangleBubbleLoader id: rectangleBubbleLoader
@ -32,6 +48,8 @@ Item {
sourceComponent: Component { sourceComponent: Component {
Rectangle { Rectangle {
id: rectangleBubble id: rectangleBubble
property alias button: joinBtn
property bool isPendingRequest: chatsModel.communities.isCommunityRequestPending(communityId)
width: 270 width: 270
height: childrenRect.height + Style.current.halfPadding height: childrenRect.height + Style.current.halfPadding
radius: 16 radius: 16
@ -39,6 +57,74 @@ Item {
border.color: Style.current.border border.color: Style.current.border
border.width: 1 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 // TODO add check if verified
StyledText { StyledText {
id: title id: title
@ -58,10 +144,18 @@ Item {
StyledText { StyledText {
id: invitedYou 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)) qsTr("You invited %1 to join a community").arg(chatsModel.userNameOrAlias(chatsModel.channelView.activeChannel.id))
//% "%1 invited you to join a community" //% "%1 invited you to join a community"
: qsTrId("-1-invited-you-to-join-a-community").arg(displayUserName) : 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.top: title.bottom
anchors.topMargin: 4 anchors.topMargin: 4
anchors.left: parent.left anchors.left: parent.left
@ -127,69 +221,29 @@ Item {
} }
StatusButton { StatusButton {
property int access: invitedCommunity.access
property bool isPendingRequest: {
if (invitedCommunity.access !== Constants.communityChatOnRequestAccess) {
return false
}
return chatsModel.communities.isCommunityRequestPending(communityId)
}
id: joinBtn id: joinBtn
type: "secondary" type: "secondary"
anchors.top: sep2.bottom anchors.top: sep2.bottom
width: parent.width width: parent.width
height: 44 height: 44
enabled: { enabled: true
if (invitedCommunity.ensOnly && !profileModel.profile.ensVerified) { text: qsTr("Unsupported state")
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");
}
}
onClicked: { onClicked: {
let error let error
if (invitedCommunity.joined || invitedCommunity.isMember) { if (rectangleBubble.state === "joined") {
chatsModel.communities.setActiveCommunity(communityId); chatsModel.communities.setActiveCommunity(communityId);
return return
} else if (rectangleBubble.state === "unjoined") {
error = chatsModel.communities.joinCommunity(communityId, true)
} }
else if (rectangleBubble.state === "requestToJoin") {
if (joinBtn.access === Constants.communityChatOnRequestAccess) {
error = chatsModel.communities.requestToJoinCommunity(communityId, error = chatsModel.communities.requestToJoinCommunity(communityId,
profileModel.profile.ensVerified ? profileModel.profile.username : "") profileModel.profile.ensVerified ? profileModel.profile.username : "")
if (!error) { if (!error) {
enabled = false rectangleBubble.isPendingRequest = chatsModel.communities.isCommunityRequestPending(communityId)
text = qsTr("Pending")
} }
} else {
error = chatsModel.communities.joinCommunity(communityId, true)
enabled = false
text = qsTr("Joined")
} }
if (error) { if (error) {