Improve the "Create a Room/Space" and "Select Existing Room" dialog

First of all, these are now all separate dialogs instead of shoving all
of these functions (which are only marginally related) into one single
dialog. We also convert these to in-app Kirigami Dialogs, which look a
bit nicer. I also touched up the UX in some places, such as adding
descriptions which were previously available to translators. I also hid
some not oft used options like setting a topic, which almost nobody does
before creating a room/space.
This commit is contained in:
Joshua Goins
2025-02-21 17:29:00 -05:00
parent e9568b50fc
commit d3fd441c88
8 changed files with 350 additions and 282 deletions

View File

@@ -105,6 +105,8 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AvatarNotification.qml
qml/ReasonDialog.qml
qml/NewPollDialog.qml
qml/CreateSpaceDialog.qml
qml/SelectExistingRoomDialog.qml
DEPENDENCIES
QtCore
QtQuick

View File

@@ -11,265 +11,63 @@ import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
FormCard.FormCardPage {
Kirigami.Dialog {
id: root
property string parentId: ""
property bool isSpace: false
property bool showChildType: false
property bool showCreateChoice: false
property string parentId
required property NeoChatConnection connection
signal addChild(string childId, bool setChildParent, bool canonical)
signal newChild(string childName)
title: isSpace ? i18nc("@title", "Create a Space") : i18nc("@title", "Create a Room")
title: i18nc("@title", "Create Room")
implicitWidth: Kirigami.Units.gridUnit * 20
standardButtons: Kirigami.Dialog.Cancel
customFooterActions: [
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:button Create new room", "Create")
enabled: roomNameField.text.length > 0
onTriggered: {
root.connection.createRoom(roomNameField.text, "", root.parentId, false);
root.newChild(roomNameField.text);
root.close();
}
}
]
Component.onCompleted: roomNameField.forceActiveFocus()
FormCard.FormHeader {
title: root.isSpace ? i18n("New Space Information") : i18n("New Room Information")
}
FormCard.FormCard {
FormCard.FormComboBoxDelegate {
id: roomTypeCombo
property bool isInitialising: true
ColumnLayout {
spacing: Kirigami.Units.largeSpacing
visible: root.showChildType
text: i18n("Select type")
model: ListModel {
id: roomTypeModel
}
textRole: "text"
valueRole: "isSpace"
Component.onCompleted: {
currentIndex = indexOfValue(root.isSpace);
roomTypeModel.append({
"text": i18n("Room"),
"isSpace": false
});
roomTypeModel.append({
"text": i18n("Space"),
"isSpace": true
});
roomTypeCombo.currentIndex = 0;
roomTypeCombo.isInitialising = false;
}
onCurrentValueChanged: {
if (!isInitialising) {
root.isSpace = currentValue;
}
}
}
FormCard.FormDelegateSeparator {
visible: root.showChildType
}
FormCard.FormTextFieldDelegate {
id: roomNameField
label: i18n("Name:")
onAccepted: if (roomNameField.text.length > 0) {
roomTopicField.forceActiveFocus();
}
}
FormCard.FormDelegateSeparator {}
FormCard.FormTextFieldDelegate {
id: roomTopicField
label: i18n("Topic:")
onAccepted: ok.clicked()
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
id: newOfficialCheck
visible: root.parentId.length > 0
text: i18nc("@option:check As in make the space from which this dialog was created an official parent.", "Make this parent official")
FormCard.FormRadioDelegate {
id: privateTypeDelegate
text: i18nc("@info:label", "Private")
description: i18nc("@info:description", "This room can only be joined with an invite.")
checked: true
}
FormCard.FormDelegateSeparator {
visible: root.parentId.length > 0
}
FormCard.FormButtonDelegate {
id: ok
text: root.isSpace ? i18nc("@action:button", "Create Space") : i18nc("@action:button", "Create Room")
enabled: roomNameField.text.length > 0
onClicked: {
if (root.isSpace) {
root.connection.createSpace(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
} else {
root.connection.createRoom(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
}
root.newChild(roomNameField.text);
root.closeDialog();
}
}
}
FormCard.FormHeader {
visible: root.showChildType
title: i18n("Select Existing Room")
}
FormCard.FormCard {
visible: root.showChildType
FormCard.FormButtonDelegate {
visible: !chosenRoomDelegate.visible
text: i18nc("@action:button", "Pick room")
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;
chosenRoomDelegate.avatarUrl = avatarUrl;
chosenRoomDelegate.alias = alias;
chosenRoomDelegate.topic = topic;
chosenRoomDelegate.memberCount = memberCount;
chosenRoomDelegate.isJoined = isJoined;
chosenRoomDelegate.visible = true;
});
}
}
FormCard.AbstractFormDelegate {
id: chosenRoomDelegate
property string roomId
property string displayName
property url avatarUrl
property string alias
property string topic
property int memberCount
property bool isJoined
visible: false
contentItem: RowLayout {
Components.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
source: chosenRoomDelegate.avatarUrl
name: chosenRoomDelegate.displayName
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
Kirigami.Heading {
Layout.fillWidth: true
level: 4
text: chosenRoomDelegate.displayName
font.bold: true
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
QQC2.Label {
visible: chosenRoomDelegate.isJoined
text: i18n("Joined")
color: Kirigami.Theme.linkColor
}
}
QQC2.Label {
Layout.fillWidth: true
visible: text
text: chosenRoomDelegate.topic ? chosenRoomDelegate.topic.replace(/(\r\n\t|\n|\r\t)/gm, " ") : ""
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
RowLayout {
Layout.fillWidth: true
Kirigami.Icon {
source: "user"
color: Kirigami.Theme.disabledTextColor
implicitHeight: Kirigami.Units.iconSizes.small
implicitWidth: Kirigami.Units.iconSizes.small
}
QQC2.Label {
text: chosenRoomDelegate.memberCount + " " + (chosenRoomDelegate.alias ?? chosenRoomDelegate.roomId)
color: Kirigami.Theme.disabledTextColor
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
}
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;
chosenRoomDelegate.avatarUrl = avatarUrl;
chosenRoomDelegate.alias = alias;
chosenRoomDelegate.topic = topic;
chosenRoomDelegate.memberCount = memberCount;
chosenRoomDelegate.isJoined = isJoined;
chosenRoomDelegate.visible = true;
});
}
FormCard.FormRadioDelegate {
id: publicTypeDelegate
text: i18nc("@info:label", "Public")
description: i18nc("@info:description", "This room can be found and joined by anyone.")
}
FormCard.FormDelegateSeparator {}
FormCard.FormCheckDelegate {
id: existingOfficialCheck
visible: root.parentId.length > 0
text: i18nc("@option:check As in make the space from which this dialog was created an official parent.", "Make this parent official")
description: enabled ? i18n("You have the required privilege level in the child to set this state") : i18n("You do not have a high enough privilege level in the child to set this state")
checked: enabled
enabled: {
if (chosenRoomDelegate.visible) {
let room = root.connection.room(chosenRoomDelegate.roomId);
if (room) {
if (room.canSendState("m.space.parent")) {
return true;
}
}
}
return false;
}
FormCard.FormTextFieldDelegate {
id: roomNameField
label: i18nc("@info:label Name of the room", "Name:")
placeholderText: i18nc("@info:placeholder Placeholder for room name", "New Room")
}
FormCard.FormDelegateSeparator {
visible: root.parentId.length > 0
}
FormCard.FormCheckDelegate {
id: makeCanonicalCheck
text: i18nc("@option:check The canonical parent is the default one if a room has multiple parent spaces.", "Make this space the canonical parent")
checked: enabled
enabled: existingOfficialCheck.enabled
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Ok")
enabled: chosenRoomDelegate.visible
onClicked: {
root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked, makeCanonicalCheck.checked);
root.closeDialog();
}
FormCard.FormTextFieldDelegate {
id: roomAddressField
label: i18nc("@info:label Address or alias to refer to the room by", "Address:")
placeholderText: i18nc("@info:placeholder Placeholder address for the room", "new-room")
visible: publicTypeDelegate.checked
}
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
Kirigami.Dialog {
id: root
property string parentId
required property NeoChatConnection connection
signal newChild(string childName)
title: i18nc("@title", "Create a Space")
implicitWidth: Kirigami.Units.gridUnit * 20
standardButtons: Kirigami.Dialog.Cancel
Component.onCompleted: roomNameField.forceActiveFocus()
customFooterActions: [
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:button Create new space", "Create")
enabled: roomNameField.text.length > 0
onTriggered: {
root.connection.createSpace(roomNameField.text, "", root.parentId, newOfficialCheck.checked);
root.newChild(roomNameField.text);
root.close();
}
}
]
ColumnLayout {
spacing: Kirigami.Units.largeSpacing
FormCard.FormTextFieldDelegate {
id: roomNameField
label: i18nc("@info:label Name of the space", "Name:")
placeholderText: i18nc("@info:placeholder", "New Space")
}
FormCard.FormDelegateSeparator {
above: roomNameField
below: newOfficialCheck
visible: newOfficialCheck.visible
}
FormCard.FormCheckDelegate {
id: newOfficialCheck
visible: root.parentId.length > 0
text: i18nc("@option:check As in make the space from which this dialog was created an official parent.", "Make this parent official")
checked: true
}
}
}

View File

@@ -0,0 +1,180 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
Kirigami.Dialog {
id: root
property string parentId
required property NeoChatConnection connection
signal addChild(string childId, bool setChildParent, bool canonical)
signal newChild(string childName)
title: i18nc("@title", "Select Existing Room")
implicitWidth: Kirigami.Units.gridUnit * 20
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
onAccepted: root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked, makeCanonicalCheck.checked);
Component.onCompleted: pickRoomDelegate.forceActiveFocus()
ColumnLayout {
spacing: Kirigami.Units.largeSpacing
FormCard.FormButtonDelegate {
id: pickRoomDelegate
visible: !chosenRoomDelegate.visible
text: i18nc("@action:button", "Pick Room")
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;
chosenRoomDelegate.avatarUrl = avatarUrl;
chosenRoomDelegate.alias = alias;
chosenRoomDelegate.topic = topic;
chosenRoomDelegate.memberCount = memberCount;
chosenRoomDelegate.isJoined = isJoined;
chosenRoomDelegate.visible = true;
});
}
}
FormCard.AbstractFormDelegate {
id: chosenRoomDelegate
property string roomId
property string displayName
property url avatarUrl
property string alias
property string topic
property int memberCount
property bool isJoined
visible: false
contentItem: RowLayout {
Components.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
source: chosenRoomDelegate.avatarUrl
name: chosenRoomDelegate.displayName
}
ColumnLayout {
Layout.fillWidth: true
RowLayout {
Layout.fillWidth: true
Kirigami.Heading {
Layout.fillWidth: true
level: 4
text: chosenRoomDelegate.displayName
font.bold: true
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
QQC2.Label {
visible: chosenRoomDelegate.isJoined
text: i18n("Joined")
color: Kirigami.Theme.linkColor
}
}
QQC2.Label {
Layout.fillWidth: true
visible: text
text: chosenRoomDelegate.topic ? chosenRoomDelegate.topic.replace(/(\r\n\t|\n|\r\t)/gm, " ") : ""
textFormat: Text.PlainText
elide: Text.ElideRight
wrapMode: Text.NoWrap
}
RowLayout {
Layout.fillWidth: true
Kirigami.Icon {
source: "user"
color: Kirigami.Theme.disabledTextColor
implicitHeight: Kirigami.Units.iconSizes.small
implicitWidth: Kirigami.Units.iconSizes.small
}
QQC2.Label {
text: chosenRoomDelegate.memberCount + " " + (chosenRoomDelegate.alias ?? chosenRoomDelegate.roomId)
color: Kirigami.Theme.disabledTextColor
elide: Text.ElideRight
Layout.fillWidth: true
}
}
}
}
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
chosenRoomDelegate.roomId = roomId;
chosenRoomDelegate.displayName = displayName;
chosenRoomDelegate.avatarUrl = avatarUrl;
chosenRoomDelegate.alias = alias;
chosenRoomDelegate.topic = topic;
chosenRoomDelegate.memberCount = memberCount;
chosenRoomDelegate.isJoined = isJoined;
chosenRoomDelegate.visible = true;
});
}
}
FormCard.FormDelegateSeparator {
below: existingOfficialCheck
}
FormCard.FormCheckDelegate {
id: existingOfficialCheck
visible: root.parentId.length > 0
text: i18nc("@option:check As in make the space from which this dialog was created an official parent.", "Make this parent official")
description: enabled ? i18nc("@info:description", "You have the required privilege level in the child to set this state") : i18n("You do not have a high enough privilege level in the child to set this state")
checked: enabled
enabled: {
if (chosenRoomDelegate.visible) {
let room = root.connection.room(chosenRoomDelegate.roomId);
if (room) {
if (room.canSendState("m.space.parent")) {
return true;
}
}
}
return false;
}
}
FormCard.FormDelegateSeparator {
above: existingOfficialCheck
below: makeCanonicalCheck
}
FormCard.FormCheckDelegate {
id: makeCanonicalCheck
text: i18nc("@option:check The canonical parent is the default one if a room has multiple parent spaces.", "Make this space the canonical parent")
description: i18nc("@info:description", "The canonical parent is the default one if a room has multiple parent spaces.")
checked: enabled
enabled: existingOfficialCheck.enabled
}
}
}

