refactor(Communities): use StackView inside profile popup for memberlist

Prior to this commit, the community memberlist was represented in a nested modal
which doesn't adhere to the designs. Rather, the section should render inside the
existing modal, requiring it to be refactored using a `StackView`.

This commit refactors the community profile popup so that the different content
sections ("Overview" and "MemberList") are rendered inside of the popup and can
be pushed onto and popped off a stack view.

The content components (newly introduced in this commit) `CommunityProfilePopupMembersList`
and `CommunityProfilePopupOverview` need to define a `headerTitle`, `headerDescription` and
if needed `imageSource` so they can alter the modal's header.

The same pattern might be used in other places of the modal if required.

Partially fixes #1959
This commit is contained in:
Pascal Precht 2021-03-16 14:07:09 +01:00
parent b9594591b0
commit 638892f21d
No known key found for this signature in database
GPG Key ID: 0EE28D8D6FD85D7D
3 changed files with 498 additions and 249 deletions

View File

@ -1,4 +1,5 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
@ -17,25 +18,38 @@ ModalPopup {
property string communityColor: ""
property int nbMembers: community.nbMembers
property bool isAdmin: false
height: (isAdmin ? 600 : 590) + descriptionText.height
height: stack.currentItem.height + modalHeader.height + modalFooter.height + Style.current.padding*3
id: popup
onClosed: {
while (stack.depth > 1) {
stack.pop()
}
}
header: Item {
id: modalHeader
height: childrenRect.height
width: parent.width
property string title: stack.currentItem.headerTitle
property string description: stack.currentItem.headerDescription
property string imageSource: stack.currentItem.headerImageSource
property bool useLetterIdenticon: !!stack.currentItem.useLetterIdenticon
Loader {
id: communityImg
sourceComponent: !!popup.source ? commmunityImgCmp : letterIdenticonCmp
sourceComponent: !modalHeader.useLetterIdenticon ? commmunityImgCmp : letterIdenticonCmp
active: !!modalHeader.imageSource || modalHeader.useLetterIdenticon
}
Component {
id: commmunityImgCmp
RoundedImage {
source: popup.source
source: modalHeader.imageSource
width: 40
height: 40
visible: !!modalHeader.imageSource
}
}
@ -47,34 +61,23 @@ ModalPopup {
chatName: popup.name
color: popup.communityColor
}
}
}
StyledTextEdit {
id: communityName
text: popup.name
text: modalHeader.title
anchors.top: parent.top
anchors.topMargin: 2
anchors.left: communityImg.right
anchors.leftMargin: Style.current.smallPadding
anchors.left: communityImg.active ? communityImg.right : parent.left
anchors.leftMargin: communityImg.active ? Style.current.smallPadding : 0
font.bold: true
font.pixelSize: 17
readOnly: true
}
StyledText {
text: {
switch(access) {
//% "Public community"
case Constants.communityChatPublicAccess: return qsTrId("public-community");
//% "Invitation only community"
case Constants.communityChatInvitationOnlyAccess: return qsTrId("invitation-only-community");
//% "On request community"
case Constants.communityChatOnRequestAccess: return qsTrId("on-request-community");
//% "Unknown community"
default: return qsTrId("unknown-community");
}
}
id: headerDescription
text: modalHeader.description
anchors.left: communityName.left
anchors.top: communityName.bottom
anchors.topMargin: 2
@ -84,7 +87,7 @@ ModalPopup {
}
Separator {
anchors.top: communityImg.bottom
anchors.top: headerDescription.bottom
anchors.topMargin: Style.current.padding
anchors.left: parent.left
anchors.right: parent.right
@ -93,242 +96,61 @@ ModalPopup {
}
}
StyledText {
id: descriptionText
text: popup.description
wrapMode: Text.Wrap
width: parent.width
font.pixelSize: 15
}
StackView {
id: stack
initialItem: profileOverview
anchors.fill: parent
Separator {
id: sep1
anchors.left: parent.left
anchors.right: parent.right
anchors.top: descriptionText.bottom
anchors.topMargin: Style.current.smallPadding
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
pushEnter: Transition { enabled: false }
pushExit: Transition { enabled: false }
popEnter: Transition { enabled: false }
popExit: Transition { enabled: false }
TextWithLabel {
id: shareCommunity
anchors.top: sep1.bottom
anchors.topMargin: Style.current.bigPadding
//% "Share community"
label: qsTrId("share-community")
text: "https://join.status.im/u/TODO"
textToCopy: text
}
Component {
id: membersList
CommunityProfilePopupMembersList {
headerTitle: qsTr("Members")
headerDescription: popup.nbMembers.toString()
}
}
Separator {
id: sep2
anchors.left: parent.left
anchors.right: parent.right
anchors.top: shareCommunity.bottom
anchors.topMargin: Style.current.smallPadding
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
Column {
anchors.top: sep2.bottom
anchors.topMargin: Style.current.halfPadding
width: parent.width
Loader {
active: isAdmin
width: parent.width
sourceComponent: Component {
CommunityPopupButton {
id: memberBtn
label: qsTr("Members")
iconName: "members"
txtColor: Style.current.textColor
onClicked: openPopup(communityMembersPopup)
Component {
id: communityMembersPopup
CommunityMembersPopup {}
}
Item {
property int nbRequests: chatsModel.communities.activeCommunity.communityMembershipRequests.nbRequests
id: memberBlock
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
width: childrenRect.width
height: memberBtn.height
StyledText {
id: nbMemberText
text: nbMembers.toString()
anchors.verticalCenter: parent.verticalCenter
padding: 0
font.pixelSize: 15
color: Style.current.secondaryText
}
Rectangle {
id: badge
visible: memberBlock.nbRequests > 0
anchors.left: nbMemberText.right
anchors.leftMargin: visible ? Style.current.halfPadding : 0
anchors.verticalCenter: parent.verticalCenter
color: Style.current.blue
width: visible ? 22 : 0
height: 22
radius: width / 2
Text {
font.pixelSize: 12
color: Style.current.white
anchors.centerIn: parent
text: memberBlock.nbRequests
}
}
SVGImage {
id: caret
anchors.left: badge.right
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
source: "../../../img/caret.svg"
width: 13
height: 7
rotation: -90
ColorOverlay {
anchors.fill: parent
source: parent
color: Style.current.secondaryText
}
}
Component {
id: profileOverview
CommunityProfilePopupOverview {
property bool useLetterIdenticon: !!!popup.source
headerTitle: popup.name
headerDescription: {
switch(access) {
//% "Public community"
case Constants.communityChatPublicAccess: return qsTrId("public-community");
//% "Invitation only community"
case Constants.communityChatInvitationOnlyAccess: return qsTrId("invitation-only-community");
//% "On request community"
case Constants.communityChatOnRequestAccess: return qsTrId("on-request-community");
//% "Unknown community"
default: return qsTrId("unknown-community");
}
}
headerImageSource: popup.source
description: popup.description
}
}
}
// TODO add this back when roles exist
// Loader {
// active: isAdmin
// width: parent.width
// sourceComponent: CommunityPopupButton {
// label: qsTr("Roles")
// iconName: "roles"
// width: parent.width
// onClicked: console.log("TODO:")
// txtColor: Style.current.textColor
// SVGImage {
// anchors.verticalCenter: parent.verticalCenter
// anchors.right: parent.right
// anchors.rightMargin: Style.current.padding
// source: "../../../img/caret.svg"
// width: 13
// height: 7
// rotation: -90
// ColorOverlay {
// anchors.fill: parent
// source: parent
// color: Style.current.secondaryText
// }
// }
// }
// }
CommunityPopupButton {
id: notificationsBtn
//% "Notifications"
label: qsTrId("notifications")
iconName: "notifications"
width: parent.width
txtColor: Style.current.textColor
onClicked: function(){
notificationSwitch.checked = !notificationSwitch.checked
}
StatusSwitch {
id: notificationSwitch
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
onCheckedChanged: function(value) {
// TODO: enable/disable notifications
console.log("TODO: toggle")
}
}
}
Item {
id: spacer1
width: parent.width
height: Style.current.halfPadding
}
Separator {
footer: Item {
id: modalFooter
visible: stack.depth > 1
width: parent.width
height: modalFooter.visible ? btnBack.height : 0
StatusRoundButton {
id: btnBack
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
anchors.leftMargin: -Style.current.padding
}
Item {
id: spacer2
width: parent.width
height: Style.current.halfPadding
}
Loader {
active: isAdmin
width: parent.width
sourceComponent: CommunityPopupButton {
//% "Edit community"
label: qsTrId("edit-community")
iconName: "edit"
onClicked: openPopup(editCommunityPopup)
Component {
id: editCommunityPopup
CreateCommunityPopup {
isEdit: true
}
}
}
}
Loader {
active: isAdmin
width: parent.width
sourceComponent: CommunityPopupButton {
label: qsTr("Transfer ownership")
iconName: "../transfer"
onClicked: {
const exportResult = chatsModel.communities.exportComumnity()
openPopup(transferOwnershipPopup, {privateKey: exportResult})
}
Component {
id: transferOwnershipPopup
TransferOwnershipPopup {}
}
}
}
CommunityPopupButton {
//% "Leave community"
label: qsTrId("leave-community")
iconName: "leave"
}
Loader {
active: isAdmin
width: parent.width
sourceComponent: CommunityPopupButton {
id: deleteBtn
//% "Delete"
label: qsTrId("delete")
iconName: "delete"
txtColor: Style.current.danger
type: "warn"
icon.name: "arrow-right"
icon.width: 20
icon.height: 16
rotation: 180
onClicked: {
stack.pop()
}
}
}

View File

@ -0,0 +1,165 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
import "./"
import "../components"
Item {
property string headerTitle: ""
property string headerDescription: ""
property string headerImageSource: ""
width: parent.width
height: childrenRect.height
CommunityPopupButton {
id: inviteBtn
//% "Invite People"
label: qsTrId("invite-people")
width: parent.width
iconName: "invite"
onClicked: openPopup(inviteFriendsPopup)
Component {
id: inviteFriendsPopup
InviteFriendsToCommunityPopup {
onClosed: {
destroy()
}
}
}
}
Separator {
id: sep
anchors.left: parent.left
anchors.right: parent.right
anchors.top: inviteBtn.bottom
anchors.topMargin: Style.current.smallPadding
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
MembershipRequestsButton {
id: membershipRequestsBtn
anchors.top: sep.bottom
anchors.topMargin: visible ? Style.current.smallPadding : 0
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
Separator {
id: sep2
visible: membershipRequestsBtn.visible
anchors.left: parent.left
anchors.right: parent.right
anchors.top: membershipRequestsBtn.bottom
anchors.topMargin: Style.current.smallPadding
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
ListView {
id: memberList
anchors.top: sep2.visible ? sep2.bottom : sep.bottom
anchors.topMargin: Style.current.smallPadding
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottomMargin: Style.current.halfPadding
spacing: 4
model: community.members
delegate: Item {
id: contactRow
width: parent.width
height: identicon.height
property string nickname: appMain.getUserNickname(model.pubKey)
StatusImageIdenticon {
id: identicon
anchors.left: parent.left
source: model.identicon
}
StyledText {
text: !model.userName.endsWith(".eth") && !!contactRow.nickname ?
contactRow.nickname : Utils.removeStatusEns(model.userName)
anchors.left: identicon.right
anchors.leftMargin: Style.current.smallPadding
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 13
}
StyledText {
id: moreActionsBtn
text: "..."
font.letterSpacing: 0.5
font.bold: true
lineHeight: 1.4
font.pixelSize: 25
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
anchors.verticalCenter: parent.verticalCenter
MouseArea {
anchors.fill: parent
onClicked: contextMenu.popup(-contextMenu.width / 2 + moreActionsBtn.width / 2, moreActionsBtn.height)
cursorShape: Qt.PointingHandCursor
PopupMenu {
id: contextMenu
Action {
icon.source: "../../../img/communities/menu/view-profile.svg"
icon.width: 16
icon.height: 16
//% "View Profile"
text: qsTrId("view-profile")
onTriggered: openProfilePopup(model.userName, model.pubKey, model.identicon, '', contactRow.nickname)
}
Action {
icon.source: "../../../img/communities/menu/roles.svg"
icon.width: 16
icon.height: 16
//% "Roles"
text: qsTrId("roles")
onTriggered: console.log("TODO")
}
Separator {}
Action {
icon.source: "../../../img/communities/menu/kick.svg"
icon.width: 16
icon.height: 16
icon.color: Style.current.red
//% "Kick"
text: qsTrId("kick")
onTriggered: chatsModel.removeUserFromCommunity(model.pubKey)
}
Action {
icon.source: "../../../img/communities/menu/ban.svg"
icon.width: 16
icon.height: 16
icon.color: Style.current.red
//% "Ban"
text: qsTrId("ban")
onTriggered: console.log("TODO")
}
Separator {}
Action {
icon.source: "../../../img/communities/menu/transfer-ownership.svg"
icon.width: 16
icon.height: 16
icon.color: Style.current.red
//% "Transfer ownership"
text: qsTrId("transfer-ownership")
onTriggered: console.log("TODO")
}
}
}
}
}
}
}

View File

@ -0,0 +1,262 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
import "../ContactsColumn"
Item {
id: root
property string headerTitle: ""
property string headerDescription: ""
property string headerImageSource: ""
property string description: ""
width: parent.width
height: childrenRect.height
StyledText {
id: descriptionText
text: root.description
wrapMode: Text.Wrap
width: parent.width
font.pixelSize: 15
}
Separator {
id: sep1
anchors.left: parent.left
anchors.right: parent.right
anchors.top: descriptionText.bottom
anchors.topMargin: Style.current.smallPadding
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
TextWithLabel {
id: shareCommunity
anchors.top: sep1.bottom
anchors.topMargin: Style.current.bigPadding
//% "Share community"
label: qsTrId("share-community")
text: "https://join.status.im/u/TODO"
textToCopy: text
}
Separator {
id: sep2
anchors.left: parent.left
anchors.right: parent.right
anchors.top: shareCommunity.bottom
anchors.topMargin: Style.current.smallPadding
anchors.leftMargin: -Style.current.padding
anchors.rightMargin: -Style.current.padding
}
Column {
anchors.top: sep2.bottom
anchors.topMargin: Style.current.halfPadding
width: parent.width
Loader {
active: isAdmin
width: parent.width
sourceComponent: Component {
CommunityPopupButton {
id: memberBtn
label: qsTr("Members")
iconName: "members"
txtColor: Style.current.textColor
//onClicked: openPopup(communityMembersPopup)
onClicked: stack.push(membersList)
Component {
id: communityMembersPopup
CommunityMembersPopup {}
}
Item {
property int nbRequests: chatsModel.communities.activeCommunity.communityMembershipRequests.nbRequests
id: memberBlock
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
width: childrenRect.width
height: memberBtn.height
StyledText {
id: nbMemberText
text: nbMembers.toString()
anchors.verticalCenter: parent.verticalCenter
padding: 0
font.pixelSize: 15
color: Style.current.secondaryText
}
Rectangle {
id: badge
visible: memberBlock.nbRequests > 0
anchors.left: nbMemberText.right
anchors.leftMargin: visible ? Style.current.halfPadding : 0
anchors.verticalCenter: parent.verticalCenter
color: Style.current.blue
width: visible ? 22 : 0
height: 22
radius: width / 2
Text {
font.pixelSize: 12
color: Style.current.white
anchors.centerIn: parent
text: memberBlock.nbRequests
}
}
SVGImage {
id: caret
anchors.left: badge.right
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
source: "../../../img/caret.svg"
width: 13
height: 7
rotation: -90
ColorOverlay {
anchors.fill: parent
source: parent
color: Style.current.secondaryText
}
}
}
}
}
}
// TODO add this back when roles exist
// Loader {
// active: isAdmin
// width: parent.width
// sourceComponent: CommunityPopupButton {
// label: qsTr("Roles")
// iconName: "roles"
// width: parent.width
// onClicked: console.log("TODO:")
// txtColor: Style.current.textColor
// SVGImage {
// anchors.verticalCenter: parent.verticalCenter
// anchors.right: parent.right
// anchors.rightMargin: Style.current.padding
// source: "../../../img/caret.svg"
// width: 13
// height: 7
// rotation: -90
// ColorOverlay {
// anchors.fill: parent
// source: parent
// color: Style.current.secondaryText
// }
// }
// }
// }
CommunityPopupButton {
id: notificationsBtn
//% "Notifications"
label: qsTrId("notifications")
iconName: "notifications"
width: parent.width
txtColor: Style.current.textColor
onClicked: function(){
notificationSwitch.checked = !notificationSwitch.checked
}
StatusSwitch {
id: notificationSwitch
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
onCheckedChanged: function(value) {
// TODO: enable/disable notifications
console.log("TODO: toggle")
}
}
}
Item {
id: spacer1
width: parent.width
height: Style.current.halfPadding
}
Separator {
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: -Style.current.padding
anchors.leftMargin: -Style.current.padding
}
Item {
id: spacer2
width: parent.width
height: Style.current.halfPadding
}
Loader {
active: isAdmin
width: parent.width
sourceComponent: CommunityPopupButton {
//% "Edit community"
label: qsTrId("edit-community")
iconName: "edit"
onClicked: openPopup(editCommunityPopup)
Component {
id: editCommunityPopup
CreateCommunityPopup {
isEdit: true
}
}
}
}
Loader {
active: isAdmin
width: parent.width
sourceComponent: CommunityPopupButton {
label: qsTr("Transfer ownership")
iconName: "../transfer"
onClicked: {
const exportResult = chatsModel.communities.exportComumnity()
openPopup(transferOwnershipPopup, {privateKey: exportResult})
}
Component {
id: transferOwnershipPopup
TransferOwnershipPopup {}
}
}
}
CommunityPopupButton {
//% "Leave community"
label: qsTrId("leave-community")
iconName: "leave"
}
Loader {
active: isAdmin
width: parent.width
sourceComponent: CommunityPopupButton {
id: deleteBtn
//% "Delete"
label: qsTrId("delete")
iconName: "delete"
txtColor: Style.current.danger
type: "warn"
}
}
}
}