Compare commits

...

1 Commits

Author SHA1 Message Date
Joshua Goins
d362937474 Improve profile editing UX, especially around unsaved changes
As seen with a real life user (and myself) I will sometimes back out of
editing user information, despite there being a Save button. That's
because we make no attempt to warn or stop the user before doing so. Now
that is fixed, and the window will be prevented from closing or the page
popping.

Additionally, there's now a "Reset Changes" button that works as you'd
expect. I also made it so this new button and the existing "Save" button
are only enabled when there's unsaved changes.

I also did some work on the UX flow to get to this account editing page.
There used to be a dedicated button in the account menu, but that's
duplicative with the new "Open Profile" option. That's removed, but I
also replaced the useless "Message" and "Ignore" (what? ignoring
yourself?) buttons with a button to edit the account.

Related to removing UI cruft, the "Show QR code" button on this page is
removed. There's an easier way to access that through your own profile.
2026-02-22 08:47:17 -05:00
3 changed files with 101 additions and 34 deletions

View File

@@ -40,12 +40,6 @@ KirigamiComponents.ConvergentContextMenu {
})
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Edit This Account")
icon.name: "document-edit"
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Notification Settings")
icon.name: "notifications"

View File

@@ -149,15 +149,24 @@ Kirigami.Dialog {
Kirigami.Action {
text: i18nc("@action:intoolbar Message this user directly", "Message")
icon.name: "document-send-symbolic"
visible: !root.isSelf
onTriggered: {
root.close();
root.connection.requestDirectChat(root.user.id);
}
},
Kirigami.Action {
text: i18nc("@action:intoolbar Edit This Account", "Edit")
icon.name: "document-edit"
visible: root.isSelf
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
},
Kirigami.Action {
icon.name: "im-invisible-user-symbolic"
text: root.connection.isIgnored(root.user.id) ? i18nc("@action:intoolbar Unignore or 'unblock' this user", "Unignore") : i18nc("@action:intoolbar Ignore or 'block' this user", "Ignore")
visible: !root.isSelf
onTriggered: {
root.close();

View File

@@ -19,6 +19,91 @@ FormCard.FormCardPage {
title: i18nc("@title:window", "Edit Account")
property NeoChatConnection connection
readonly property bool hasUnsavedChanges: root.connection.localUser.displayName !== name.text
|| avatar.source != avatar.findOriginalAvatarUrl()
|| root.connection.label !== accountLabel.text;
function resetChanges(): void {
name.text = root.connection ? root.connection.localUser.displayName : "";
accountLabel.text = root.connection ? root.connection.label : "";
avatar.source = avatar.findOriginalAvatarUrl();
}
function saveChanges(): void {
if (avatar.source != avatar.findOriginalAvatarUrl() && !root.connection.setAvatar(avatar.source)) {
(root.Window.window as Kirigami.ApplicationWindow).showPassiveNotification(i18nc("@info", "New avatar could not be set."));
}
if (root.connection.localUser.displayName !== name.text) {
root.connection.localUser.rename(name.text);
}
if (root.connection.label !== accountLabel.text) {
root.connection.label = accountLabel.text;
}
}
function checkForUnsavedChanges(): bool {
if (root.hasUnsavedChanges) {
resetChangesDialog.open();
return true;
}
return false;
}
onBackRequested: event => {
if (checkForUnsavedChanges(event)) {
event.accepted = true; // Prevent the page from popping
}
}
Connections {
target: root.Window.window
function onClosing(event): void {
if (root.checkForUnsavedChanges(event)) {
event.accepted = false; // Prevent the window from closing
}
}
}
Kirigami.PromptDialog {
id: resetChangesDialog
parent: root.QQC2.Overlay.overlay
preferredWidth: Kirigami.Units.gridUnit * 24
title: i18nc("@title:dialog Apply unsaved settings", "Apply Settings")
subtitle: i18nc("@info", "There are unsaved changes to user information. Apply the changes or discard them?")
standardButtons: QQC2.Dialog.Cancel
footer: QQC2.DialogButtonBox {
QQC2.Button {
text: i18nc("@action:button As in 'Remove this device'", "Apply")
icon.name: "dialog-ok-apply"
onClicked: {
root.saveChanges();
resetChangesDialog.close();
}
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.ApplyRole
}
QQC2.Button {
text: i18nc("@action:button As in 'Remove this device'", "Reset")
icon.name: "edit-reset-symbolic"
onClicked: {
root.resetChanges();
resetChangesDialog.close();
}
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.ResetRole
}
}
}
Component.onCompleted: resetChanges()
KirigamiComponents.AvatarButton {
id: avatar
@@ -34,8 +119,6 @@ FormCard.FormCardPage {
padding: 0
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
source: findOriginalAvatarUrl()
name: root.connection.localUser.displayName
function findOriginalAvatarUrl(): string {
@@ -109,14 +192,12 @@ FormCard.FormCardPage {
FormCard.FormTextFieldDelegate {
id: name
label: i18n("Display Name:")
text: root.connection ? root.connection.localUser.displayName : ""
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
id: accountLabel
label: i18n("Label:")
placeholderText: i18n("Work")
text: root.connection ? root.connection.label : ""
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextDelegate {
@@ -140,34 +221,17 @@ FormCard.FormCardPage {
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
onClicked: {
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
text: "https://matrix.to/#/" + root.connection.localUser.id,
title: root.connection.localUser.displayName,
subtitle: root.connection.localUser.id,
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
avatarSource: root.connection && (root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : "")
});
qrMax.open();
}
text: i18nc("@action:button", "Reset Changes")
icon.name: "edit-reset-symbolic"
enabled: root.hasUnsavedChanges
onClicked: root.resetChanges()
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Save")
text: i18nc("@action:button Save changes to user", "Save")
icon.name: "document-save-symbolic"
onClicked: {
if (avatar.source != avatar.findOriginalAvatarUrl() && !root.connection.setAvatar(avatar.source)) {
(root.Window.window as Kirigami.ApplicationWindow).showPassiveNotification("The Avatar could not be set");
}
if (root.connection.localUser.displayName !== name.text) {
root.connection.localUser.rename(name.text);
}
if (root.connection.label !== accountLabel.text) {
root.connection.label = accountLabel.text;
}
}
enabled: root.hasUnsavedChanges
onClicked: root.saveChanges()
}
}