diff --git a/imports/Spectral/Component/Timeline/FileDelegate.qml b/imports/Spectral/Component/Timeline/FileDelegate.qml index 87186b6..966eb28 100644 --- a/imports/Spectral/Component/Timeline/FileDelegate.qml +++ b/imports/Spectral/Component/Timeline/FileDelegate.qml @@ -9,6 +9,7 @@ import Spectral 0.1 import Spectral.Setting 0.1 import Spectral.Component 2.0 +import Spectral.Dialog 2.0 import Spectral.Font 0.1 ColumnLayout { @@ -111,16 +112,19 @@ ColumnLayout { onSecondaryClicked: messageContextMenu.popup() + Component { + id: messageSourceDialog + + MessageSourceDialog {} + } + Menu { id: messageContextMenu MenuItem { text: "View Source" - onTriggered: { - sourceDialog.sourceText = toolTip - sourceDialog.open() - } + onTriggered: messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open() } MenuItem { text: "Open Externally" diff --git a/imports/Spectral/Component/Timeline/ImageDelegate.qml b/imports/Spectral/Component/Timeline/ImageDelegate.qml index b92881c..cdb6395 100644 --- a/imports/Spectral/Component/Timeline/ImageDelegate.qml +++ b/imports/Spectral/Component/Timeline/ImageDelegate.qml @@ -9,6 +9,7 @@ import Spectral 0.1 import Spectral.Setting 0.1 import Spectral.Component 2.0 +import Spectral.Dialog 2.0 import Spectral.Effect 2.0 import Spectral.Font 0.1 @@ -133,16 +134,19 @@ ColumnLayout { onSecondaryClicked: messageContextMenu.popup() + Component { + id: messageSourceDialog + + MessageSourceDialog {} + } + Menu { id: messageContextMenu MenuItem { text: "View Source" - onTriggered: { - sourceDialog.sourceText = toolTip - sourceDialog.open() - } + onTriggered: messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open() } MenuItem { diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index 100d062..036a756 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -7,6 +7,7 @@ import Spectral 0.1 import Spectral.Setting 0.1 import Spectral.Component 2.0 +import Spectral.Dialog 2.0 import Spectral.Effect 2.0 import Spectral.Font 0.1 @@ -84,6 +85,12 @@ ColumnLayout { onSecondaryClicked: messageContextMenu.popup() + Component { + id: messageSourceDialog + + MessageSourceDialog {} + } + Menu { readonly property string selectedText: contentLabel.selectedText @@ -92,10 +99,7 @@ ColumnLayout { MenuItem { text: "View Source" - onTriggered: { - sourceDialog.sourceText = toolTip - sourceDialog.open() - } + onTriggered: messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open() } MenuItem { text: "Reply" diff --git a/imports/Spectral/Dialog/CreateRoomDialog.qml b/imports/Spectral/Dialog/CreateRoomDialog.qml new file mode 100644 index 0000000..e6efb64 --- /dev/null +++ b/imports/Spectral/Dialog/CreateRoomDialog.qml @@ -0,0 +1,38 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Spectral.Component 2.0 + +Dialog { + anchors.centerIn: parent + width: 360 + + id: root + + title: "Create a Room" + + contentItem: ColumnLayout { + AutoTextField { + Layout.fillWidth: true + + id: roomNameField + + placeholderText: "Room Name" + } + + AutoTextField { + Layout.fillWidth: true + + id: roomTopicField + + placeholderText: "Room Topic" + } + } + + standardButtons: Dialog.Ok | Dialog.Cancel + + onAccepted: spectralController.createRoom(spectralController.connection, roomNameField.text, roomTopicField.text) + + onClosed: destroy() +} diff --git a/imports/Spectral/Dialog/InviteUserDialog.qml b/imports/Spectral/Dialog/InviteUserDialog.qml new file mode 100644 index 0000000..f56858a --- /dev/null +++ b/imports/Spectral/Dialog/InviteUserDialog.qml @@ -0,0 +1,28 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Spectral.Component 2.0 + +Dialog { + property var room + + anchors.centerIn: parent + width: 360 + + id: root + + title: "Invite User" + + modal: true + standardButtons: Dialog.Ok | Dialog.Cancel + + contentItem: AutoTextField { + id: inviteUserDialogTextField + placeholderText: "User ID" + } + + onAccepted: room.inviteToRoom(inviteUserDialogTextField.text) + + onClosed: destroy() +} diff --git a/imports/Spectral/Dialog/JoinRoomDialog.qml b/imports/Spectral/Dialog/JoinRoomDialog.qml new file mode 100644 index 0000000..e819cf5 --- /dev/null +++ b/imports/Spectral/Dialog/JoinRoomDialog.qml @@ -0,0 +1,38 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Spectral.Component 2.0 + +Dialog { + anchors.centerIn: parent + width: 360 + + id: root + + title: "Start a Chat" + + contentItem: ColumnLayout { + AutoTextField { + Layout.fillWidth: true + + id: identifierField + + placeholderText: "Room Alias/User ID" + } + } + + standardButtons: Dialog.Ok | Dialog.Cancel + + onAccepted: { + var identifier = identifierField.text + var firstChar = identifier.charAt(0) + if (firstChar == "@") { + spectralController.createDirectChat(spectralController.connection, identifier) + } else if (firstChar == "!" || firstChar == "#") { + spectralController.joinRoom(spectralController.connection, identifier) + } + } + + onClosed: destroy() +} diff --git a/imports/Spectral/Dialog/LoginDialog.qml b/imports/Spectral/Dialog/LoginDialog.qml new file mode 100644 index 0000000..3148300 --- /dev/null +++ b/imports/Spectral/Dialog/LoginDialog.qml @@ -0,0 +1,56 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Spectral.Component 2.0 + +Dialog { + anchors.centerIn: parent + width: 360 + + id: root + + title: "Login" + + standardButtons: Dialog.Ok | Dialog.Cancel + + onAccepted: doLogin() + + contentItem: ColumnLayout { + AutoTextField { + Layout.fillWidth: true + + id: serverField + + placeholderText: "Server Address" + text: "https://matrix.org" + } + + AutoTextField { + Layout.fillWidth: true + + id: usernameField + + placeholderText: "Username" + + onAccepted: passwordField.forceActiveFocus() + } + + AutoTextField { + Layout.fillWidth: true + + id: passwordField + + placeholderText: "Password" + echoMode: TextInput.Password + + onAccepted: root.doLogin() + } + } + + function doLogin() { + spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text) + } + + onClosed: root.destroy() +} diff --git a/imports/Spectral/Dialog/MessageSourceDialog.qml b/imports/Spectral/Dialog/MessageSourceDialog.qml new file mode 100644 index 0000000..41ab04c --- /dev/null +++ b/imports/Spectral/Dialog/MessageSourceDialog.qml @@ -0,0 +1,27 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 + +Popup { + property string sourceText + + anchors.centerIn: parent + width: 480 + + id: root + + modal: true + padding: 16 + + closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside + + contentItem: ScrollView { + clip: true + + Label { + text: sourceText + } + } + + onClosed: root.destroy() +} + diff --git a/imports/Spectral/Dialog/RoomSettingsDialog.qml b/imports/Spectral/Dialog/RoomSettingsDialog.qml new file mode 100644 index 0000000..c71db94 --- /dev/null +++ b/imports/Spectral/Dialog/RoomSettingsDialog.qml @@ -0,0 +1,218 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Spectral.Component 2.0 +import Spectral.Effect 2.0 +import Spectral.Setting 0.1 + +Dialog { + property var room + + anchors.centerIn: parent + width: 480 + + id: root + + title: "Room Settings - " + (room ? room.displayName : "") + modal: true + + contentItem: ColumnLayout { + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + Avatar { + Layout.preferredWidth: 72 + Layout.preferredHeight: 72 + Layout.alignment: Qt.AlignTop + + hint: room ? room.displayName : "No name" + source: room ? room.avatarMediaId : null + } + + ColumnLayout { + Layout.fillWidth: true + Layout.margins: 4 + + AutoTextField { + Layout.fillWidth: true + + text: room ? room.name : "" + placeholderText: "Room Name" + } + + AutoTextField { + Layout.fillWidth: true + + text: room ? room.topic : "" + placeholderText: "Room Topic" + } + } + } + + Control { + Layout.fillWidth: true + + visible: room ? room.predecessorId : false + + padding: 8 + + contentItem: RowLayout { + MaterialIcon { + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + + icon: "\ue8d4" + } + + ColumnLayout { + Layout.fillWidth: true + + spacing: 0 + + Label { + Layout.fillWidth: true + + font.bold: true + color: MPalette.foreground + text: "This room is a continuation of another conversation." + } + + Label { + Layout.fillWidth: true + + color: MPalette.lighter + text: "Click here to see older messages." + } + } + } + + background: Rectangle { + color: MPalette.banner + + RippleEffect { + anchors.fill: parent + + onClicked: { + roomListForm.enteredRoom = spectralController.connection.room(room.predecessorId) + root.close() + } + } + } + } + + Control { + Layout.fillWidth: true + + visible: room ? room.successorId : false + + padding: 8 + + contentItem: RowLayout { + MaterialIcon { + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + + icon: "\ue8d4" + } + + ColumnLayout { + Layout.fillWidth: true + + spacing: 0 + + Label { + Layout.fillWidth: true + + font.bold: true + color: MPalette.foreground + text: "This room has been replaced and is no longer active." + } + + Label { + Layout.fillWidth: true + + color: MPalette.lighter + text: "The conversation continues here." + } + } + } + + background: Rectangle { + color: MPalette.banner + + RippleEffect { + anchors.fill: parent + + onClicked: { + roomListForm.enteredRoom = spectralController.connection.room(room.successorId) + root.close() + } + } + } + } + + MenuSeparator { + Layout.fillWidth: true + } + + ColumnLayout { + Layout.fillWidth: true + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.preferredWidth: 100 + + wrapMode: Label.Wrap + text: "Main Alias" + color: MPalette.lighter + } + + ComboBox { + Layout.fillWidth: true + + model: room ? room.aliases : null + + currentIndex: room ? room.aliases.indexOf(room.canonicalAlias) : -1 + } + } + + RowLayout { + Layout.fillWidth: true + + Label { + Layout.preferredWidth: 100 + Layout.alignment: Qt.AlignTop + + wrapMode: Label.Wrap + text: "Aliases" + color: MPalette.lighter + } + + ColumnLayout { + Layout.fillWidth: true + + Repeater { + model: room ? room.aliases : null + + delegate: Label { + Layout.fillWidth: true + + text: modelData + + font.pixelSize: 12 + color: MPalette.lighter + } + } + } + } + } + } + + onClosed: destroy() +} + diff --git a/imports/Spectral/Dialog/UserDetailDialog.qml b/imports/Spectral/Dialog/UserDetailDialog.qml new file mode 100644 index 0000000..af59d79 --- /dev/null +++ b/imports/Spectral/Dialog/UserDetailDialog.qml @@ -0,0 +1,162 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 + +import Spectral.Component 2.0 +import Spectral.Effect 2.0 +import Spectral.Setting 0.1 + +Dialog { + property var room + property var user + + anchors.centerIn: parent + width: 360 + + id: root + + modal: true + + contentItem: ColumnLayout { + RowLayout { + Layout.fillWidth: true + + spacing: 16 + + Avatar { + Layout.preferredWidth: 72 + Layout.preferredHeight: 72 + + hint: user ? user.displayName : "No name" + source: user ? user.avatarMediaId : null + } + + ColumnLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + + font.pixelSize: 18 + font.bold: true + wrapMode: Label.Wrap + text: user ? user.displayName : "No Name" + color: MPalette.foreground + } + + Label { + Layout.fillWidth: true + + wrapMode: Label.Wrap + text: "Online" + color: MPalette.lighter + } + } + } + + MenuSeparator { + Layout.fillWidth: true + } + + RowLayout { + Layout.fillWidth: true + + spacing: 8 + + MaterialIcon { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + Layout.alignment: Qt.AlignTop + + icon: "\ue88f" + color: MPalette.lighter + } + + ColumnLayout { + Layout.fillWidth: true + + Label { + Layout.fillWidth: true + + wrapMode: Label.Wrap + text: user ? user.id : "No ID" + color: MPalette.accent + } + + Label { + Layout.fillWidth: true + + wrapMode: Label.Wrap + text: "User ID" + color: MPalette.lighter + } + } + } + + MenuSeparator { + Layout.fillWidth: true + } + + Control { + Layout.fillWidth: true + + contentItem: RowLayout { + MaterialIcon { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + Layout.alignment: Qt.AlignTop + + icon: room.connection.isIgnored(user) ? "\ue7f5" : "\ue7f6" + color: MPalette.lighter + } + + Label { + Layout.fillWidth: true + + wrapMode: Label.Wrap + text: room.connection.isIgnored(user) ? "Unignore this user" : "Ignore this user" + + color: MPalette.accent + } + } + + background: RippleEffect { + onPrimaryClicked: { + room.connection.isIgnored(user) ? room.connection.removeFromIgnoredUsers(user) : room.connection.addToIgnoredUsers(user) + root.close() + } + } + } + + Control { + Layout.fillWidth: true + + contentItem: RowLayout { + MaterialIcon { + Layout.preferredWidth: 32 + Layout.preferredHeight: 32 + Layout.alignment: Qt.AlignTop + + icon: "\ue5d9" + color: MPalette.lighter + } + + Label { + Layout.fillWidth: true + + wrapMode: Label.Wrap + text: "Kick this user" + + color: MPalette.accent + } + } + + background: RippleEffect { + onPrimaryClicked: room.kickMember(user.id) + } + } + } + + onClosed: destroy() +} + diff --git a/imports/Spectral/Dialog/qmldir b/imports/Spectral/Dialog/qmldir new file mode 100644 index 0000000..0649630 --- /dev/null +++ b/imports/Spectral/Dialog/qmldir @@ -0,0 +1,8 @@ +module Spectral.Dialog +RoomSettingsDialog 2.0 RoomSettingsDialog.qml +UserDetailDialog 2.0 UserDetailDialog.qml +MessageSourceDialog 2.0 MessageSourceDialog.qml +LoginDialog 2.0 LoginDialog.qml +CreateRoomDialog 2.0 CreateRoomDialog.qml +JoinRoomDialog 2.0 JoinRoomDialog.qml +InviteUserDialog 2.0 InviteUserDialog.qml diff --git a/imports/Spectral/Panel/RoomDrawer.qml b/imports/Spectral/Panel/RoomDrawer.qml index 72a1e8d..3fc90fb 100644 --- a/imports/Spectral/Panel/RoomDrawer.qml +++ b/imports/Spectral/Panel/RoomDrawer.qml @@ -4,6 +4,7 @@ import QtQuick.Controls.Material 2.12 import QtQuick.Layouts 1.12 import Spectral.Component 2.0 +import Spectral.Dialog 2.0 import Spectral.Effect 2.0 import Spectral.Setting 0.1 @@ -115,7 +116,7 @@ Drawer { } background: RippleEffect { - onPrimaryClicked: roomDetailDialog.open() + onPrimaryClicked: roomSettingDialog.createObject(ApplicationWindow.overlay, {"room": room}).open() } } @@ -153,7 +154,7 @@ Drawer { color: MPalette.lighter } - onClicked: inviteUserDialog.open() + onClicked: inviteUserDialog.createObject(ApplicationWindow.overlay, {"room": room}).open() } } @@ -198,6 +199,8 @@ Drawer { RippleEffect { anchors.fill: parent + + onPrimaryClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": room, "user": user}).open() } } @@ -205,225 +208,21 @@ Drawer { } } - Dialog { - anchors.centerIn: parent - width: 360 + Component { + id: roomSettingDialog + RoomSettingsDialog {} + } + + Component { + id: userDetailDialog + + UserDetailDialog {} + } + + Component { id: inviteUserDialog - parent: ApplicationWindow.overlay - - title: "Invite User" - modal: true - standardButtons: Dialog.Ok | Dialog.Cancel - - contentItem: AutoTextField { - id: inviteUserDialogTextField - placeholderText: "User ID" - } - - onAccepted: room.inviteToRoom(inviteUserDialogTextField.text) - } - - Dialog { - anchors.centerIn: parent - width: 480 - - id: roomDetailDialog - - parent: ApplicationWindow.overlay - - title: "Room Settings - " + (room ? room.displayName : "") - modal: true - - contentItem: ColumnLayout { - RowLayout { - Layout.fillWidth: true - - spacing: 16 - - Avatar { - Layout.preferredWidth: 72 - Layout.preferredHeight: 72 - Layout.alignment: Qt.AlignTop - - hint: room ? room.displayName : "No name" - source: room ? room.avatarMediaId : null - } - - ColumnLayout { - Layout.fillWidth: true - Layout.margins: 4 - - AutoTextField { - Layout.fillWidth: true - - text: room ? room.name : "" - placeholderText: "Room Name" - } - - AutoTextField { - Layout.fillWidth: true - - text: room ? room.topic : "" - placeholderText: "Room Topic" - } - } - } - - Control { - Layout.fillWidth: true - - visible: room ? room.predecessorId : false - - padding: 8 - - contentItem: RowLayout { - MaterialIcon { - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 - - icon: "\ue8d4" - } - - ColumnLayout { - Layout.fillWidth: true - - spacing: 0 - - Label { - Layout.fillWidth: true - - font.bold: true - color: MPalette.foreground - text: "This room is a continuation of another conversation." - } - - Label { - Layout.fillWidth: true - - color: MPalette.lighter - text: "Click here to see older messages." - } - } - } - - background: Rectangle { - color: MPalette.banner - - RippleEffect { - anchors.fill: parent - - onClicked: roomListForm.enteredRoom = spectralController.connection.room(room.predecessorId) - } - } - } - - Control { - Layout.fillWidth: true - - visible: room ? room.successorId : false - - padding: 8 - - contentItem: RowLayout { - MaterialIcon { - Layout.preferredWidth: 48 - Layout.preferredHeight: 48 - - icon: "\ue8d4" - } - - ColumnLayout { - Layout.fillWidth: true - - spacing: 0 - - Label { - Layout.fillWidth: true - - font.bold: true - color: MPalette.foreground - text: "This room has been replaced and is no longer active." - } - - Label { - Layout.fillWidth: true - - color: MPalette.lighter - text: "The conversation continues here." - } - } - } - - background: Rectangle { - color: MPalette.banner - - RippleEffect { - anchors.fill: parent - - onClicked: roomListForm.enteredRoom = spectralController.connection.room(room.successorId) - } - } - } - - MenuSeparator { - Layout.fillWidth: true - } - - ColumnLayout { - Layout.fillWidth: true - - RowLayout { - Layout.fillWidth: true - - Label { - Layout.preferredWidth: 100 - - wrapMode: Label.Wrap - text: "Main Alias" - color: MPalette.lighter - } - - ComboBox { - Layout.fillWidth: true - - model: room ? room.aliases : null - - currentIndex: room ? room.aliases.indexOf(room.canonicalAlias) : -1 - } - } - - RowLayout { - Layout.fillWidth: true - - Label { - Layout.preferredWidth: 100 - Layout.alignment: Qt.AlignTop - - wrapMode: Label.Wrap - text: "Aliases" - color: MPalette.lighter - } - - ColumnLayout { - Layout.fillWidth: true - - Repeater { - model: room ? room.aliases : null - - delegate: Label { - Layout.fillWidth: true - - text: modelData - - font.pixelSize: 12 - color: MPalette.lighter - } - } - } - } - } - } + InviteUserDialog {} } } diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml index b2826c8..443dc8c 100644 --- a/imports/Spectral/Panel/RoomPanel.qml +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -88,9 +88,11 @@ Item { sourceModel: messageEventModel - filters: ExpressionFilter { - expression: marks !== 0x10 && eventType !== "other" - } + filters: [ + ExpressionFilter { + expression: marks !== 0x10 && eventType !== "other" + } + ] onModelReset: { if (currentRoom) { @@ -272,31 +274,6 @@ Item { onClicked: messageListView.positionViewAtBeginning() } - - Popup { - property string sourceText - - anchors.centerIn: parent - width: 480 - - id: sourceDialog - - parent: ApplicationWindow.overlay - - padding: 16 - - closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside - - contentItem: ScrollView { - clip: true - TextArea { - readOnly: true - selectByMouse: true - - text: sourceDialog.sourceText - } - } - } } Control { diff --git a/include/libqmatrixclient b/include/libqmatrixclient index af55d9a..52a81df 160000 --- a/include/libqmatrixclient +++ b/include/libqmatrixclient @@ -1 +1 @@ -Subproject commit af55d9a0f23ed48c7dcec26efe6b01fb44a8c4fc +Subproject commit 52a81dfa8a5415be369d819837f445479b833cde diff --git a/qml/main.qml b/qml/main.qml index 3de3130..19f06ac 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -7,6 +7,7 @@ import Qt.labs.platform 1.0 as Platform import Spectral.Panel 2.0 import Spectral.Component 2.0 +import Spectral.Dialog 2.0 import Spectral.Page 2.0 import Spectral.Effect 2.0 @@ -39,16 +40,14 @@ ApplicationWindow { menu: Platform.Menu { Platform.MenuItem { - text: qsTr("Hide Window") - onTriggered: hideWindow() + text: qsTr("Toggle Window") + onTriggered: window.visible ? hideWindow() : showWindow() } Platform.MenuItem { text: qsTr("Quit") onTriggered: Qt.quit() } } - - onActivated: showWindow() } Controller { @@ -74,102 +73,6 @@ ApplicationWindow { onActivated: Qt.quit() } - Dialog { - property bool busy: false - - width: 360 - x: (window.width - width) / 2 - y: (window.height - height) / 2 - - id: loginDialog - - parent: ApplicationWindow.overlay - - title: "Login" - - contentItem: ColumnLayout { - AutoTextField { - Layout.fillWidth: true - - id: serverField - - placeholderText: "Server Address" - text: "https://matrix.org" - } - - AutoTextField { - Layout.fillWidth: true - - id: usernameField - - placeholderText: "Username" - - onAccepted: passwordField.forceActiveFocus() - } - - AutoTextField { - Layout.fillWidth: true - - id: passwordField - - placeholderText: "Password" - echoMode: TextInput.Password - - onAccepted: loginDialog.doLogin() - } - } - - footer: DialogButtonBox { - Button { - text: "Cancel" - flat: true - enabled: !loginDialog.busy - - onClicked: loginDialog.close() - } - - Button { - text: "OK" - flat: true - enabled: !loginDialog.busy - - onClicked: loginDialog.doLogin() - } - - ToolTip { - id: loginButtonTooltip - - } - } - - onVisibleChanged: { - if (visible) spectralController.onErrorOccured.connect(showError) - else spectralController.onErrorOccured.disconnect(showError) - } - - function showError(error, detail) { - loginDialog.busy = false - loginButtonTooltip.text = error + ": " + detail - loginButtonTooltip.open() - } - - function doLogin() { - if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) { - loginButtonTooltip.text = "Server address should start with http(s)://" - loginButtonTooltip.open() - return - } - - loginDialog.busy = true - spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text) - - spectralController.connectionAdded.connect(function(conn) { - busy = false - loginDialog.close() - }) - } - } - Dialog { anchors.centerIn: parent @@ -267,7 +170,7 @@ ApplicationWindow { color: MPalette.lighter } - onClicked: loginDialog.open() + onClicked: loginDialog.createObject(ApplicationWindow.overlay).open() } } @@ -294,7 +197,7 @@ ApplicationWindow { RippleEffect { anchors.fill: parent - onPrimaryClicked: joinRoomDialog.open() + onPrimaryClicked: joinRoomDialog.createObject(ApplicationWindow.overlay).open() } } @@ -321,7 +224,7 @@ ApplicationWindow { RippleEffect { anchors.fill: parent - onPrimaryClicked: createRoomDialog.open() + onPrimaryClicked: createRoomDialog.createObject(ApplicationWindow.overlay).open() } } @@ -433,66 +336,22 @@ ApplicationWindow { } } - Dialog { - anchors.centerIn: parent - width: 360 + Component { + id: loginDialog - id: joinRoomDialog - - title: "Start a Chat" - - contentItem: ColumnLayout { - AutoTextField { - Layout.fillWidth: true - - id: identifierField - - placeholderText: "Room Alias/User ID" - } - } - - standardButtons: Dialog.Ok | Dialog.Cancel - - onAccepted: { - var identifier = identifierField.text - var firstChar = identifier.charAt(0) - if (firstChar == "@") { - spectralController.createDirectChat(spectralController.connection, identifier) - } else if (firstChar == "!" || firstChar == "#") { - spectralController.joinRoom(spectralController.connection, identifier) - } - } + LoginDialog {} } - Dialog { - anchors.centerIn: parent - width: 360 + Component { + id: joinRoomDialog + JoinRoomDialog {} + } + + Component { id: createRoomDialog - title: "Create a Room" - - contentItem: ColumnLayout { - AutoTextField { - Layout.fillWidth: true - - id: roomNameField - - placeholderText: "Room Name" - } - - AutoTextField { - Layout.fillWidth: true - - id: roomTopicField - - placeholderText: "Room Topic" - } - } - - standardButtons: Dialog.Ok | Dialog.Cancel - - onAccepted: spectralController.createRoom(spectralController.connection, roomNameField.text, roomTopicField.text) + CreateRoomDialog {} } Drawer { diff --git a/res.qrc b/res.qrc index 63748cb..bdc2d16 100644 --- a/res.qrc +++ b/res.qrc @@ -44,5 +44,13 @@ imports/Spectral/Setting/Palette.qml imports/Spectral/Component/Timeline/FileDelegate.qml imports/Spectral/Component/FullScreenImage.qml + imports/Spectral/Dialog/qmldir + imports/Spectral/Dialog/RoomSettingsDialog.qml + imports/Spectral/Dialog/UserDetailDialog.qml + imports/Spectral/Dialog/MessageSourceDialog.qml + imports/Spectral/Dialog/LoginDialog.qml + imports/Spectral/Dialog/CreateRoomDialog.qml + imports/Spectral/Dialog/JoinRoomDialog.qml + imports/Spectral/Dialog/InviteUserDialog.qml diff --git a/src/controller.cpp b/src/controller.cpp index 4080262..d305f38 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -7,6 +7,7 @@ #include "events/eventcontent.h" #include "events/roommessageevent.h" +#include "csapi/account-data.h" #include "csapi/joining.h" #include "csapi/logout.h" diff --git a/src/controller.h b/src/controller.h index f31cc08..1dc208a 100644 --- a/src/controller.h +++ b/src/controller.h @@ -3,9 +3,9 @@ #include "connection.h" #include "notifications/manager.h" +#include "room.h" #include "settings.h" #include "user.h" -#include "room.h" #include #include @@ -54,13 +54,16 @@ class Controller : public QObject { } Connection* connection() { - if (m_connection.isNull()) return nullptr; + if (m_connection.isNull()) + return nullptr; return m_connection; } void setConnection(Connection* conn) { - if (!conn) return; - if (conn == m_connection) return; + if (!conn) + return; + if (conn == m_connection) + return; m_connection = conn; emit connectionChanged(); } @@ -100,9 +103,12 @@ class Controller : public QObject { void createDirectChat(Connection* c, const QString& userID); void copyToClipboard(const QString& text); void playAudio(QUrl localFile); - void postNotification(const QString& roomId, const QString& eventId, - const QString& roomName, const QString& senderName, - const QString& text, const QImage& icon); + void postNotification(const QString& roomId, + const QString& eventId, + const QString& roomName, + const QString& senderName, + const QString& text, + const QImage& icon); }; #endif // CONTROLLER_H diff --git a/src/main.cpp b/src/main.cpp index fb949ef..cdee88c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -23,7 +23,7 @@ using namespace QMatrixClient; -int main(int argc, char *argv[]) { +int main(int argc, char* argv[]) { #if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD) if (qgetenv("QT_SCALE_FACTOR").size() == 0) { QSettings settings("ENCOM", "Spectral"); @@ -59,11 +59,13 @@ int main(int argc, char *argv[]) { "RoomMessageEvent", "ENUM"); qmlRegisterUncreatableType("Spectral", 0, 1, "RoomType", "ENUM"); - qRegisterMetaType("User*"); - qRegisterMetaType("Room*"); + qRegisterMetaType("User*"); + qRegisterMetaType("const User*"); + qRegisterMetaType("Room*"); + qRegisterMetaType("Connection*"); qRegisterMetaType("MessageEventType"); - qRegisterMetaType("SpectralRoom*"); - qRegisterMetaType("SpectralUser*"); + qRegisterMetaType("SpectralRoom*"); + qRegisterMetaType("SpectralUser*"); #if defined(BUNDLE_FONT) QFontDatabase::addApplicationFont(":/assets/font/roboto.ttf"); @@ -73,12 +75,13 @@ int main(int argc, char *argv[]) { QQmlApplicationEngine engine; engine.addImportPath("qrc:/imports"); - ImageProvider *m_provider = new ImageProvider(); + ImageProvider* m_provider = new ImageProvider(); engine.rootContext()->setContextProperty("imageProvider", m_provider); engine.addImageProvider(QLatin1String("mxc"), m_provider); engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml"))); - if (engine.rootObjects().isEmpty()) return -1; + if (engine.rootObjects().isEmpty()) + return -1; return app.exec(); } diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index 30c3eca..40c15d4 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -41,7 +41,7 @@ QHash MessageEventModel::roleNames() const { return roles; } -MessageEventModel::MessageEventModel(QObject *parent) +MessageEventModel::MessageEventModel(QObject* parent) : QAbstractListModel(parent), m_currentRoom(nullptr) { using namespace QMatrixClient; qmlRegisterType(); @@ -52,8 +52,9 @@ MessageEventModel::MessageEventModel(QObject *parent) MessageEventModel::~MessageEventModel() {} -void MessageEventModel::setRoom(SpectralRoom *room) { - if (room == m_currentRoom) return; +void MessageEventModel::setRoom(SpectralRoom* room) { + if (room == m_currentRoom) + return; beginResetModel(); if (m_currentRoom) { @@ -96,8 +97,9 @@ void MessageEventModel::setRoom(SpectralRoom *room) { connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows); connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, - [this](RoomEvent *, int i) { - if (i == 0) return; // No need to move anything, just refresh + [this](RoomEvent*, int i) { + if (i == 0) + return; // No need to move anything, just refresh movingEvent = true; // Reverse i because row 0 is bottommost in the model @@ -131,7 +133,7 @@ void MessageEventModel::setRoom(SpectralRoom *room) { refreshEventRoles(lastReadEventId, {ReadMarkerRole}); }); connect(m_currentRoom, &Room::replacedEvent, this, - [this](const RoomEvent *newEvent) { + [this](const RoomEvent* newEvent) { refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex()); }); @@ -144,10 +146,15 @@ void MessageEventModel::setRoom(SpectralRoom *room) { connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent); connect(m_currentRoom, &Room::readMarkerForUserMoved, this, - [=](User *, QString fromEventId, QString toEventId) { + [=](User*, QString fromEventId, QString toEventId) { refreshEventRoles(fromEventId, {UserMarkerRole}); refreshEventRoles(toEventId, {UserMarkerRole}); }); + connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, + this, [=] { + beginResetModel(); + endResetModel(); + }); qDebug() << "Connected to room" << room->id() << "as" << room->localUser()->id(); } else @@ -155,23 +162,25 @@ void MessageEventModel::setRoom(SpectralRoom *room) { endResetModel(); } -int MessageEventModel::refreshEvent(const QString &eventId) { +int MessageEventModel::refreshEvent(const QString& eventId) { return refreshEventRoles(eventId); } -void MessageEventModel::refreshRow(int row) { refreshEventRoles(row); } +void MessageEventModel::refreshRow(int row) { + refreshEventRoles(row); +} int MessageEventModel::timelineBaseIndex() const { return m_currentRoom ? int(m_currentRoom->pendingEvents().size()) : 0; } -void MessageEventModel::refreshEventRoles(int row, const QVector &roles) { +void MessageEventModel::refreshEventRoles(int row, const QVector& roles) { const auto idx = index(row); emit dataChanged(idx, idx, roles); } -int MessageEventModel::refreshEventRoles(const QString &eventId, - const QVector &roles) { +int MessageEventModel::refreshEventRoles(const QString& eventId, + const QVector& roles) { const auto it = m_currentRoom->findInTimeline(eventId); if (it == m_currentRoom->timelineEdge()) { qWarning() << "Trying to refresh inexistent event:" << eventId; @@ -183,15 +192,16 @@ int MessageEventModel::refreshEventRoles(const QString &eventId, return row; } -inline bool hasValidTimestamp(const QMatrixClient::TimelineItem &ti) { +inline bool hasValidTimestamp(const QMatrixClient::TimelineItem& ti) { return ti->timestamp().isValid(); } QDateTime MessageEventModel::makeMessageTimestamp( - const QMatrixClient::Room::rev_iter_t &baseIt) const { - const auto &timeline = m_currentRoom->messageEvents(); + const QMatrixClient::Room::rev_iter_t& baseIt) const { + const auto& timeline = m_currentRoom->messageEvents(); auto ts = baseIt->event()->timestamp(); - if (ts.isValid()) return ts; + if (ts.isValid()) + return ts; // The event is most likely redacted or just invalid. // Look for the nearest date around and slap zero time to it. @@ -210,11 +220,14 @@ QDateTime MessageEventModel::makeMessageTimestamp( QString MessageEventModel::renderDate(QDateTime timestamp) const { auto date = timestamp.toLocalTime().date(); - if (date == QDate::currentDate()) return tr("Today"); - if (date == QDate::currentDate().addDays(-1)) return tr("Yesterday"); + if (date == QDate::currentDate()) + return tr("Today"); + if (date == QDate::currentDate().addDays(-1)) + return tr("Yesterday"); if (date == QDate::currentDate().addDays(-2)) return tr("The day before yesterday"); - if (date > QDate::currentDate().addDays(-7)) return date.toString("dddd"); + if (date > QDate::currentDate().addDays(-7)) + return date.toString("dddd"); return date.toString(Qt::DefaultLocaleShortDate); } @@ -222,8 +235,8 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) { if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow) return; - const auto &timelineBottom = m_currentRoom->messageEvents().rbegin(); - const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId(); + const auto& timelineBottom = m_currentRoom->messageEvents().rbegin(); + const auto& lastSender = (*(timelineBottom + baseTimelineRow))->senderId(); const auto limit = timelineBottom + std::min(baseTimelineRow + 10, m_currentRoom->timelineSize()); for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0); @@ -235,12 +248,13 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) { } } -int MessageEventModel::rowCount(const QModelIndex &parent) const { - if (!m_currentRoom || parent.isValid()) return 0; +int MessageEventModel::rowCount(const QModelIndex& parent) const { + if (!m_currentRoom || parent.isValid()) + return 0; return m_currentRoom->timelineSize(); } -QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { +QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { const auto row = idx.row(); if (!m_currentRoom || row < 0 || @@ -253,7 +267,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { std::max(0, row - timelineBaseIndex()); const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex()); - const auto &evt = isPending ? **pendingIt : **timelineIt; + const auto& evt = isPending ? **pendingIt : **timelineIt; if (role == Qt::DisplayRole) { return utils::removeReply(m_currentRoom->eventToString(evt, Qt::RichText)); @@ -282,7 +296,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { return e->hasFileContent() ? "file" : "message"; } } - if (evt.isStateEvent()) return "state"; + if (evt.isStateEvent()) + return "state"; return "other"; } @@ -298,7 +313,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { if (role == ContentTypeRole) { if (auto e = eventCast(&evt)) { - const auto &contentType = e->mimeType().name(); + const auto& contentType = e->mimeType().name(); return contentType == "text/plain" ? QStringLiteral("text/html") : contentType; } @@ -323,19 +338,27 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { }; } - if (role == HighlightRole) return m_currentRoom->isEventHighlighted(&evt); + if (role == HighlightRole) + return m_currentRoom->isEventHighlighted(&evt); if (role == ReadMarkerRole) return evt.id() == lastReadEventId && row > timelineBaseIndex(); if (role == SpecialMarksRole) { - if (isPending) return pendingIt->deliveryStatus(); + if (isPending) + return pendingIt->deliveryStatus(); - if (is(evt)) return EventStatus::Hidden; - if (evt.isRedacted()) return EventStatus::Hidden; + if (is(evt)) + return EventStatus::Hidden; + if (evt.isRedacted()) + return EventStatus::Hidden; if (evt.isStateEvent() && - static_cast(evt).repeatsState()) + static_cast(evt).repeatsState()) + return EventStatus::Hidden; + + if (m_currentRoom->connection()->isIgnored( + m_currentRoom->user(evt.senderId()))) return EventStatus::Hidden; return EventStatus::Normal; @@ -351,7 +374,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { } if (role == AnnotationRole) - if (isPending) return pendingIt->annotation(); + if (isPending) + return pendingIt->annotation(); if (role == TimeRole || role == SectionRole) { auto ts = @@ -361,8 +385,9 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { if (role == UserMarkerRole) { QVariantList variantList; - for (User *user : m_currentRoom->usersAtEventId(evt.id())) { - if (user == m_currentRoom->localUser()) continue; + for (User* user : m_currentRoom->usersAtEventId(evt.id())) { + if (user == m_currentRoom->localUser()) + continue; variantList.append(QVariant::fromValue(user)); } return variantList; @@ -370,22 +395,24 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { if (role == ReplyEventIdRole || role == ReplyDisplayRole || role == ReplyAuthorRole) { - const QString &replyEventId = evt.contentJson()["m.relates_to"] + const QString& replyEventId = evt.contentJson()["m.relates_to"] .toObject()["m.in_reply_to"] .toObject()["event_id"] .toString(); - if (replyEventId.isEmpty()) return {}; + if (replyEventId.isEmpty()) + return {}; const auto replyIt = m_currentRoom->findInTimeline(replyEventId); - if (replyIt == m_currentRoom->timelineEdge()) return {}; + if (replyIt == m_currentRoom->timelineEdge()) + return {}; const auto& replyEvt = **replyIt; switch (role) { case ReplyEventIdRole: return replyEventId; case ReplyDisplayRole: - return utils::removeReply(m_currentRoom->eventToString(replyEvt, Qt::RichText)); + return utils::removeReply( + m_currentRoom->eventToString(replyEvt, Qt::RichText)); case ReplyAuthorRole: - return QVariant::fromValue( - m_currentRoom->user(replyEvt.senderId())); + return QVariant::fromValue(m_currentRoom->user(replyEvt.senderId())); } return {}; } @@ -394,7 +421,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { role == AboveAuthorRole || role == AboveTimeRole) for (auto r = row + 1; r < rowCount(); ++r) { auto i = index(r); - if (data(i, SpecialMarksRole) != EventStatus::Hidden) switch (role) { + if (data(i, SpecialMarksRole) != EventStatus::Hidden) + switch (role) { case AboveEventTypeRole: return data(i, EventTypeRole); case AboveSectionRole: @@ -409,7 +437,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const { return {}; } -int MessageEventModel::eventIDToIndex(const QString &eventID) { +int MessageEventModel::eventIDToIndex(const QString& eventID) { const auto it = m_currentRoom->findInTimeline(eventID); if (it == m_currentRoom->timelineEdge()) { qWarning() << "Trying to find inexistent event:" << eventID; diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index 7934c2a..772f709 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -6,6 +6,7 @@ #include "csapi/content-repo.h" #include "csapi/leaving.h" #include "csapi/typing.h" +#include "events/accountdataevents.h" #include "events/typingevent.h" #include diff --git a/src/userlistmodel.cpp b/src/userlistmodel.cpp index 15cff77..ff7e697 100644 --- a/src/userlistmodel.cpp +++ b/src/userlistmodel.cpp @@ -14,14 +14,16 @@ UserListModel::UserListModel(QObject* parent) : QAbstractListModel(parent), m_currentRoom(nullptr) {} void UserListModel::setRoom(QMatrixClient::Room* room) { - if (m_currentRoom == room) return; + if (m_currentRoom == room) + return; using namespace QMatrixClient; beginResetModel(); if (m_currentRoom) { m_currentRoom->disconnect(this); -// m_currentRoom->connection()->disconnect(this); - for (User* user : m_users) user->disconnect(this); + // m_currentRoom->connection()->disconnect(this); + for (User* user : m_users) + user->disconnect(this); m_users.clear(); } m_currentRoom = room; @@ -49,12 +51,14 @@ void UserListModel::setRoom(QMatrixClient::Room* room) { } QMatrixClient::User* UserListModel::userAt(QModelIndex index) { - if (index.row() < 0 || index.row() >= m_users.size()) return nullptr; + if (index.row() < 0 || index.row() >= m_users.size()) + return nullptr; return m_users.at(index.row()); } QVariant UserListModel::data(const QModelIndex& index, int role) const { - if (!index.isValid()) return QVariant(); + if (!index.isValid()) + return QVariant(); if (index.row() >= m_users.count()) { qDebug() @@ -71,12 +75,16 @@ QVariant UserListModel::data(const QModelIndex& index, int role) const { if (role == AvatarRole) { return user->avatarMediaId(); } + if (role == ObjectRole) { + return QVariant::fromValue(user); + } return QVariant(); } int UserListModel::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) return 0; + if (parent.isValid()) + return 0; return m_users.count(); } @@ -111,7 +119,8 @@ void UserListModel::refresh(QMatrixClient::User* user, QVector roles) { void UserListModel::avatarChanged(QMatrixClient::User* user, const QMatrixClient::Room* context) { - if (context == m_currentRoom) refresh(user, {AvatarRole}); + if (context == m_currentRoom) + refresh(user, {AvatarRole}); } int UserListModel::findUserPos(User* user) const { @@ -127,5 +136,6 @@ QHash UserListModel::roleNames() const { roles[NameRole] = "name"; roles[UserIDRole] = "userId"; roles[AvatarRole] = "avatar"; + roles[ObjectRole] = "user"; return roles; } diff --git a/src/userlistmodel.h b/src/userlistmodel.h index fd01a5e..25db08a 100644 --- a/src/userlistmodel.h +++ b/src/userlistmodel.h @@ -17,7 +17,12 @@ class UserListModel : public QAbstractListModel { Q_PROPERTY( QMatrixClient::Room* room READ room WRITE setRoom NOTIFY roomChanged) public: - enum EventRoles { NameRole = Qt::UserRole + 1, UserIDRole, AvatarRole }; + enum EventRoles { + NameRole = Qt::UserRole + 1, + UserIDRole, + AvatarRole, + ObjectRole + }; using User = QMatrixClient::User;