fix(@desktop/chat): rework invitation bubbles

InvitationBubbleView.qml:
- rework view to use layouts properly
- add missing community identicon

Backend:
- remove local community requests on community join
- propagate SIGNAL_COMMUNITY_MY_REQUEST_ADDED to UI

fixes: #7139
This commit is contained in:
Patryk Osmaczko 2022-09-13 09:56:47 +02:00 committed by osmaczko
parent 6b25ea67a8
commit d1b3e7af9e
8 changed files with 269 additions and 217 deletions

View File

@ -62,6 +62,10 @@ proc init*(self: Controller) =
let args = CommunityMutedArgs(e)
self.delegate.communityMuted(args.communityId, args.muted)
self.events.on(SIGNAL_COMMUNITY_MY_REQUEST_ADDED) do(e:Args):
let args = CommunityRequestArgs(e)
self.delegate.communityAccessRequested(args.communityRequest.communityId)
self.events.on(SIGNAL_DISCORD_CATEGORIES_AND_CHANNELS_EXTRACTED) do(e:Args):
let args = DiscordCategoriesAndChannelsArgs(e)
self.delegate.discordCategoriesAndChannelsExtracted(args.categories, args.channels, args.oldestMessageTimestamp, args.errors, args.errorsCount)

View File

@ -109,6 +109,9 @@ method viewDidLoad*(self: AccessInterface) {.base.} =
method communityMuted*(self: AccessInterface, communityId: string, muted: bool) {.base.} =
raise newException(ValueError, "No implementation available")
method communityAccessRequested*(self: AccessInterface, communityId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method requestExtractDiscordChannelsAndCategories*(self: AccessInterface, filesToImport: seq[string]) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -232,6 +232,9 @@ method reorderCommunityCategories*(self: Module, communityId: string, categoryId
method communityMuted*(self: Module, communityId: string, muted: bool) =
self.view.model().setMuted(communityId, muted)
method communityAccessRequested*(self: Module, communityId: string) =
self.view.communityAccessRequested(communityId)
method discordCategoriesAndChannelsExtracted*(self: Module, categories: seq[DiscordCategoryDto], channels: seq[DiscordChannelDto], oldestMessageTimestamp: int, errors: Table[string, DiscordImportError], errorsCount: int) =
for filePath in errors.keys:

View File

@ -76,6 +76,7 @@ QtObject:
proc communityChanged*(self: View, communityId: string) {.signal.}
proc discordOldestMessageTimestampChanged*(self: View) {.signal.}
proc discordImportErrorsCountChanged*(self: View) {.signal.}
proc communityAccessRequested*(self: View, communityId: string) {.signal.}
proc setCommunityTags*(self: View, communityTags: string) =
self.communityTags = newQVariant(communityTags)

View File

@ -32,7 +32,7 @@ type CommunityMembershipRequestDto* = object
chatId*: string
communityId*: string
state*: int
our*: string
our*: string #FIXME: should be bool
type CommunitySettingsDto* = object
id*: string

View File

@ -1,4 +1,4 @@
import NimQml, Tables, json, sequtils, std/algorithm, strformat, strutils, chronicles, json_serialization
import NimQml, Tables, json, sequtils, std/algorithm, strformat, strutils, chronicles, json_serialization, sugar
import ./dto/community as community_dto
@ -265,6 +265,7 @@ QtObject:
if(not self.joinedCommunities.hasKey(community.id)):
if (community.joined and community.isMember):
self.joinedCommunities[community.id] = community
keepIf(self.myCommunityRequests, request => request.communityId != community.id)
self.events.emit(SIGNAL_COMMUNITY_JOINED, CommunityArgs(community: community, fromUserAction: false))
return

View File

@ -7,48 +7,60 @@ import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import shared.panels 1.0
import shared.popups 1.0
Item {
id: root
anchors.left: parent.left
height: rectangleBubbleLoader.height
width: rectangleBubbleLoader.width
property string communityId
property var invitedCommunity
property int innerMargin: 12
property bool isLink: false
implicitHeight: loader.height
implicitWidth: loader.width
property var store
property string communityId
function getCommunity() {
try {
const communityJson = root.store.getSectionByIdJson(communityId)
QtObject {
id: d
if (!communityJson) {
root.store.requestCommunityInfo(communityId)
return null
property var invitedCommunity
property bool invitationPending
readonly property int margin: 12
function getCommunity() {
try {
const communityJson = root.store.getSectionByIdJson(communityId)
if (!communityJson) {
root.store.requestCommunityInfo(communityId)
return null
}
return JSON.parse(communityJson);
} catch (e) {
console.error("Error parsing community", e)
}
return JSON.parse(communityJson);
} catch (e) {
console.error("Error parsing community", e)
return null
}
return null
function reevaluate() {
invitedCommunity = getCommunity()
invitationPending = root.store.isCommunityRequestPending(communityId)
}
}
Component.onCompleted: {
root.invitedCommunity = getCommunity()
d.reevaluate()
}
Connections {
target: root.store.communitiesModuleInst
onCommunityChanged: function (communityId) {
if (communityId === root.communityId) {
root.invitedCommunity = getCommunity()
d.reevaluate()
}
}
}
@ -57,135 +69,128 @@ Item {
target: root.store.communitiesModuleInst
onCommunityAdded: function (communityId) {
if (communityId === root.communityId) {
root.invitedCommunity = getCommunity()
d.reevaluate()
}
}
}
Component {
id: communityIntroDialog
CommunityIntroDialog {
anchors.centerIn: parent
property var joinMethod: () => {}
name: root.invitedCommunity ? root.invitedCommunity.name : ""
introMessage: root.invitedCommunity ? root.invitedCommunity.introMessage : ""
imageSrc: root.invitedCommunity ? root.invitedCommunity.image : ""
onJoined: joinMethod()
Connections {
target: root.store.communitiesModuleInst
onCommunityAccessRequested: function (communityId) {
if (communityId === root.communityId) {
d.reevaluate()
}
}
}
Loader {
id: rectangleBubbleLoader
active: !!invitedCommunity
id: loader
sourceComponent: Component {
Rectangle {
id: rectangleBubble
property alias button: joinBtn
property bool isPendingRequest: root.store.isCommunityRequestPending(communityId)
width: 270
height: column.implicitHeight
radius: 16
color: Style.current.background
border.color: Style.current.border
border.width: 1
active: !!d.invitedCommunity
states: [
State {
name: "requiresEns"
when: invitedCommunity.ensOnly && !userProfile.ensName
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) ||
(invitedCommunity.access === Constants.communityChatPublicAccess &&
invitedCommunity.joined)
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 Access")
sourceComponent: Rectangle {
id: rectangleBubble
}
},
State {
name: "unjoined"
when: (invitedCommunity.access === Constants.communityChatOnRequestAccess &&
invitedCommunity.isMember) ||
(invitedCommunity.access === Constants.communityChatPublicAccess &&
!invitedCommunity.joined)
PropertyChanges {
target: joinBtn
text: qsTr("Join")
}
width: 270
height: columnLayout.implicitHeight
radius: 16
color: Style.current.background
border.color: Style.current.border
border.width: 1
states: [
State {
name: "requiresEns"
when: d.invitedCommunity.ensOnly && !userProfile.ensName
PropertyChanges {
target: joinBtn
text: qsTr("Membership requires an ENS username")
enabled: false
}
]
},
State {
name: "inviteOnly"
when: d.invitedCommunity.access === Constants.communityChatInvitationOnlyAccess
PropertyChanges {
target: joinBtn
text: qsTr("You need to be invited")
enabled: false
}
},
State {
name: "pending"
when: d.invitedCommunity.access === Constants.communityChatOnRequestAccess &&
d.invitationPending
PropertyChanges {
target: joinBtn
text: qsTr("Pending")
enabled: false
}
},
State {
name: "joined"
when: (d.invitedCommunity.joined && d.invitedCommunity.isMember) ||
(d.invitedCommunity.access === Constants.communityChatPublicAccess &&
d.invitedCommunity.joined)
PropertyChanges {
target: joinBtn
text: qsTr("View")
}
},
State {
name: "requestToJoin"
when: d.invitedCommunity.access === Constants.communityChatOnRequestAccess &&
!d.invitedCommunity.joined
PropertyChanges {
target: joinBtn
text: qsTr("Request Access")
// Not Refactored Yet
// Connections {
// target: root.store.chatsModelInst.communities
// onMembershipRequestChanged: function(communityId, communityName, requestAccepted) {
// if (communityId === root.communityId) {
// rectangleBubble.isPendingRequest = false
// }
// }
// }
}
},
State {
name: "unjoined"
when: d.invitedCommunity.access === Constants.communityChatPublicAccess &&
!d.invitedCommunity.joined
PropertyChanges {
target: joinBtn
text: qsTr("Join")
}
}
]
ColumnLayout {
id: columnLayout
width: parent.width
spacing: 0
ColumnLayout {
id: column
width: parent.width
spacing: Style.current.halfPadding
id: invitationDescriptionLayout
Layout.leftMargin: d.margin
Layout.rightMargin: d.margin
Layout.topMargin: 8
Layout.bottomMargin: 8
spacing: 4
// TODO add check if verified
StatusBaseText {
id: title
color: invitedCommunity.verifed ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
text: invitedCommunity.verifed ?
qsTr("Verified community invitation") :
qsTr("Community invitation")
Layout.fillWidth: true
text: d.invitedCommunity.verifed ? qsTr("Verified community invitation") : qsTr("Community invitation")
color: d.invitedCommunity.verifed ? Theme.palette.primaryColor1 : Theme.palette.baseColor1
font.weight: Font.Medium
Layout.topMargin: Style.current.halfPadding
Layout.leftMargin: root.innerMargin
font.pixelSize: 13
}
StatusBaseText {
id: invitedYou
Layout.fillWidth: true
visible: text != ""
text: {
// Not Refactored Yet
@ -200,109 +205,145 @@ Item {
// : qsTr("A community has been shared")
// }
}
Layout.leftMargin: root.innerMargin
Layout.rightMargin: root.innerMargin
Layout.fillWidth: true
wrapMode: Text.WordWrap
font.pixelSize: 15
color: Theme.palette.directColor1
}
}
Separator {
Layout.fillWidth: true
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 1
color: Style.current.separator
}
// TODO add image when it's supported
StatusBaseText {
id: communityName
text: invitedCommunity.name
Layout.topMargin: 2
Layout.leftMargin: root.innerMargin
Layout.fillWidth: true
Layout.rightMargin: root.innerMargin
font.weight: Font.Bold
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
font.pixelSize: 17
color: Theme.palette.directColor1
}
RowLayout {
id: communityDescriptionLayout
StatusBaseText {
id: communityDesc
text: invitedCommunity.description
Layout.leftMargin: root.innerMargin
Layout.rightMargin: root.innerMargin
Layout.fillWidth: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
font.pixelSize: 15
color: Theme.palette.directColor1
}
Layout.leftMargin: d.margin
Layout.rightMargin: d.margin
Layout.topMargin: 12
Layout.bottomMargin: 12
StatusBaseText {
id: communityNbMembers
// TODO add the plural support
text: qsTr("%1 members").arg(invitedCommunity.nbMembers)
Layout.leftMargin: root.innerMargin
font.pixelSize: 13
font.weight: Font.Medium
color: Theme.palette.baseColor1
}
spacing: 12
Separator {
Layout.fillWidth: true
}
StatusSmartIdenticon {
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: 40
Layout.preferredHeight: 40
Item {
id: btnItemId
Layout.topMargin: -column.spacing
Layout.fillWidth: true
height: 44
clip: true
StatusFlatButton {
id: joinBtn
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
enabled: true
text: qsTr("Unsupported state")
onClicked: {
let error
name: d.invitedCommunity.name
if (rectangleBubble.state === "joined") {
root.store.setActiveCommunity(communityId);
return
}
if (rectangleBubble.state === "unjoined") {
Global.openPopup(communityIntroDialog, { joinMethod: () => {
let error = root.store.joinCommunity(communityId, userProfile.name)
if (error) joiningError.showError(error)
} });
}
else if (rectangleBubble.state === "requestToJoin") {
Global.openPopup(communityIntroDialog, { joinMethod: () => {
let error = root.store.requestToJoinCommunity(communityId, userProfile.name)
if (error) joiningError.showError(error)
else rectangleBubble.isPendingRequest = root.store.isCommunityRequestPending(communityId)
} });
}
if (error) joiningError.showError(error)
}
MessageDialog {
id: joiningError
function showError(error) {
joiningError.text = error
joiningError.open()
}
title: qsTr("Error joining the community")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
asset {
width: 40
height: 40
name: d.invitedCommunity.image
color: d.invitedCommunity.color
isImage: true
}
}
ColumnLayout {
spacing: 2
StatusBaseText {
Layout.fillWidth: true
text: d.invitedCommunity.name
font.weight: Font.Bold
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
font.pixelSize: 17
color: Theme.palette.directColor1
}
StatusBaseText {
Layout.fillWidth: true
text: d.invitedCommunity.description
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
font.pixelSize: 15
color: Theme.palette.directColor1
}
StatusBaseText {
Layout.fillWidth: true
// TODO add the plural support
text: qsTr("%1 members").arg(d.invitedCommunity.nbMembers)
font.pixelSize: 13
font.weight: Font.Medium
color: Theme.palette.baseColor1
}
}
}
Rectangle {
Layout.fillWidth: true
implicitHeight: 1
color: Style.current.separator
}
StatusFlatButton {
id: joinBtn
Layout.fillWidth: true
Layout.preferredHeight: 44
text: qsTr("Unsupported state")
onClicked: {
if (rectangleBubble.state === "joined") {
root.store.setActiveCommunity(communityId);
return
}
if (rectangleBubble.state === "unjoined") {
Global.openPopup(communityIntroDialog, { joinMethod: () => {
let error = root.store.joinCommunity(communityId, userProfile.name)
if (error) joiningError.showError(error)
} });
}
else if (rectangleBubble.state === "requestToJoin") {
Global.openPopup(communityIntroDialog, { joinMethod: () => {
let error = root.store.requestToJoinCommunity(communityId, userProfile.name)
if (error) joiningError.showError(error)
} });
}
}
Component.onCompleted: {
// FIXME: extract StatusButtonBackground or expose radius property in StatusBaseButton
background.radius = 16
}
}
}
}
}
Component {
id: communityIntroDialog
CommunityIntroDialog {
anchors.centerIn: parent
property var joinMethod: () => {}
name: d.invitedCommunity ? d.invitedCommunity.name : ""
introMessage: d.invitedCommunity ? d.invitedCommunity.introMessage : ""
imageSrc: d.invitedCommunity ? d.invitedCommunity.image : ""
onJoined: joinMethod()
}
}
MessageDialog {
id: joiningError
function showError(error) {
joiningError.text = error
joiningError.open()
}
title: qsTr("Error joining the community")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
}

View File

@ -240,7 +240,6 @@ Column {
InvitationBubbleView {
store: root.store
communityId: linkData.communityId
isLink: true
anchors.left: parent.left
}
}