feat(profile): Break profile settings page into tabs

- Each section of the current profile settings view is moved into the
corresponding tab
- The Preview tab becomes the Preview button
- Password button becomes a separate page: PR: [Settings]: Added change
password view #13284 (I kept the Change password button for now until
this is merged)

- Also creates the new Web (Social links) tab as a result

Fixes #13330
Fixes #13406
This commit is contained in:
Lukáš Tinkl 2024-02-04 12:50:26 +01:00 committed by Lukáš Tinkl
parent bb391a68ba
commit 3ebd4509bb
5 changed files with 199 additions and 314 deletions

View File

@ -253,6 +253,9 @@ Control {
Layout.fillWidth: true
Layout.minimumHeight: empty ? Math.floor(hiddenTargetDropArea.height + hiddenTargetDropArea.anchors.topMargin)
: d.defaultDelegateHeight * Math.min(count, 4)
implicitHeight: contentHeight
interactive: false
model: root.baseModel
readonly property bool empty: !contentHeight
@ -333,5 +336,7 @@ Control {
}
}
}
Item { Layout.fillHeight: true }
}
}

View File

@ -161,5 +161,7 @@ Control {
}
}
}
Item { Layout.fillHeight: true }
}
}

View File

@ -1,3 +1,4 @@
ProfileDescriptionPanel 1.0 ProfileDescriptionPanel.qml
ProfileSocialLinksPanel 1.0 ProfileSocialLinksPanel.qml
ProfileShowcaseCommunitiesPanel 1.0 ProfileShowcaseCommunitiesPanel.qml
ProfileShowcaseCollectiblesPanel 1.0 ProfileShowcaseCollectiblesPanel.qml

View File

