feat(Settings/Communities): implement new communities list

Closes #11145
This commit is contained in:
Lukáš Tinkl 2023-06-21 22:37:51 +02:00 committed by Lukáš Tinkl
parent 9df6e68a77
commit 620c7a746d
16 changed files with 446 additions and 132 deletions

View File

@ -69,6 +69,10 @@ ListModel {
title: "DeviceSyncingView"
section: "Views"
}
ListElement {
title: "CommunitiesView"
section: "Views"
}
ListElement {
title: "StatusCommunityCard"
section: "Panels"

View File

@ -221,5 +221,8 @@
],
"StatusButton": [
"https://www.figma.com/file/MtAO3a7HnEH5xjCDVNilS7/%F0%9F%8E%A8-Design-System-%E2%8E%9C-Desktop?type=design&node-id=1-12&t=UHegCbqAa5K7qUKd-0"
],
"CommunitiesView": [
"https://www.figma.com/file/idUoxN7OIW2Jpp3PMJ1Rl8/%E2%9A%99%EF%B8%8F-Settings-%7C-Desktop?type=design&node-id=16089-387522&t=HRT9BmZXnl7Lt55Q-0"
]
}

View File

@ -0,0 +1,161 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import StatusQ.Core 0.1
import AppLayouts.Profile.views 1.0
import mainui 1.0
import utils 1.0
import Storybook 1.0
import Models 1.0
SplitView {
id: root
Logs { id: logs }
orientation: Qt.Vertical
Popups {
popupParent: root
rootStore: QtObject {}
}
ListModel {
id: emptyModel
}
ListModel {
id: communitiesModel
Component.onCompleted:
append([{
id: "0x0001",
name: "Test community",
description: "Lorem ipsum dolor sit amet",
introMessage: "Welcome to ze club",
outroMessage: "Sad to see you go",
joined: true,
spectated: false,
memberRole: Constants.memberRole.owner,
image: ModelsData.icons.dribble,
color: "yellow",
muted: false,
members: [ { pubKey: "0xdeadbeef" } ]
},
{
id: "0x0002",
name: "Test community 2",
description: "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.",
introMessage: "Welcome to ze club",
outroMessage: "Sad to see you go",
joined: true,
spectated: false,
memberRole: Constants.memberRole.none,
image: ModelsData.icons.status,
color: "peach",
muted: false,
members: [ { pubKey: "0xdeadbeef" }, { pubKey: "0xdeadbeef" }, { pubKey: "0xdeadbeef" } ]
},
{
id: "0x0003",
name: "Free to join",
introMessage: "Welcome to ze club",
description: "Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquid ex ea commodi consequat.",
outroMessage: "Sad to see you go",
joined: false,
spectated: true,
memberRole: Constants.memberRole.none,
image: "",
color: "red",
muted: false,
members: [ { pubKey: "0xdeadbeef" } ]
},
{
id: "0x0004",
name: "Muted community",
introMessage: "Welcome to ze club",
description: "Lorem ipsum dolor sit amet",
outroMessage: "Sad to see you go",
joined: true,
spectated: false,
memberRole: Constants.memberRole.none,
image: "",
color: "whitesmoke",
muted: true,
members: []
},
{
id: "0x0005",
name: "Test community 4",
description: "Lorem ipsum dolor sit amet",
introMessage: "Welcome to ze club",
outroMessage: "Sad to see you go",
joined: true,
spectated: false,
memberRole: Constants.memberRole.admin,
image: ModelsData.icons.spotify,
color: "green",
muted: false,
members: [{ pubKey: "0xdeadbeef" }, { pubKey: "0xdeadbeef" }, { pubKey: "0xdeadbeef" }, { pubKey: "0xdeadbeef" }]
},
{
id: "0x0006",
name: "Pending request here",
description: "Lorem ipsum dolor sit amet",
introMessage: "Welcome to ze club",
outroMessage: "Sad to see you go",
joined: false,
spectated: true,
memberRole: Constants.memberRole.none,
image: ModelsData.icons.spotify,
color: "pink",
muted: false,
members: [{ pubKey: "0xdeadbeef" }]
}
])
}
CommunitiesView {
SplitView.fillWidth: true
SplitView.preferredHeight: 400
contentWidth: 664
profileSectionStore: QtObject {
property var communitiesProfileModule: QtObject {
function setCommunityMuted(communityId, mutedType) {
logs.logEvent("profileSectionStore::communitiesProfileModule::setCommunityMuted", ["communityId", "mutedType"], arguments)
}
function leaveCommunity(communityId) {
logs.logEvent("profileSectionStore::communitiesProfileModule::leaveCommunity", ["communityId"], arguments)
}
}
property var communitiesList: ctrlEmptyView.checked ? emptyModel : communitiesModel
}
rootStore: QtObject {
function isCommunityRequestPending(communityId) {
return communityId === "0x0006"
}
function cancelPendingRequest(communityId) {
logs.logEvent("rootStore::cancelPendingRequest", ["communityId"], arguments)
}
function setActiveCommunity(communityId) {
logs.logEvent("rootStore::setActiveCommunity", ["communityId"], arguments)
}
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumHeight: 100
SplitView.preferredHeight: 200
logsView.logText: logs.logText
Switch {
id: ctrlEmptyView
text: "No communities"
}
}
}

View File

@ -143,11 +143,13 @@ StatusListItem {
statusListItemIcon.badge.implicitHeight: 12 // 8 px + 2 px * 2 borders
statusListItemIcon.badge.implicitWidth: 12 // 8 px + 2 px * 2 borders
components: [
StatusIcon {
anchors.verticalCenter: parent.verticalCenter
visible: root.isAdmin
icon: "crown"
color: Theme.palette.directColor1
Loader {
active: root.isAdmin
sourceComponent: StatusIcon {
anchors.verticalCenter: parent.verticalCenter
icon: "crown"
color: Theme.palette.directColor1
}
}
]
}

View File

@ -91,25 +91,6 @@ StackLayout {
}
}
}
CommunityIntroDialog {
id: communityIntroDialog
isInvitationPending: joinCommunityView.isInvitationPending
name: communityData.name
introMessage: communityData.introMessage
imageSrc: communityData.image
accessType: communityData.access
onJoined: {
root.rootStore.requestToJoinCommunityWithAuthentication(communityData.id, root.rootStore.userProfileInst.name)
}
onCancelMembershipRequest: {
root.rootStore.cancelPendingRequest(communityData.id)
joinCommunityView.isInvitationPending = root.rootStore.isCommunityRequestPending(communityData.id)
}
}
}
}
@ -136,7 +117,7 @@ StackLayout {
viewAndPostPermissionsModel: root.permissionsStore.viewAndPostPermissionsModel
assetsModel: root.rootStore.assetsModel
collectiblesModel: root.rootStore.collectiblesModel
isInvitationPending: root.rootStore.isCommunityRequestPending(root.sectionItemModel.id)
isInvitationPending: root.rootStore.isCommunityRequestPending(chatView.communityId)
onCommunityInfoButtonClicked: root.currentIndex = 1
onCommunityManageButtonClicked: root.currentIndex = 1
@ -149,8 +130,8 @@ StackLayout {
}
onRevealAddressClicked: {
Global.openPopup(communityIntroDialogPopup, {
communityId: root.sectionItemModel.id,
isInvitationPending: root.rootStore.isCommunityRequestPending(root.sectionItemModel.id),
communityId: chatView.communityId,
isInvitationPending: root.rootStore.isCommunityRequestPending(chatView.communityId),
name: root.sectionItemModel.name,
introMessage: root.sectionItemModel.introMessage,
imageSrc: root.sectionItemModel.image,
@ -158,8 +139,8 @@ StackLayout {
})
}
onInvitationPendingClicked: {
root.rootStore.cancelPendingRequest(root.sectionItemModel.id)
chatView.isInvitationPending = root.rootStore.isCommunityRequestPending(root.sectionItemModel.id)
root.rootStore.cancelPendingRequest(chatView.communityId)
chatView.isInvitationPending = root.rootStore.isCommunityRequestPending(chatView.communityId)
}
}
}