View File

@@ -90,29 +90,13 @@ RowLayout {
action: QQC2.Action {
shortcut: StandardKey.New
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
Qt.createComponent('org.kde.neochat', 'CreateRoomDialog').createObject(root, {
connection: root.connection
}, {
title: i18nc("@title", "Create a Room")
});
}).open();
}
}
}
QQC2.MenuItem {
text: i18n("Create a Space")
icon.name: "list-add"
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection,
isSpace: true,
title: i18nc("@title", "Create a Space")
}, {
title: i18nc("@title", "Create a Space")
});
}
}
QQC2.MenuItem {
text: i18n("Scan a QR Code")
icon.name: "view-barcode-qr"

View File

@@ -159,13 +159,9 @@ Kirigami.NavigationTabBar {
text: i18n("Create a Space")
icon.name: "list-add"
onTriggered: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection,
isSpace: true,
title: i18nc("@title", "Create a Space")
}, {
title: i18nc("@title", "Create a Space")
});
Qt.createComponent('org.kde.neochat', 'CreateSpaceDialog').createObject(root, {
connection: root.connection
}).open();
explorePopup.close();
}
}

View File

@@ -268,13 +268,11 @@ QQC2.Control {
activeFocusOnTab: true
onSelected: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
connection: root.connection,
isSpace: true,
title: i18nc("@title", "Create a Space")
}, {
title: i18nc("@title", "Create a Space")
})
onSelected: {
Qt.createComponent('org.kde.neochat', 'CreateSpaceDialog').createObject(root, {
connection: root.connection
}).open();
}
}
AvatarTabButton {

View File

@@ -6,6 +6,7 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.neochat
import org.kde.neochat.libneochat as LibNeoChat
@@ -20,6 +21,30 @@ ColumnLayout {
spacing: 0
Component {
id: roomMenuComponent
KirigamiComponents.ConvergentContextMenu {
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:inmenu", "New Room…")
onTriggered: _private.createRoom(root.currentRoom.id)
}
Kirigami.Action {
icon.name: "list-add-symbolic"
text: i18nc("@action:inmenu", "New Space…")
onTriggered: _private.createSpace(root.currentRoom.id)
}
Kirigami.Action {
icon.name: "search-symbolic"
text: i18nc("@action:inmenu", "Existing Room…")
onTriggered: _private.selectExisting(root.currentRoom.id)
}
}
}
QQC2.Control {
id: headerItem
Layout.fillWidth: true
@@ -57,10 +82,15 @@ ColumnLayout {
})
}
QQC2.Button {
id: addNewButton
visible: root.currentRoom.canSendState("m.space.child")
text: i18nc("@button", "Add new room")
text: i18nc("@button", "Add to Space")
icon.name: "list-add"
onClicked: _private.createRoom(root.currentRoom.id)
onClicked: {
const menu = roomMenuComponent.createObject(addNewButton);
menu.popup();
}
}
QQC2.Button {
text: i18nc("@action:button", "Leave this space")
@@ -158,15 +188,33 @@ ColumnLayout {
}
QtObject {
id: _private
function createRoom(parentId) {
let dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
title: i18nc("@title", "Create a Child"),
const dialog = Qt.createComponent('org.kde.neochat', 'CreateRoomDialog').createObject(root, {
connection: root.currentRoom.connection,
parentId: parentId
});
dialog.newChild.connect(childName => {
spaceChildrenModel.addPendingChild(childName);
});
dialog.open();
}
function createSpace(parentId) {
const dialog = Qt.createComponent('org.kde.neochat', 'CreateSpaceDialog').createObject(root, {
connection: root.currentRoom.connection,
parentId: parentId,
});
dialog.newChild.connect(childName => {
spaceChildrenModel.addPendingChild(childName);
});
dialog.open();
}
function selectExisting(parentId) {
const dialog = Qt.createComponent('org.kde.neochat', 'SelectExistingRoomDialog').createObject(root, {
connection: root.currentRoom.connection,
parentId: parentId,
showChildType: true,
showCreateChoice: true
}, {
title: i18nc("@title", "Create a Child")
});
dialog.addChild.connect((childId, setChildParent, canonical) => {
// We have to get a room object from the connection as we may not
@@ -176,9 +224,7 @@ ColumnLayout {
parent.addChild(childId, setChildParent, canonical);
}
});
dialog.newChild.connect(childName => {
spaceChildrenModel.addPendingChild(childName);
});
dialog.open();
}
}
}