@ -19,6 +19,7 @@ import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import AppLayouts.Profile.panels 1.0
import AppLayouts.Wallet.stores 1.0
SettingsContentBase {
@ -34,86 +35,221 @@ SettingsContentBase {
property var communitiesModel
titleRowComponentLoader.sourceComponent: StatusButton {
objectName: "profileSettingsChangePasswordButton"
text: qsTr("Change Password")
onClicked: Global.openPopup(changePasswordModal)
enabled: !userProfile.isKeycardUser
titleRowComponentLoader.sourceComponent: RowLayout {
StatusButton {
objectName: "profileSettingsChangePasswordButton"
text: qsTr("Change Password")
onClicked: Global.openPopup(changePasswordModal)
enabled: !userProfile.isKeycardUser
}
StatusButton {
text: qsTr("Preview")
onClicked: Global.openPopup(profilePreview)
}
}
dirty: settingsView.dirty
saveChangesButtonEnabled: settingsView.valid
dirty: (!descriptionPanel.isEnsName &&
descriptionPanel.displayName.text !== profileStore.displayName) ||
descriptionPanel.bio.text !== profileStore.bio ||
profileStore.socialLinksDirty ||
profileHeader.icon !== profileStore.profileLargeImage ||
priv.hasAnyProfileShowcaseChanges
saveChangesButtonEnabled: !!descriptionPanel.displayName.text && descriptionPanel.displayName.valid
onResetChangesClicked: {
settingsView.reset()
profilePreview.reload()
}
onSaveChangesClicked: {
settingsView.save()
profilePreview.reload()
}
onResetChangesClicked: priv.reset()
onSaveChangesClicked: priv.save()
bottomHeaderComponents: StatusTabBar {
id: editPreviwTabBar
id: profileTabBar
StatusTabButton {
width: implicitWidth
leftPadding: 0
width: implicitWidth
text: qsTr("Edit")
text: qsTr("Identity")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Preview")
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")
}
}
onVisibleChanged: if (visible) profileStore.requestProfileShowcasePreferences()
Component.onCompleted: profileStore.requestProfileShowcasePreferences()
readonly property var priv: QtObject {
id: priv
property bool hasAnyProfileShowcaseChanges: false
function reset() {
descriptionPanel.displayName.text = Qt.binding(() => { return profileStore.displayName })
descriptionPanel.bio.text = Qt.binding(() => { return profileStore.bio })
profileStore.resetSocialLinks()
profileHeader.icon = Qt.binding(() => { return profileStore.profileLargeImage })
profileShowcaseCommunitiesPanel.reset()
profileShowcaseAccountsPanel.reset()
profileShowcaseCollectiblesPanel.reset()
profileShowcaseAssetsPanel.reset()
root.profileStore.requestProfileShowcasePreferences()
hasAnyProfileShowcaseChanges = false
}
function save() {
if (hasAnyProfileShowcaseChanges)
profileStore.storeProfileShowcasePreferences()
if (!descriptionPanel.isEnsName)
profileStore.setDisplayName(descriptionPanel.displayName.text)
profileStore.setBio(descriptionPanel.bio.text.trim())
profileStore.saveSocialLinks()
if (profileHeader.icon === "") {
root.profileStore.removeImage()
} else {
profileStore.uploadImage(profileHeader.icon,
profileHeader.cropRect.x.toFixed(),
profileHeader.cropRect.y.toFixed(),
(profileHeader.cropRect.x + profileHeader.cropRect.width).toFixed(),
(profileHeader.cropRect.y + profileHeader.cropRect.height).toFixed());
}
reset()
}
}
ColumnLayout {
id: layout
spacing: Constants.settingsSection.itemSpacing
width: root.contentWidth
StackLayout {
id: stackLayout
currentIndex: editPreviwTabBar.currentIndex
currentIndex: profileTabBar.currentIndex
MyProfileSettingsView {
id: settingsView
// identity
ColumnLayout {
objectName: "myProfileSettingsView"
profileStore: root.profileStore
privacyStore: root.privacyStore
walletStore: root.walletStore
communitiesModel: root.communitiesModel
walletAssetsStore: root.walletAssetsStore
currencyStore: root.currencyStore
ProfileHeader {
id: profileHeader
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
onVisibleChanged: if (visible) stackLayout.Layout.preferredHeight = settingsView.implicitHeight
Component.onCompleted: stackLayout.Layout.preferredHeight = Qt.binding(() => settingsView.implicitHeight)
store: root.profileStore
displayName: profileStore.name
pubkey: profileStore.pubkey
icon: profileStore.profileLargeImage
imageSize: ProfileHeader.ImageSize.Big
displayNameVisible: false
pubkeyVisible: false
emojiHashVisible: false
editImageButtonVisible: true
}
ProfileDescriptionPanel {
id: descriptionPanel
readonly property bool isEnsName: profileStore.preferredName
Layout.fillWidth: true
displayName.focus: !isEnsName
displayName.input.edit.readOnly: isEnsName
displayName.text: profileStore.name
displayName.validationMode: StatusInput.ValidationMode.Always
displayName.validators: isEnsName ? [] : Constants.validators.displayName
bio.text: profileStore.bio
}
}
MyProfilePreview {
// communities
ProfileShowcaseCommunitiesPanel {
id: profileShowcaseCommunitiesPanel
baseModel: root.communitiesModel
showcaseModel: root.profileStore.profileShowcaseCommunitiesModel
onShowcaseEntryChanged: priv.hasAnyProfileShowcaseChanges = true
}
// accounts
ProfileShowcaseAccountsPanel {
id: profileShowcaseAccountsPanel
baseModel: root.walletStore.accounts
showcaseModel: root.profileStore.profileShowcaseAccountsModel
currentWallet: root.walletStore.overview.mixedcaseAddress
onShowcaseEntryChanged: priv.hasAnyProfileShowcaseChanges = true
}
// collectibles
ProfileShowcaseCollectiblesPanel {
id: profileShowcaseCollectiblesPanel
baseModel: root.profileStore.collectiblesModel
showcaseModel: root.profileStore.profileShowcaseCollectiblesModel
onShowcaseEntryChanged: priv.hasAnyProfileShowcaseChanges = true
}
// assets
ProfileShowcaseAssetsPanel {
id: profileShowcaseAssetsPanel
baseModel: root.walletAssetsStore.groupedAccountAssetsModel // TODO: instantiate an assets model in profile module
showcaseModel: root.profileStore.profileShowcaseAssetsModel
onShowcaseEntryChanged: priv.hasAnyProfileShowcaseChanges = true
formatCurrencyAmount: function(amount, symbol) {
return root.currencyStore.formatCurrencyAmount(amount, symbol)
}
}
// web
ProfileSocialLinksPanel {
profileStore: root.profileStore
socialLinksModel: root.profileStore.temporarySocialLinksModel
}
Component {
id: changePasswordModal
ChangePasswordModal {
privacyStore: root.privacyStore
onPasswordChanged: Global.openPopup(successPopup)
}
}
Component {
id: successPopup
ChangePasswordSuccessModal {}
}
Component {
id: profilePreview
profileStore: root.profileStore
contactsStore: root.contactsStore
networkConnectionStore: root.networkConnectionStore
communitiesModel: root.communitiesModel
dirtyValues: settingsView.dirtyValues
dirty: settingsView.dirty
onVisibleChanged: if (visible) stackLayout.Layout.preferredHeight = Qt.binding(() => profilePreview.implicitHeight)
ProfileDialog {
publicKey: root.contactsStore.myPublicKey
profileStore: root.profileStore
contactsStore: root.contactsStore
networkConnectionStore: root.networkConnectionStore
communitiesModel: root.communitiesModel
onClosed: destroy()
}
}
}
Component {
id: changePasswordModal
ChangePasswordModal {
privacyStore: root.privacyStore
onPasswordChanged: Global.openPopup(successPopup);
}
}
Component {
id: successPopup
ChangePasswordSuccessModal { }
}
}
}

View File

@ -1,259 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.13
import utils 1.0
import shared 1.0
import shared.stores 1.0
import shared.panels 1.0
import shared.popups 1.0
import shared.controls.chat 1.0
import "../../popups"
import "../../stores"
import "../../controls"
import "../../panels"
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import AppLayouts.Wallet.stores 1.0
ColumnLayout {
id: root
spacing: 20
property PrivacyStore privacyStore
property ProfileStore profileStore
property WalletStore walletStore
required property WalletAssetsStore walletAssetsStore
required property CurrenciesStore currencyStore
property var communitiesModel
property bool hasAnyProfileShowcaseChanges: false
property QtObject dirtyValues: QtObject {
property string displayName: descriptionPanel.displayName.text
property string bio: descriptionPanel.bio.text
property bool biomentricValue: biometricsSwitch.checked
property url profileLargeImage: profileHeader.previewIcon
}
readonly property bool dirty: (!descriptionPanel.isEnsName &&
descriptionPanel.displayName.text !== profileStore.displayName) ||
descriptionPanel.bio.text !== profileStore.bio ||
profileStore.socialLinksDirty ||
biometricsSwitch.checked !== biometricsSwitch.currentStoredValue ||
profileHeader.icon !== profileStore.profileLargeImage ||
hasAnyProfileShowcaseChanges
readonly property bool valid: !!descriptionPanel.displayName.text && descriptionPanel.displayName.valid
function reset() {
descriptionPanel.displayName.text = Qt.binding(() => { return profileStore.displayName })
descriptionPanel.bio.text = Qt.binding(() => { return profileStore.bio })
profileStore.resetSocialLinks()
biometricsSwitch.checked = Qt.binding(() => { return biometricsSwitch.currentStoredValue })
profileHeader.icon = Qt.binding(() => { return profileStore.profileLargeImage })
profileShowcaseCommunitiesPanel.reset()
profileShowcaseAccountsPanel.reset()
profileShowcaseCollectiblesPanel.reset()
profileShowcaseAssetsPanel.reset()
root.profileStore.requestProfileShowcasePreferences()
hasAnyProfileShowcaseChanges = false
}
function save() {
if (hasAnyProfileShowcaseChanges)
profileStore.storeProfileShowcasePreferences()
if (!descriptionPanel.isEnsName)
profileStore.setDisplayName(descriptionPanel.displayName.text)
profileStore.setBio(descriptionPanel.bio.text.trim())
profileStore.saveSocialLinks()
if (profileHeader.icon === "") {
root.profileStore.removeImage()
} else {
profileStore.uploadImage(profileHeader.icon,
profileHeader.cropRect.x.toFixed(),
profileHeader.cropRect.y.toFixed(),
(profileHeader.cropRect.x + profileHeader.cropRect.width).toFixed(),
(profileHeader.cropRect.y + profileHeader.cropRect.height).toFixed());
}
if (biometricsSwitch.checked && !biometricsSwitch.currentStoredValue)
root.privacyStore.tryStoreToKeyChain()
else if (!biometricsSwitch.checked)
root.privacyStore.tryRemoveFromKeyChain()
reset()
}
onVisibleChanged: if (visible) profileStore.requestProfileShowcasePreferences()
Component.onCompleted: profileStore.requestProfileShowcasePreferences()
readonly property Connections privacyStoreConnections: Connections {
target: Qt.platform.os === Constants.mac ? root.privacyStore.privacyModule : null
function onStoreToKeychainError(errorDescription: string) {
root.reset()
}
function onStoreToKeychainSuccess() {
root.reset()
}
}
ProfileHeader {
id: profileHeader
Layout.fillWidth: true
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
store: root.profileStore
displayName: profileStore.name
pubkey: profileStore.pubkey
icon: profileStore.profileLargeImage
imageSize: ProfileHeader.ImageSize.Big
displayNameVisible: false
pubkeyVisible: false
emojiHashVisible: false
editImageButtonVisible: true
}
ProfileDescriptionPanel {
id: descriptionPanel
readonly property bool isEnsName: profileStore.preferredName
Layout.fillWidth: true
displayName.focus: !isEnsName
displayName.input.edit.readOnly: isEnsName
displayName.text: profileStore.name
displayName.validationMode: StatusInput.ValidationMode.Always
displayName.validators: isEnsName ? [] : Constants.validators.displayName
bio.text: profileStore.bio
}
Separator {
Layout.fillWidth: true
}
ProfileSocialLinksPanel {
Layout.fillWidth: true
profileStore: root.profileStore
socialLinksModel: root.profileStore.temporarySocialLinksModel
}
Separator {
Layout.fillWidth: true
}
StatusBaseText {
visible: Qt.platform.os === Constants.mac
text: qsTr("Security")
color: Theme.palette.baseColor1
}
StatusListItem {
Layout.fillWidth: true
visible: Qt.platform.os === Constants.mac
title: qsTr("Biometric login and transaction authentication")
asset.name: "touch-id"
components: [ StatusSwitch {
id: biometricsSwitch
horizontalPadding: 0
readonly property bool currentStoredValue: localAccountSettings.storeToKeychainValue === Constants.keychain.storedValue.store
checked: currentStoredValue
} ]
onClicked: biometricsSwitch.toggle()
}
Separator {
Layout.fillWidth: true
visible: Qt.platform.os === Constants.mac
}
StatusBaseText {
text: qsTr("Showcase")
color: Theme.palette.baseColor1
}
StatusTabBar {
id: showcaseTabBar
StatusTabButton {
width: implicitWidth
leftPadding: 0
text: qsTr("Communities")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Accounts")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Collectibles")
}
StatusTabButton {
width: implicitWidth
text: qsTr("Assets")
}
}
StackLayout {
id: showcaseStack
Layout.fillWidth: true
currentIndex: showcaseTabBar.currentIndex
ProfileShowcaseCommunitiesPanel {
id: profileShowcaseCommunitiesPanel
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: implicitHeight
baseModel: root.communitiesModel
showcaseModel: root.profileStore.profileShowcaseCommunitiesModel
onShowcaseEntryChanged: hasAnyProfileShowcaseChanges = true
}
ProfileShowcaseAccountsPanel {
id: profileShowcaseAccountsPanel
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: implicitHeight
baseModel: root.walletStore.accounts
showcaseModel: root.profileStore.profileShowcaseAccountsModel
currentWallet: root.walletStore.overview.mixedcaseAddress
onShowcaseEntryChanged: hasAnyProfileShowcaseChanges = true
}
ProfileShowcaseCollectiblesPanel {
id: profileShowcaseCollectiblesPanel
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: implicitHeight
baseModel: root.profileStore.collectiblesModel
showcaseModel: root.profileStore.profileShowcaseCollectiblesModel
onShowcaseEntryChanged: hasAnyProfileShowcaseChanges = true
}
ProfileShowcaseAssetsPanel {
id: profileShowcaseAssetsPanel
Layout.minimumHeight: implicitHeight
Layout.maximumHeight: implicitHeight
baseModel: root.walletAssetsStore.groupedAccountAssetsModel // TODO: instantiate an assets model in profile module
showcaseModel: root.profileStore.profileShowcaseAssetsModel
onShowcaseEntryChanged: hasAnyProfileShowcaseChanges = true
formatCurrencyAmount: function(amount, symbol) {
return root.currencyStore.formatCurrencyAmount(amount, symbol)
}
}
}
}