View File

@ -108,11 +108,10 @@ SettingsPageLayout {
model: root.membersModel
rootStore: root.rootStore
placeholderText: {
if (root.membersModel.count === 0) {
if (root.membersModel.count === 0)
return qsTr("No members to search")
} else {
return qsTr("Search %1's %n member(s)", "", root.membersModel ? root.membersModel.count : 0).arg(root.communityName)
}
return qsTr("Search %1's %n member(s)", "", root.membersModel ? root.membersModel.count : 0).arg(root.communityName)
}
panelType: CommunityMembersTabPanel.TabType.AllMembers
@ -136,13 +135,10 @@ SettingsPageLayout {
model: root.pendingMemberRequestsModel
rootStore: root.rootStore
placeholderText: {
if (root.pendingMemberRequestsModel.count === 0) {
if (root.pendingMemberRequestsModel.count === 0)
return qsTr("No pending requests to search")
} else {
return qsTr("Search %1's %2 pending request%3").arg(root.communityName)
.arg(root.pendingMemberRequestsModel.count)
.arg(root.pendingMemberRequestsModel.count > 1 ? "s" : "")
}
return qsTr("Search %1's %n pending request(s)", "", root.pendingMemberRequestsModel.count).arg(root.communityName)
}
panelType: CommunityMembersTabPanel.TabType.PendingRequests
@ -157,13 +153,10 @@ SettingsPageLayout {
model: root.declinedMemberRequestsModel
rootStore: root.rootStore
placeholderText: {
if (root.declinedMemberRequestsModel.count === 0) {
if (root.declinedMemberRequestsModel.count === 0)
return qsTr("No rejected members to search")
} else {
return qsTr("Search %1's %2 rejected member%3").arg(root.communityName)
.arg(root.declinedMemberRequestsModel.count)
.arg(root.declinedMemberRequestsModel.count > 1 ? "s" : "")
}
return qsTr("Search %1's %n rejected member(s)", "", root.declinedMemberRequestsModel.count).arg(root.communityName)
}
panelType: CommunityMembersTabPanel.TabType.DeclinedRequests
@ -177,13 +170,10 @@ SettingsPageLayout {
model: root.bannedMembersModel
rootStore: root.rootStore
placeholderText: {
if (root.bannedMembersModel.count === 0) {
if (root.bannedMembersModel.count === 0)
return qsTr("No banned members to search")
} else {
return qsTr("Search %1's %2 banned member%3").arg(root.communityName)
.arg(root.bannedMembersModel.count)
.arg(root.bannedMembersModel.count > 1 ? "s" : "")
}
return qsTr("Search %1's %n banned member(s)", "", root.bannedMembersModel.count).arg(root.communityName)
}
panelType: CommunityMembersTabPanel.TabType.BannedMembers

View File

@ -165,7 +165,7 @@ Item {
ProfileContextMenu {
id: memberContextMenuView
store: root.rootStore
myPublicKey: root.rootStore.myPublicKey()
myPublicKey: userProfile.pubKey
onOpenProfileClicked: {
Global.openProfilePopup(publicKey, null)

View File

@ -632,6 +632,7 @@ StatusSectionLayout {
TransferOwnershipPopup {
anchors.centerIn: parent
store: root.rootStore
onClosed: destroy()
}
}

View File

@ -300,7 +300,6 @@ StatusSectionLayout {
profileSectionStore: root.store
rootStore: root.globalStore
contactStore: root.store.contactsStore
sectionTitle: root.store.getNameForSubsection(Constants.settingsSubsection.communitiesSettings)
contentWidth: d.contentWidth
}

View File

@ -8,88 +8,155 @@ import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import utils 1.0
import shared.controls.chat.menuItems 1.0
StatusListView {
id: root
property bool hasAddedContacts: false
property var rootStore
signal inviteFriends(var communityData)
signal closeCommunityClicked(string communityId)
signal leaveCommunityClicked(string community, string communityId, string outroMessage)
signal setCommunityMutedClicked(string communityId, int mutedType)
signal setActiveCommunityClicked(string communityId)
signal showCommunityIntroDialog(string communityId, string name, string introMessage, string imageSrc, int accessType)
signal cancelMembershipRequest(string communityId)
interactive: false
implicitHeight: contentItem.childrenRect.height
spacing: 0
delegate: StatusListItem {
id: statusCommunityItem
width: parent.width
id: listItem
width: ListView.view.width
title: model.name
statusListItemTitle.font.pixelSize: 17
statusListItemTitle.font.bold: true
statusListItemIcon.anchors.verticalCenter: undefined
statusListItemIcon.anchors.top: statusListItemTitleArea.top
subTitle: model.description
tertiaryTitle: qsTr("%n member(s)", "", model.members.count)
statusListItemTertiaryTitle.font.weight: Font.Medium
asset.name: model.image
asset.isLetterIdenticon: !model.image
asset.bgColor: model.color || Theme.palette.primaryColor1
asset.width: 40
asset.height: 40
visible: model.joined
height: visible ? implicitHeight: 0
onClicked: setActiveCommunityClicked(model.id)
components: [
readonly property bool isOwner: model.memberRole === Constants.memberRole.owner
readonly property bool isAdmin: model.memberRole === Constants.memberRole.admin
readonly property bool isInvitationPending: root.rootStore.isCommunityRequestPending(model.id)
components: [
StatusFlatButton {
anchors.verticalCenter: parent.verticalCenter
size: StatusBaseButton.Size.Small
icon.name: "dots-icon"
onClicked: menu.popup(0, height)
icon.name: "notification-muted"
icon.color: Theme.palette.baseColor1
visible: model.muted
onClicked: root.setCommunityMutedClicked(model.id, Constants.MutingVariations.Unmuted)
},
StatusFlatButton {
anchors.verticalCenter: parent.verticalCenter
size: StatusBaseButton.Size.Small
text: listItem.isInvitationPending ? qsTr("Membership Request Sent") : qsTr("View & Join Community")
visible: model.spectated
onClicked: root.showCommunityIntroDialog(model.id, model.name, model.introMessage, model.image, model.access)
},
StatusFlatButton {
anchors.verticalCenter: parent.verticalCenter
size: StatusBaseButton.Size.Small
icon.name: "more"
icon.color: Theme.palette.directColor1
highlighted: moreMenu.opened
onClicked: moreMenu.popup(-moreMenu.width + width, height + 4)
property StatusMenu menu: StatusMenu {
id: communityContextMenu
width: 180
StatusMenu {
id: moreMenu
StatusAction {
text: qsTr("Invite People")
icon.name: "share-ios"
enabled: model.canManageUsers
onTriggered: root.inviteFriends(model)
}
MuteChatMenuItem {
enabled: !model.muted
title: qsTr("Mute Community")
onMuteTriggered: {
root.setCommunityMutedClicked(model.id, interval)
communityContextMenu.close()
text: qsTr("Community Admin")
icon.name: "settings"
enabled: listItem.isOwner || listItem.isAdmin
onTriggered: {
moreMenu.close()
Global.switchToCommunity(model.id)
Global.switchToCommunitySettings(model.id)
}
}
StatusAction {
enabled: model.muted
text: qsTr("Unmute Community")
icon.name: "notification-muted"
onTriggered: root.setCommunityMutedClicked(model.id, Constants.MutingVariations.Unmuted)
enabled: model.muted
icon.name: "notification"
onTriggered: {
moreMenu.close()
root.setCommunityMutedClicked(model.id, Constants.MutingVariations.Unmuted)
}
}
MuteChatMenuItem {
enabled: (model.joined || (model.spectated && !listItem.isInvitationPending)) && !model.muted
title: qsTr("Mute Community")
onMuteTriggered: {
moreMenu.close()
root.setCommunityMutedClicked(model.id, interval)
}
}
StatusMenuSeparator {}
StatusAction {
text: model.spectated ? qsTr("Close Community") : qsTr("Leave Community")
icon.name: "arrow-left"
text: qsTr("Invite People")
icon.name: "invite-users"
onTriggered: {
moreMenu.close()
root.inviteFriends(model)
}
}
StatusAction {
id: shareAddressesMenuItem
text: qsTr("Edit Shared Addresses")
icon.name: "wallet"
enabled: {
if (listItem.isOwner)
return false
if (model.spectated && !listItem.isInvitationPending)
return false
return true
}
onTriggered: {
moreMenu.close()
Global.openEditSharedAddressesFlow(model.id)
// TODO shared addresses flow, cf https://github.com/status-im/status-desktop/issues/11138
}
}
StatusMenuSeparator {
visible: shareAddressesMenuItem.enabled && leaveMenuItem.enabled
}
StatusAction {
id: leaveMenuItem
objectName: "CommunitiesListPanel_leaveCommunityPopupButton"
text: {
if (listItem.isInvitationPending)
return qsTr("Cancel Membership Request")
return model.spectated ? qsTr("Close Community") : qsTr("Leave Community")
}
icon.name: {
if (listItem.isInvitationPending)
return "arrow-left"
return model.spectated ? "close-circle" : "arrow-left"
}
type: StatusAction.Type.Danger
onTriggered: model.spectated ? root.closeCommunityClicked(model.id)
: root.leaveCommunityClicked(model.name, model.id, model.outroMessage)
enabled: !listItem.isOwner
onTriggered: {
moreMenu.close()
if (listItem.isInvitationPending)
root.cancelMembershipRequest(model.id)
else if (model.spectated)
root.closeCommunityClicked(model.id)
else
root.leaveCommunityClicked(model.name, model.id, model.outroMessage)
}
}
}
}

View File

@ -23,7 +23,6 @@ SettingsContentBase {
property var profileSectionStore
property var rootStore
property var contactStore
clip: true
@ -41,7 +40,7 @@ SettingsContentBase {
ColumnLayout {
id: noCommunitiesLayout
anchors.fill: parent
visible: communitiesList.count === 0
visible: !root.profileSectionStore.communitiesList.count
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
Image {
@ -90,53 +89,141 @@ SettingsContentBase {
anchors.left: parent.left
spacing: Style.current.padding
StatusBaseText {
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
color: Theme.palette.baseColor1
text: qsTr("Communities you've joined")
Heading {
text: qsTr("Owner")
visible: panelOwners.count
}
CommunitiesListPanel {
id: communitiesList
objectName: "CommunitiesView_communitiesListPanel"
width: parent.width
model: SortFilterProxyModel {
id: filteredModel
sourceModel: root.profileSectionStore.communitiesList
filters: [
ValueFilter {
roleName: "joined"
value: true
}
]
Panel {
id: panelOwners
filters: ValueFilter {
readonly property int role: Constants.memberRole.owner
roleName: "memberRole"
value: role
}
}
onCloseCommunityClicked: {
root.profileSectionStore.communitiesProfileModule.leaveCommunity(communityId)
Heading {
text: qsTr("Admin")
visible: panelAdmins.count
}
Panel {
id: panelAdmins
filters: ValueFilter {
readonly property int role: Constants.memberRole.admin
roleName: "memberRole"
value: role
}
}
onLeaveCommunityClicked: {
Global.leaveCommunityRequested(community, communityId, outroMessage)
Heading {
text: qsTr("Member")
visible: panelMembers.count
}
Panel {
id: panelMembers
filters: ExpressionFilter {
readonly property int ownerRole: Constants.memberRole.owner
readonly property int adminRole: Constants.memberRole.admin
expression: model.joined && model.memberRole !== ownerRole && model.memberRole !== adminRole
}
}
onSetCommunityMutedClicked: {
root.profileSectionStore.communitiesProfileModule.setCommunityMuted(communityId, mutedType)
}
Heading {
text: qsTr("Pending")
visible: panelPendingRequests.count
}
onSetActiveCommunityClicked: {
rootStore.setActiveCommunity(communityId)
}
onInviteFriends: {
Global.openInviteFriendsToCommunityPopup(communityData,
root.profileSectionStore.communitiesProfileModule,
null)
Panel {
id: panelPendingRequests
filters: ValueFilter {
roleName: "spectated"
value: true
}
}
}
}
component Heading: StatusBaseText {
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
color: Theme.palette.baseColor1
}
component Panel: CommunitiesListPanel {
id: panel
property var filters
width: parent.width
rootStore: root.rootStore
model: SortFilterProxyModel {
sourceModel: root.profileSectionStore.communitiesList
filters: panel.filters
}
onCloseCommunityClicked: {
root.profileSectionStore.communitiesProfileModule.leaveCommunity(communityId)
}
onLeaveCommunityClicked: {
Global.leaveCommunityRequested(community, communityId, outroMessage)
}
onSetCommunityMutedClicked: {
root.profileSectionStore.communitiesProfileModule.setCommunityMuted(communityId, mutedType)
}
onSetActiveCommunityClicked: {
rootStore.setActiveCommunity(communityId)
}
onInviteFriends: {
Global.openInviteFriendsToCommunityPopup(communityData,
root.profileSectionStore.communitiesProfileModule,
null)
}
onShowCommunityIntroDialog: {
Global.openPopup(communityIntroDialogPopup, {
communityId: communityId,
isInvitationPending: root.rootStore.isCommunityRequestPending(communityId),
name: name,
introMessage: introMessage,
imageSrc: imageSrc,
accessType: accessType
})
}
onCancelMembershipRequest: {
root.rootStore.cancelPendingRequest(communityId)
}
}
readonly property var communityIntroDialogPopup: Component {
id: communityIntroDialogPopup
CommunityIntroDialog {
id: communityIntroDialog
property string communityId
readonly property var chatCommunitySectionModule: {
root.rootStore.mainModuleInst.prepareCommunitySectionModuleForCommunityId(communityIntroDialog.communityId)
return root.rootStore.mainModuleInst.getCommunitySectionModule()
}
onJoined: {
chatCommunitySectionModule.requestToJoinCommunityWithAuthentication(communityIntroDialog.communityId, root.rootStore.userProfileInst.name)
}
onCancelMembershipRequest: {
root.rootStore.cancelPendingRequest(communityIntroDialog.communityId)
}
onClosed: {
destroy()
}
}
}
}

View File

@ -93,6 +93,14 @@ QtObject {
return communitiesModuleInst.isMemberOfCommunity(communityId, pubKey)
}
function isCommunityRequestPending(id: string) {
return communitiesModuleInst.isCommunityRequestPending(id)
}
function cancelPendingRequest(id: string) {
communitiesModuleInst.cancelRequestToJoinCommunity(id)
}
function copyToClipboard(text) {
globalUtils.copyToClipboard(text)
}

View File

@ -369,7 +369,7 @@ Item {
StatusAction {
enabled: model.muted
text: qsTr("Unmute Community")
icon.name: "notification-muted"
icon.name: "notification"
onTriggered: {
communityContextMenu.chatCommunitySectionModule.setCommunityMuted(Constants.MutingVariations.Unmuted)
}
@ -1096,6 +1096,15 @@ Item {
restoreMode: Binding.RestoreBindingOrValue
}
Connections {
target: Global
function onSwitchToCommunitySettings(communityId: string) {
if (communityId !== model.id)
return
chatLayoutComponent.currentIndex = 1 // Settings
}
}
emojiPopup: statusEmojiPopup.item
stickersPopup: statusStickersPopupLoader.item
sectionItemModel: model

View File

@ -549,8 +549,8 @@ QtObject {
type: StatusBaseButton.Type.Danger
text: qsTr("Leave %1").arg(leavePopup.community)
onClicked: {
root.rootStore.profileSectionStore.communitiesProfileModule.leaveCommunity(leavePopup.communityId)
leavePopup.close()
root.rootStore.profileSectionStore.communitiesProfileModule.leaveCommunity(leavePopup.communityId)
}
}
]

View File

@ -14,7 +14,7 @@ StatusMenu {
title: isCommunityChat ? qsTr("Mute Channel") : qsTr("Mute Chat")
assetSettings.name: "notification"
assetSettings.name: "notification-muted"
StatusAction {
text: qsTr("For 15 mins")
@ -37,7 +37,7 @@ StatusMenu {
}
StatusAction {
text: qsTr("Until you turn it back on")
text: qsTr("Until I turn it back on")
onTriggered: muteTriggered(Constants.MutingVariations.TillUnmuted)
}
}

View File

@ -54,9 +54,11 @@ QtObject {
signal openSendModal(string address)
signal switchToCommunity(string communityId)
signal switchToCommunitySettings(string communityId)
signal createCommunityPopupRequested(bool isDiscordImport)
signal importCommunityPopupRequested()
signal leaveCommunityRequested(string community, string communityId, string outroMessage)
signal openEditSharedAddressesFlow(string communityId)
signal playSendMessageSound()
signal playNotificationSound()