feat(ProfileShowcase): Align UI save flow according to backend response
- Added loading state in dirty toast message. - Added store connections in `MyProfileView` and save loading logic. - Added toasts notifications. Closes #13950 a
This commit is contained in:
parent
1d15398ea7
commit
d35c0bd3d1
|
@ -45,14 +45,38 @@ QtObject {
|
|||
|
||||
readonly property bool isFirstShowcaseInteraction: localAccountSettings.isFirstShowcaseInteraction
|
||||
|
||||
onUserDeclinedBackupBannerChanged: {
|
||||
if (userDeclinedBackupBanner !== localAccountSensitiveSettings.userDeclinedBackupBanner) {
|
||||
localAccountSensitiveSettings.userDeclinedBackupBanner = userDeclinedBackupBanner
|
||||
property var details: Utils.getContactDetailsAsJson(pubkey)
|
||||
|
||||
// The following signals wrap the settings / preferences save request responses in one unique result (identity + preferences result)
|
||||
signal profileSettingsSaveSucceeded()
|
||||
signal profileSettingsSaveFailed()
|
||||
|
||||
// The following signals describe separate save request responses between identity and preferences
|
||||
signal profileIdentitySaveSucceeded()
|
||||
signal profileIdentitySaveFailed()
|
||||
signal profileShowcasePreferencesSaveSucceeded()
|
||||
signal profileShowcasePreferencesSaveFailed()
|
||||
|
||||
readonly property Connections profileModuleConnections: Connections {
|
||||
target: root.profileModule
|
||||
|
||||
function onProfileIdentitySaveSucceeded() {
|
||||
root.profileIdentitySaveSucceeded()
|
||||
}
|
||||
|
||||
function onProfileIdentitySaveFailed() {
|
||||
root.profileIdentitySaveFailed()
|
||||
}
|
||||
|
||||
function onProfileShowcasePreferencesSaveSucceeded() {
|
||||
root.profileShowcasePreferencesSaveSucceeded()
|
||||
}
|
||||
|
||||
function onProfileShowcasePreferencesSaveFailed() {
|
||||
root.profileShowcasePreferencesSaveFailed()
|
||||
}
|
||||
}
|
||||
|
||||
property var details: Utils.getContactDetailsAsJson(pubkey)
|
||||
|
||||
function getQrCodeSource(text) {
|
||||
return globalUtils.qrCode(text)
|
||||
}
|
||||
|
@ -67,12 +91,12 @@ QtObject {
|
|||
"displayName": displayName,
|
||||
"bio": bio,
|
||||
"image": source ? {
|
||||
"source": source,
|
||||
"aX": aX,
|
||||
"aY": aY,
|
||||
"bX": bX,
|
||||
"bY": bY
|
||||
} : null
|
||||
"source": source,
|
||||
"aX": aX,
|
||||
"aY": aY,
|
||||
"bX": bX,
|
||||
"bY": bY
|
||||
} : null
|
||||
}
|
||||
let json = JSON.stringify(identityInfo)
|
||||
root.profileModule.saveProfileIdentity(json)
|
||||
|
@ -102,28 +126,6 @@ QtObject {
|
|||
root.profileModule.setIsFirstShowcaseInteraction()
|
||||
}
|
||||
|
||||
signal profileIdentitySaveSucceeded()
|
||||
signal profileIdentitySaveFailed()
|
||||
signal profileShowcasePreferencesSaveSucceeded()
|
||||
signal profileShowcasePreferencesSaveFailed()
|
||||
|
||||
readonly property Connections profileModuleConnections: Connections {
|
||||
target: root.profileModule
|
||||
|
||||
function onProfileIdentitySaveSucceeded() {
|
||||
root.profileIdentitySaveSucceeded()
|
||||
}
|
||||
function onProfileIdentitySaveFailed() {
|
||||
root.profileIdentitySaveFailed()
|
||||
}
|
||||
function onProfileShowcasePreferencesSaveSucceeded() {
|
||||
root.profileShowcasePreferencesSaveSucceeded()
|
||||
}
|
||||
function onProfileShowcasePreferencesSaveFailed() {
|
||||
root.profileShowcasePreferencesSaveFailed()
|
||||
}
|
||||
}
|
||||
|
||||
// Social links related: All to be removed: Deprecated --> Issue #13688
|
||||
function containsSocialLink(text, url) {
|
||||
return root.profileModule.containsSocialLink(text, url)
|
||||
|
@ -153,4 +155,10 @@ QtObject {
|
|||
root.profileModule.saveSocialLinks(silent)
|
||||
}
|
||||
// End of social links to be removed
|
||||
|
||||
onUserDeclinedBackupBannerChanged: {
|
||||
if (userDeclinedBackupBanner !== localAccountSensitiveSettings.userDeclinedBackupBanner) {
|
||||
localAccountSensitiveSettings.userDeclinedBackupBanner = userDeclinedBackupBanner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,7 +71,8 @@ SettingsContentBase {
|
|||
|
||||
toast.saveChangesTooltipText: saveChangesButtonEnabled ? "" : qsTr("Invalid changes made to Identity")
|
||||
autoscrollWhenDirty: profileTabBar.currentIndex === MyProfileView.Identity
|
||||
|
||||
toast.loading: priv.expectedBackendResponses > 0
|
||||
|
||||
onResetChangesClicked: priv.reset()
|
||||
|
||||
onSaveChangesClicked: priv.save()
|
||||
|
@ -124,12 +125,12 @@ SettingsContentBase {
|
|||
readonly property var priv: QtObject {
|
||||
id: priv
|
||||
|
||||
property bool hasAnyProfileShowcaseChanges: showcaseModels.dirty
|
||||
property bool isIdentityTabDirty: (!descriptionPanel.isEnsName &&
|
||||
descriptionPanel.displayName.text !== profileStore.displayName) ||
|
||||
descriptionPanel.bio.text !== profileStore.bio ||
|
||||
profileStore.socialLinksDirty ||
|
||||
profileHeader.icon !== profileStore.profileLargeImage
|
||||
readonly property bool hasAnyProfileShowcaseChanges: showcaseModels.dirty
|
||||
readonly property bool isIdentityTabDirty: (!descriptionPanel.isEnsName &&
|
||||
descriptionPanel.displayName.text !== profileStore.displayName) ||
|
||||
descriptionPanel.bio.text !== profileStore.bio ||
|
||||
profileStore.socialLinksDirty ||
|
||||
profileHeader.icon !== profileStore.profileLargeImage
|
||||
|
||||
property ProfileShowcaseModels showcaseModels: ProfileShowcaseModels {
|
||||
communitiesSourceModel: root.communitiesModel
|
||||
|
@ -147,6 +148,62 @@ SettingsContentBase {
|
|||
socialLinksSourceModel: root.profileStore.showcasePreferencesSocialLinksModel
|
||||
}
|
||||
|
||||
// Used to track which are the expected backend responses (they can be 0, 1 or 2) depending on the dirty changes
|
||||
property int expectedBackendResponses: 0
|
||||
property bool saveRequestFailed: false
|
||||
|
||||
// Maximum save action waiting time controller.
|
||||
// Backend response must be received before, otherwise it will be considered
|
||||
// a failure and UI will be released.
|
||||
property Timer saveLoadingTimeout : Timer {
|
||||
interval: 5000
|
||||
repeat: false
|
||||
running: toast.active && toast.loading
|
||||
|
||||
onTriggered: {
|
||||
// Forcing a failure
|
||||
if(priv.expectedBackendResponses > 0) {
|
||||
root.profileStore.profileSettingsSaveFailed()
|
||||
priv.expectedBackendResponses = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save backend response received:
|
||||
property Connections profileStoreConnection: Connections {
|
||||
target: root.profileStore
|
||||
|
||||
function onProfileIdentitySaveSucceeded() {
|
||||
priv.checkSaveResult(false)
|
||||
}
|
||||
|
||||
function onProfileIdentitySaveFailed() {
|
||||
priv.checkSaveResult(true)
|
||||
}
|
||||
|
||||
function onProfileShowcasePreferencesSaveSucceeded() {
|
||||
priv.checkSaveResult(false)
|
||||
}
|
||||
|
||||
function onProfileShowcasePreferencesSaveFailed() {
|
||||
priv.checkSaveResult(true)
|
||||
}
|
||||
}
|
||||
|
||||
function checkSaveResult(isFailure) {
|
||||
priv.expectedBackendResponses--
|
||||
if(isFailure)
|
||||
priv.saveRequestFailed = isFailure
|
||||
|
||||
if(priv.expectedBackendResponses == 0) {
|
||||
if(priv.saveRequestFailed || isFailure) {
|
||||
root.profileStore.profileSettingsSaveFailed()
|
||||
} else {
|
||||
root.profileStore.profileSettingsSaveSucceeded()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
descriptionPanel.displayName.text = Qt.binding(() => { return profileStore.displayName })
|
||||
descriptionPanel.bio.text = Qt.binding(() => { return profileStore.bio })
|
||||
|
@ -154,25 +211,41 @@ SettingsContentBase {
|
|||
profileHeader.icon = Qt.binding(() => { return profileStore.profileLargeImage })
|
||||
|
||||
priv.showcaseModels.revert()
|
||||
priv.saveRequestFailed = false
|
||||
priv.expectedBackendResponses = 0
|
||||
root.profileStore.requestProfileShowcasePreferences()
|
||||
}
|
||||
|
||||
function save() {
|
||||
// Accounts, Communities, Assets, Collectibles and social links info
|
||||
if (hasAnyProfileShowcaseChanges) {
|
||||
root.profileStore.saveProfileShowcasePreferences(showcaseModels.buildJSONModelsCurrentState())
|
||||
// IMPORTANT: Save implies 2 calls in backend but 1 result in UI so the order in current save method is relevant
|
||||
// First save stage: Review which are the expected responses before calling backend
|
||||
priv.expectedBackendResponses = 0
|
||||
priv.saveRequestFailed = false
|
||||
if(hasAnyProfileShowcaseChanges) {
|
||||
priv.expectedBackendResponses++
|
||||
}
|
||||
if (isIdentityTabDirty ) {
|
||||
priv.expectedBackendResponses++
|
||||
}
|
||||
|
||||
// Identity info
|
||||
if (isIdentityTabDirty) {
|
||||
root.profileStore.saveProfileIdentity(descriptionPanel.displayName.text,
|
||||
descriptionPanel.bio.text.trim(),
|
||||
profileHeader.icon,
|
||||
profileHeader.cropRect.x,
|
||||
profileHeader.cropRect.y,
|
||||
(profileHeader.cropRect.x + profileHeader.cropRect.width),
|
||||
(profileHeader.cropRect.y + profileHeader.cropRect.height))
|
||||
profileHeader.icon = Qt.binding(() => { return profileStore.profileLargeImage })
|
||||
// Second save stage: Ready to call backend
|
||||
if(priv.expectedBackendResponses > 0) {
|
||||
// Accounts, Communities, Assets, Collectibles and social links info
|
||||
if (hasAnyProfileShowcaseChanges) {
|
||||
root.profileStore.saveProfileShowcasePreferences(showcaseModels.buildJSONModelsCurrentState())
|
||||
}
|
||||
|
||||
// Identity info
|
||||
if (isIdentityTabDirty) {
|
||||
root.profileStore.saveProfileIdentity(descriptionPanel.displayName.text,
|
||||
descriptionPanel.bio.text.trim(),
|
||||
profileHeader.icon,
|
||||
profileHeader.cropRect.x,
|
||||
profileHeader.cropRect.y,
|
||||
(profileHeader.cropRect.x + profileHeader.cropRect.width),
|
||||
(profileHeader.cropRect.y + profileHeader.cropRect.height))
|
||||
profileHeader.icon = Qt.binding(() => { return profileStore.profileLargeImage })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,7 +327,7 @@ SettingsContentBase {
|
|||
|
||||
onChangePositionRequested: function (from, to) {
|
||||
priv.showcaseModels.changeAccountPosition(from, to)
|
||||
|
||||
|
||||
}
|
||||
onSetVisibilityRequested: function (key, toVisibility) {
|
||||
priv.showcaseModels.setAccountVisibility(key, toVisibility)
|
||||
|
|
|
@ -89,6 +89,7 @@ Item {
|
|||
rootStore: appMain.rootStore
|
||||
rootChatStore: appMain.rootChatStore
|
||||
communityTokensStore: appMain.communityTokensStore
|
||||
profileStore: appMain.rootStore.profileSectionStore.profileStore
|
||||
|
||||
sendModalPopup: sendModal
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import AppLayouts.Wallet 1.0
|
|||
|
||||
import AppLayouts.stores 1.0
|
||||
import AppLayouts.Chat.stores 1.0 as ChatStores
|
||||
import AppLayouts.Profile.stores 1.0
|
||||
|
||||
import shared.stores 1.0 as SharedStores
|
||||
|
||||
|
@ -31,6 +32,7 @@ QtObject {
|
|||
required property RootStore rootStore
|
||||
required property ChatStores.RootStore rootChatStore
|
||||
required property SharedStores.CommunityTokensStore communityTokensStore
|
||||
required property ProfileStore profileStore
|
||||
|
||||
// Properties:
|
||||
required property var sendModalPopup
|
||||
|
@ -39,6 +41,7 @@ QtObject {
|
|||
readonly property string viewOptimismExplorerText: qsTr("View on Optimism Explorer")
|
||||
readonly property string checkmarkCircleAssetName: "checkmark-circle"
|
||||
readonly property string crownOffAssetName: "crown-off"
|
||||
readonly property string warningAssetName: "warning"
|
||||
|
||||
// Community Transfer Ownership related toasts:
|
||||
readonly property Connections _communityTokensStoreConnections: Connections {
|
||||
|
@ -76,7 +79,7 @@ QtObject {
|
|||
} else if (status === Constants.ContractTransactionStatus.Failed) {
|
||||
Global.displayToastMessage(qsTr("%1 smart contract update failed").arg(communityName),
|
||||
root.viewOptimismExplorerText,
|
||||
"warning",
|
||||
root.warningAssetName,
|
||||
false,
|
||||
Constants.ephemeralNotificationType.danger,
|
||||
url)
|
||||
|
@ -205,6 +208,29 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
// Profile settings related toasts:
|
||||
readonly property Connections _profileStoreConnections: Connections {
|
||||
target: root.profileStore
|
||||
|
||||
function onProfileSettingsSaveSucceeded() {
|
||||
Global.displayToastMessage(qsTr("Profile changes saved"),
|
||||
"",
|
||||
root.checkmarkCircleAssetName,
|
||||
false,
|
||||
Constants.ephemeralNotificationType.success,
|
||||
"")
|
||||
}
|
||||
|
||||
function onProfileSettingsSaveFailed() {
|
||||
Global.displayToastMessage(qsTr("Profile changes could not be saved"),
|
||||
"",
|
||||
root.warningAssetName,
|
||||
false,
|
||||
Constants.ephemeralNotificationType.danger,
|
||||
"")
|
||||
}
|
||||
}
|
||||
|
||||
// It will cover all specific actions (different than open external links) that can be done after clicking toast link text
|
||||
function doAction(actionType, actionData) {
|
||||
switch(actionType) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import StatusQ.Controls 0.1
|
|||
Rectangle {
|
||||
id: root
|
||||
|
||||
property bool loading: false
|
||||
property bool active: false
|
||||
property bool cancelButtonVisible: true
|
||||
property bool saveChangesButtonEnabled: false
|
||||
|
@ -133,7 +134,7 @@ Rectangle {
|
|||
StatusButton {
|
||||
id: cancelChangesButton
|
||||
text: root.defaultCancelChangesText
|
||||
enabled: root.active
|
||||
enabled: !root.loading && root.active
|
||||
visible: root.cancelButtonVisible
|
||||
type: StatusBaseButton.Type.Danger
|
||||
onClicked: root.resetChangesClicked()
|
||||
|
@ -142,6 +143,7 @@ Rectangle {
|
|||
StatusFlatButton {
|
||||
id: saveForLaterButton
|
||||
text: root.defaultSaveForLaterText
|
||||
loading: root.loading
|
||||
enabled: root.active && root.saveChangesButtonEnabled
|
||||
visible: root.saveForLaterButtonVisible
|
||||
onClicked: root.saveForLaterClicked()
|
||||
|
@ -149,7 +151,9 @@ Rectangle {
|
|||
|
||||
StatusButton {
|
||||
id: saveChangesButton
|
||||
|
||||
objectName: "settingsDirtyToastMessageSaveButton"
|
||||
loading: root.loading
|
||||
text: root.defaultSaveChangesText
|
||||
interactive: root.active && root.saveChangesButtonEnabled
|
||||
tooltip.text: root.saveChangesTooltipText
|
||||
|
|
Loading…
Reference in New Issue