status-desktop/ui/imports/shared/views/ProfileDialogView.qml

571 lines
22 KiB
QML

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtGraphicalEffects 1.15
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Popups 0.1
import StatusQ.Popups.Dialog 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import shared.controls 1.0
import shared.controls.chat 1.0
import shared.controls.chat.menuItems 1.0
import shared.panels 1.0
import shared.popups 1.0
import shared.stores 1.0 as SharedStores
import shared.views.profile 1.0
import utils 1.0
import SortFilterProxyModel 0.2
import AppLayouts.Profile.helpers 1.0
import AppLayouts.Profile.stores 1.0 as ProfileStores
import AppLayouts.Wallet.stores 1.0 as WalletStores
Pane {
id: root
property bool readOnly // inside settings/profile/preview
property string publicKey: contactsStore.myPublicKey
readonly property alias isCurrentUser: d.isCurrentUser
property ProfileStores.ProfileStore profileStore
property ProfileStores.ContactsStore contactsStore
property SharedStores.UtilsStore utilsStore
property alias sendToAccountEnabled: showcaseView.sendToAccountEnabled
property var dirtyValues: ({})
property bool dirty: false
property var showcaseCommunitiesModel
property var showcaseAccountsModel
property var showcaseCollectiblesModel
property var showcaseSocialLinksModel
property var showcaseAssetsModel
property alias showcaseMaxVisibility: showcaseView.maxVisibility
property alias assetsModel: showcaseView.globalAssetsModel
property alias collectiblesModel: showcaseView.globalCollectiblesModel
signal closeRequested()
padding: 0
topPadding: 32
background: StatusDialogBackground {
id: background
}
ContactDetails {
id: contactDetails
publicKey: root.publicKey
contactsStore: root.contactsStore
profileStore: root.profileStore
}
QtObject {
id: d
readonly property bool isCurrentUser: root.profileStore.pubkey === root.publicKey
readonly property string userDisplayName: contactDetails.displayName
readonly property string userNickName: contactDetails.localNickname
readonly property string prettyEnsName: contactDetails.name
readonly property string aliasName: contactDetails.alias
readonly property string mainDisplayName: ProfileUtils.displayName(userNickName, prettyEnsName, userDisplayName, aliasName)
readonly property string optionalDisplayName: ProfileUtils.displayName("", prettyEnsName, userDisplayName, aliasName)
readonly property bool isContact: contactDetails.isContact
readonly property bool isBlocked: contactDetails.isBlocked
readonly property int contactRequestState: contactDetails.contactRequestState
readonly property bool isLocallyTrusted: contactDetails.trustStatus === Constants.trustStatus.trusted
readonly property string linkToProfile: root.contactsStore.getLinkToProfile(root.publicKey)
readonly property var emojiHash: root.utilsStore.getEmojiHash(root.publicKey)
}
Component {
id: btnEditProfileComponent
StatusButton {
objectName: "editProfileButton"
size: StatusButton.Size.Small
text: qsTr("Edit Profile")
interactive: !root.readOnly
tooltip.text: interactive ? "" : qsTr("Not available in preview mode")
onClicked: {
Global.changeAppSectionBySectionType(Constants.appSection.profile)
root.closeRequested()
}
}
}
Component {
id: btnSendMessageComponent
StatusButton {
size: StatusButton.Size.Small
text: qsTr("Send Message")
objectName: "sendMessageButton"
onClicked: {
root.contactsStore.joinPrivateChat(root.publicKey)
root.closeRequested()
}
}
}
Component {
id: btnAcceptContactRequestComponent
StatusButton {
objectName: "profileDialog_reviewContactRequestButton"
size: StatusButton.Size.Small
text: qsTr("Review contact request")
onClicked: Global.openReviewContactRequestPopup(root.publicKey, contactDetails, null)
}
}
Component {
id: btnSendContactRequestComponent
StatusButton {
objectName: "profileDialog_sendContactRequestButton"
size: StatusButton.Size.Small
text: qsTr("Send contact request")
onClicked: Global.openContactRequestPopup(root.publicKey, contactDetails, null)
}
}
Component {
id: btnBlockUserComponent
StatusButton {
size: StatusButton.Size.Small
type: StatusBaseButton.Type.Danger
objectName: "blockUserButton"
text: qsTr("Block user")
onClicked: Global.blockContactRequested(root.publicKey, contactDetails)
}
}
Component {
id: btnUnblockUserComponent
StatusButton {
size: StatusButton.Size.Small
objectName: "unblockUserProfileButton"
text: qsTr("Unblock user")
onClicked: Global.unblockContactRequested(root.publicKey, contactDetails)
}
}
Component {
id: txtPendingContactRequestComponent
RowLayout {
StatusIcon {
icon: "history"
width: 16
height: width
color: Theme.palette.baseColor1
}
StatusBaseText {
font.pixelSize: 13
font.weight: Font.Medium
color: Theme.palette.baseColor1
verticalAlignment: Text.AlignVCenter
text: qsTr("Contact Request Pending")
}
}
}
Component {
id: btnShareProfile
StatusFlatButton {
objectName: "shareProfileButton"
size: StatusButton.Size.Small
text: qsTr("Share Profile")
onClicked: Global.openPopup(shareProfileCmp)
}
}
Component {
id: shareProfileCmp
ShareProfileDialog {
destroyOnClose: true
title: d.isCurrentUser ? qsTr("Share your profile") : qsTr("%1's profile").arg(StatusQUtils.Emoji.parse(d.mainDisplayName))
publicKey: root.publicKey
emojiHash: d.emojiHash
linkToProfile: d.linkToProfile
qrCode: root.profileStore.getQrCodeSource(linkToProfile)
displayName: userImage.name
largeImage: userImage.image
}
}
ColumnLayout {
id: column
spacing: 20
anchors {
fill: parent
leftMargin: Theme.bigPadding
rightMargin: Theme.bigPadding
}
RowLayout {
Layout.fillWidth: true
spacing: Theme.halfPadding
UserImage {
id: userImage
Layout.alignment: Qt.AlignTop
objectName: "ProfileDialog_userImage"
name: root.dirty ? root.dirtyValues.displayName
: d.mainDisplayName
pubkey: root.publicKey
image: root.dirty ? root.dirtyValues.profileLargeImage
: Utils.addTimestampToURL(contactDetails.largeImage)
interactive: false
imageWidth: 90
imageHeight: imageWidth
ensVerified: contactDetails.ensVerified
Binding on onlineStatus {
value: contactDetails.onlineStatus
when: !d.isCurrentUser
}
}
Item { Layout.fillWidth: true }
// secondary action button
Loader {
Layout.alignment: Qt.AlignTop
Layout.preferredHeight: menuButton.visible ? menuButton.height : -1
active: d.isCurrentUser && !root.readOnly
sourceComponent: btnShareProfile
}
// primary action button
Loader {
Layout.alignment: Qt.AlignTop
Layout.preferredHeight: menuButton.visible ? menuButton.height : -1
sourceComponent: {
// current user
if (d.isCurrentUser)
return btnEditProfileComponent
// blocked user
if (d.isBlocked)
return btnUnblockUserComponent
// accept incoming CR
if (d.contactRequestState === Constants.ContactRequestState.Received)
return btnAcceptContactRequestComponent
// mutual contact
if (d.isContact || d.contactRequestState === Constants.ContactRequestState.Mutual)
return btnSendMessageComponent
// depend on contactRequestState
switch (d.contactRequestState) {
case Constants.ContactRequestState.Sent:
return txtPendingContactRequestComponent
case Constants.ContactRequestState.Received:
break // handled above
case Constants.ContactRequestState.None:
case Constants.ContactRequestState.Dismissed:
return btnSendContactRequestComponent
default:
console.warn("!!! UNHANDLED CONTACT ACTION BUTTON; PUBKEY", root.publicKey)
return null
}
}
}
StatusFlatButton {
id: menuButton
Layout.alignment: Qt.AlignTop
Layout.preferredWidth: height
visible: !d.isCurrentUser
horizontalPadding: 6
verticalPadding: 6
icon.name: "more"
icon.color: Theme.palette.directColor1
highlighted: moreMenu.opened
onClicked: moreMenu.popup(-moreMenu.width + width, height + 4)
StatusMenu {
id: moreMenu
SendContactRequestMenuItem {
enabled: !d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent &&
contactDetails.trustStatus === Constants.trustStatus.untrustworthy // we have an action button otherwise
onTriggered: {
Global.openContactRequestPopup(root.publicKey, contactDetails, null)
}
}
StatusAction {
text: qsTr("Mark as trusted")
icon.name: "checkmark-circle"
enabled: d.isContact && !d.isBlocked && !d.isLocallyTrusted
onTriggered: Global.openMarkAsIDVerifiedPopup(root.publicKey, contactDetails, null)
}
StatusAction {
objectName: "addEditNickNameStatusAction"
text: d.userNickName ? qsTr("Edit nickname") : qsTr("Add nickname")
icon.name: "edit_pencil"
onTriggered: {
Global.openNicknamePopupRequested(root.publicKey, contactDetails, null)
}
}
StatusAction {
text: qsTr("Show QR code")
icon.name: "qr"
enabled: !d.isCurrentUser
onTriggered: {
Global.openPopup(shareProfileCmp)
}
}
StatusAction {
text: qsTr("Copy link to profile")
icon.name: "copy"
onTriggered: {
ClipboardUtils.setText(d.linkToProfile)
}
}
StatusMenuSeparator {}
StatusAction {
text: qsTr("Remove trusted mark")
icon.name: "delete"
type: StatusAction.Type.Danger
enabled: d.isContact && d.isLocallyTrusted
onTriggered: Global.openRemoveIDVerificationDialog(root.publicKey, contactDetails, null)
}
StatusAction {
text: qsTr("Remove nickname")
objectName: "removeNicknameStatusAction"
icon.name: "delete"
type: StatusAction.Type.Danger
enabled: !d.isCurrentUser && !!contactDetails.localNickname
onTriggered: root.contactsStore.changeContactNickname(root.publicKey, "", d.optionalDisplayName, true)
}
StatusAction {
text: qsTr("Mark as untrusted")
icon.name: "warning"
type: StatusAction.Type.Danger
enabled: contactDetails.trustStatus !== Constants.trustStatus.untrustworthy && !d.isBlocked
onTriggered: {
Global.markAsUntrustedRequested(root.publicKey, contactDetails)
}
}
StatusAction {
text: qsTr("Remove untrusted mark")
icon.name: "warning"
type: StatusAction.Type.Danger
enabled: contactDetails.trustStatus === Constants.trustStatus.untrustworthy && !d.isBlocked
onTriggered: {
root.contactsStore.removeTrustStatus(root.publicKey)
}
}
StatusAction {
text: qsTr("Remove contact")
icon.name: "remove-contact"
type: StatusAction.Type.Danger
enabled: d.isContact && !d.isBlocked && d.contactRequestState !== Constants.ContactRequestState.Sent
onTriggered: {
Global.removeContactRequested(root.publicKey, contactDetails)
}
}
StatusAction {
text: qsTr("Block user")
objectName: "blockUserStatusAction"
icon.name: "cancel"
type: StatusAction.Type.Danger
enabled: !d.isBlocked
onTriggered: {
Global.blockContactRequested(root.publicKey, contactDetails)
}
}
}
}
}
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Item {
id: contactRow
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
StatusBaseText {
id: contactName
anchors.left: parent.left
width: Math.min(implicitWidth, contactRow.width - verificationIcons.width - verificationIcons.anchors.leftMargin)
objectName: "ProfileDialog_displayName"
font.bold: true
font.pixelSize: 22
elide: Text.ElideRight
text: StatusQUtils.Emoji.parse(root.dirty ? root.dirtyValues.displayName : d.mainDisplayName, StatusQUtils.Emoji.size.middle)
}
StatusContactVerificationIcons {
id: verificationIcons
anchors.left: contactName.right
anchors.leftMargin: Theme.halfPadding
anchors.verticalCenter: contactName.verticalCenter
objectName: "ProfileDialog_userVerificationIcons"
visible: !d.isCurrentUser
isContact: d.isContact
trustIndicator: contactDetails.trustStatus
isBlocked: d.isBlocked
tiny: false
}
}
RowLayout {
spacing: Theme.halfPadding
StatusBaseText {
id: contactSecondaryName
color: Theme.palette.baseColor1
text: StatusQUtils.Emoji.parse(d.optionalDisplayName)
visible: !!d.userNickName
}
Rectangle {
Layout.preferredWidth: 4
Layout.preferredHeight: 4
radius: width/2
color: Theme.palette.baseColor1
visible: contactSecondaryName.visible
}
StatusBaseText {
color: Theme.palette.baseColor1
text: Utils.getElidedCompressedPk(root.publicKey)
HoverHandler {
id: keyHoverHandler
}
StatusToolTip {
text: Utils.getCompressedPk(root.publicKey)
visible: keyHoverHandler.hovered
}
}
CopyButton {
Layout.leftMargin: -4
Layout.preferredWidth: 16
Layout.preferredHeight: 16
textToCopy: Utils.getCompressedPk(root.publicKey)
StatusToolTip {
text: qsTr("Copy Chat Key")
visible: parent.hovered
}
}
}
StatusScrollView {
id: bioScrollView
Layout.fillWidth: true
Layout.preferredHeight: implicitHeight
Layout.maximumHeight: 120
contentWidth: availableWidth
Layout.topMargin: Theme.halfPadding
padding: 0
rightPadding: Theme.padding
visible: !!bioText.text
StatusBaseText {
id: bioText
width: bioScrollView.availableWidth
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
text: root.dirty ? root.dirtyValues.bio.trim() : contactDetails.bio.trim()
}
}
EmojiHash {
Layout.topMargin: Theme.halfPadding
objectName: "ProfileDialog_userEmojiHash"
emojiHash: d.emojiHash
oneRow: true
}
}
StatusScrollView {
id: scrollView
Layout.fillWidth: true
Layout.fillHeight: true
Layout.leftMargin: -column.anchors.leftMargin
Layout.rightMargin: -column.anchors.rightMargin
padding: 0
contentWidth: availableWidth
ColumnLayout {
width: scrollView.availableWidth
spacing: 20
StatusTabBar {
id: showcaseTabBar
Layout.fillWidth: true
Layout.leftMargin: column.anchors.leftMargin
Layout.rightMargin: column.anchors.rightMargin
bottomPadding: -4
StatusTabButton {
leftPadding: 0
width: implicitWidth
text: qsTr("Communities")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Accounts")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Collectibles")
}
// StatusTabButton {
// width: implicitWidth
// text: qsTr("Assets")
// }
StatusTabButton {
width: implicitWidth
text: qsTr("Web")
}
}
// Profile Showcase
ProfileShowcaseView {
id: showcaseView
Layout.fillWidth: true
Layout.topMargin: -column.spacing
Layout.preferredHeight: 300
currentTabIndex: showcaseTabBar.currentIndex
mainDisplayName: root.dirty ? root.dirtyValues.displayName
: d.mainDisplayName
readOnly: root.readOnly
communitiesModel: root.showcaseCommunitiesModel
accountsModel: root.showcaseAccountsModel
collectiblesModel: root.showcaseCollectiblesModel
socialLinksModel: root.showcaseSocialLinksModel
// assetsModel: root.showcaseAssetsModel
walletStore: WalletStores.RootStore
onCloseRequested: root.closeRequested()
onCopyToClipboard: ClipboardUtils.setText(text)
}
}
}
}
layer.enabled: !root.readOnly // profile preview has its own layer.effect
layer.effect: OpacityMask {
maskSource: Rectangle {
anchors.centerIn: parent
width: column.width
height: column.height
radius: background.radius
}
}
}