diff --git a/matrique.pro b/matrique.pro index 5a6d65a2a..f27d296f3 100644 --- a/matrique.pro +++ b/matrique.pro @@ -1,7 +1,9 @@ QT += quick widgets multimedia CONFIG += c++14 CONFIG += object_parallel_to_source -CONFIG += qtquickcompiler + +# Enable this to use QtQuick Compiler. +#CONFIG += qtquickcompiler TARGET = matrique @@ -75,10 +77,9 @@ DISTFILES += \ ButtonDelegate.qml \ SideNav.qml \ RoomListForm.qml \ - RoomDetailForm.qml \ Room.qml \ Setting.qml \ - qml/js/md.js + qml/js/md.js \ HEADERS += \ src/controller.h \ diff --git a/qml/Login.qml b/qml/Login.qml index 811955bd6..25b6db972 100644 --- a/qml/Login.qml +++ b/qml/Login.qml @@ -57,17 +57,19 @@ Page { Pane { width: parent.width / 2 height: parent.height + padding: 64 ColumnLayout { - id: mainCol width: parent.width - TextField { - id: serverField + id: mainCol + TextField { Layout.fillWidth: true + id: serverField + leftPadding: 16 topPadding: 0 bottomPadding: 0 @@ -85,10 +87,10 @@ Page { } TextField { - id: usernameField - Layout.fillWidth: true + id: usernameField + leftPadding: 16 topPadding: 0 bottomPadding: 0 @@ -105,10 +107,10 @@ Page { } TextField { - id: passwordField - Layout.fillWidth: true + id: passwordField + leftPadding: 16 topPadding: 0 bottomPadding: 0 @@ -126,10 +128,10 @@ Page { } Button { - id: loginButton - Layout.fillWidth: true + id: loginButton + text: "LOGIN" highlighted: true diff --git a/qml/MatriqueSettings.qml b/qml/MatriqueSettings.qml index 6bcdbb560..1c8ead167 100644 --- a/qml/MatriqueSettings.qml +++ b/qml/MatriqueSettings.qml @@ -4,7 +4,6 @@ import Qt.labs.settings 1.0 Settings { property bool lazyLoad: true - property bool asyncMessageDelegate property bool richText property bool pressAndHold property bool rearrangeByActivity diff --git a/qml/Room.qml b/qml/Room.qml index 5e7217503..3c16583f4 100644 --- a/qml/Room.qml +++ b/qml/Room.qml @@ -18,28 +18,42 @@ Page { onNewMessage: if (!window.active) matriqueController.showMessage(roomName, content, icon) } - RowLayout { + Rectangle { anchors.fill: parent - spacing: 0 - RoomListForm { - id: roomListForm + color: MSettings.darkTheme ? "#323232" : "#f3f3f3" - Layout.fillHeight: true - Layout.preferredWidth: MSettings.miniMode ? 80 : page.width * 0.35 - Layout.minimumWidth: 80 - Layout.maximumWidth: 360 + RowLayout { + anchors.fill: parent - listModel: roomListModel - } + spacing: 0 - RoomForm { - id: roomForm + RoomListForm { + Layout.fillHeight: true + Layout.preferredWidth: MSettings.miniMode ? 64 : page.width * 0.35 + Layout.minimumWidth: 64 + Layout.maximumWidth: 360 - Layout.fillWidth: true - Layout.fillHeight: true + id: roomListForm - currentRoom: roomListForm.enteredRoom + listModel: roomListModel + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.fillHeight: true + + color: MSettings.darkTheme ? "#363636" : "#ececec" + } + + RoomForm { + Layout.fillWidth: true + Layout.fillHeight: true + + id: roomForm + + currentRoom: roomListForm.enteredRoom + } } } } diff --git a/qml/Setting.qml b/qml/Setting.qml index 1b739f419..2fcf0806a 100644 --- a/qml/Setting.qml +++ b/qml/Setting.qml @@ -58,57 +58,59 @@ Page { Page { id: generalForm + parent: null + Column { Switch { text: "Lazy load at initial sync" checked: MSettings.lazyLoad + onCheckedChanged: MSettings.lazyLoad = checked } - Switch { - text: "Force loading message delegates asynchronously" - checked: MSettings.asyncMessageDelegate - onCheckedChanged: MSettings.asyncMessageDelegate = checked - } + Switch { text: "Use RichText instead of StyledText" checked: MSettings.richText + onCheckedChanged: MSettings.richText = checked } + Switch { text: "Use press and hold instead of right click" checked: MSettings.pressAndHold - onCheckedChanged: MSettings.pressAndHold = checked - } - Switch { - text: "Rearrange rooms by activity" - checked: MSettings.rearrangeByActivity - onCheckedChanged: MSettings.rearrangeByActivity = checked - } - Button { - text: "Invoke GC" - highlighted: true - onClicked: gc() + onCheckedChanged: MSettings.pressAndHold = checked } } } Page { id: appearanceForm + parent: null + Column { Switch { text: "Dark theme" checked: MSettings.darkTheme + onCheckedChanged: MSettings.darkTheme = checked } Switch { text: "Mini Room List" checked: MSettings.miniMode + onCheckedChanged: MSettings.miniMode = checked } + + Switch { + text: "Rearrange rooms by activity" + checked: MSettings.rearrangeByActivity + + onCheckedChanged: MSettings.rearrangeByActivity = checked + } } } @@ -126,12 +128,8 @@ Page { source: "qrc:/asset/img/icon.png" } - Label { - text: "Matrique, an IM client for the Matrix protocol." - } - Label { - text: "Released under GNU General Public License, version 3." - } + Label { text: "Matrique, an IM client for the Matrix protocol." } + Label { text: "Released under GNU General Public License, version 3." } } } diff --git a/qml/component/AudioBubble.qml b/qml/component/AudioBubble.qml deleted file mode 100644 index e01f5aeca..000000000 --- a/qml/component/AudioBubble.qml +++ /dev/null @@ -1,50 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Controls.Material 2.2 -import QtMultimedia 5.9 -import Qt.labs.platform 1.0 - -AvatarContainer { - readonly property var downloadAndOpen: downloadable.downloadAndOpen - readonly property var saveFileAs: downloadable.saveFileAs - - property bool playOnFinished: false - - id: messageRow - - DownloadableContent { - id: downloadable - - width: downloadDelegate.width - height: downloadDelegate.height - - TextDelegate { - id: downloadDelegate - - maximumWidth: messageListView.width - highlighted: !sentByMe - timeLabelVisible: false - authorLabelVisible: false - - displayText: content.info.duration / 1000 + '"' - - MouseArea { - anchors.fill: parent - - propagateComposedEvents: true - - onClicked: { - if (downloadable.downloaded) - matriqueController.playAudio(progressInfo.localPath) - else - { - playOnFinished = true - currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_") + ".tmp") - } - } - } - } - onDownloadedChanged: downloaded && playOnFinished ? matriqueController.playAudio(progressInfo.localPath) : {} - } - -} diff --git a/qml/component/AutoImage.qml b/qml/component/AutoImage.qml index 75fbc618f..c7050de5a 100644 --- a/qml/component/AutoImage.qml +++ b/qml/component/AutoImage.qml @@ -6,22 +6,22 @@ Item { property alias sourceSize: baseImage.sourceSize.width readonly property bool loading: baseImage.status == Image.Loading - signal clicked() - id: rekt + signal clicked() width: loading ? 128 : baseImage.implicitWidth height: loading ? progressBar.height : baseImage.implicitHeight - Image { - id: baseImage - } + id: rekt + + Image { id: baseImage } ProgressBar { - id: progressBar width: parent.width visible: loading + id: progressBar + indeterminate: true } diff --git a/qml/component/AutoLabel.qml b/qml/component/AutoLabel.qml new file mode 100644 index 000000000..684f8927c --- /dev/null +++ b/qml/component/AutoLabel.qml @@ -0,0 +1,16 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import Matrique.Settings 0.1 + +Label { + property bool coloredBackground + + color: coloredBackground ? "white": Material.foreground + + wrapMode: Label.Wrap + linkColor: coloredBackground ? "white" : Material.accent + textFormat: MSettings.richText ? Text.RichText : Text.StyledText + + onLinkActivated: Qt.openUrlExternally(link) +} diff --git a/qml/component/AutoMouseArea.qml b/qml/component/AutoMouseArea.qml index 270ee255c..7d0cc0e26 100644 --- a/qml/component/AutoMouseArea.qml +++ b/qml/component/AutoMouseArea.qml @@ -6,6 +6,7 @@ MouseArea { signal secondaryClicked() acceptedButtons: MSettings.pressAndHold ? Qt.LeftButton : (Qt.LeftButton | Qt.RightButton) + onClicked: mouse.button == Qt.RightButton ? secondaryClicked() : primaryClicked() onPressAndHold: MSettings.pressAndHold ? secondaryClicked() : {} } diff --git a/qml/component/AvatarContainer.qml b/qml/component/AvatarContainer.qml deleted file mode 100644 index 64c4599be..000000000 --- a/qml/component/AvatarContainer.qml +++ /dev/null @@ -1,26 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 - -Row { - readonly property bool avatarVisible: !(sentByMe || (aboveAuthor === author && section === aboveSection)) - - spacing: 6 - - ImageStatus { - id: avatar - - width: height - height: 40 - round: false - visible: avatarVisible - source: author.avatarUrl != "" ? "image://mxc/" + author.avatarUrl : null - displayText: author.displayName - } - - Rectangle { - width: height - height: 40 - color: "transparent" - visible: !avatarVisible - } -} diff --git a/qml/component/DownloadableContent.qml b/qml/component/DownloadableContent.qml index a0f10720b..e2bbaea95 100644 --- a/qml/component/DownloadableContent.qml +++ b/qml/component/DownloadableContent.qml @@ -11,20 +11,18 @@ Item { z: -2 height: parent.height width: progressInfo.active && !progressInfo.completed ? progressInfo.progress / progressInfo.total * parent.width : 0 + color: Material.accent opacity: 0.4 } - onDownloadedChanged: downloaded && openOnFinished ? openSavedFile() : {} + onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile() - function saveFileAs() { - currentRoom.saveFileAs(eventId) - } + function saveFileAs() { currentRoom.saveFileAs(eventId) } function downloadAndOpen() { - if (downloaded) - openSavedFile() + if (downloaded) openSavedFile() else { openOnFinished = true @@ -34,10 +32,7 @@ Item { function openSavedFile() { - if (Qt.openUrlExternally(progressInfo.localPath)) - return; - - if (Qt.openUrlExternally(progressInfo.localDir)) - return; + if (Qt.openUrlExternally(progressInfo.localPath)) return; + if (Qt.openUrlExternally(progressInfo.localDir)) return; } } diff --git a/qml/component/EmojiPicker.qml b/qml/component/EmojiPicker.qml index 78605392f..f58189a87 100644 --- a/qml/component/EmojiPicker.qml +++ b/qml/component/EmojiPicker.qml @@ -52,6 +52,7 @@ Popup { Rectangle { Layout.fillWidth: true Layout.preferredHeight: 2 + color: Material.accent } diff --git a/qml/component/FileBubble.qml b/qml/component/FileBubble.qml deleted file mode 100644 index 4ff77bd21..000000000 --- a/qml/component/FileBubble.qml +++ /dev/null @@ -1,28 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Controls.Material 2.2 - -AvatarContainer { - readonly property var downloadAndOpen: downloadable.downloadAndOpen - readonly property var saveFileAs: downloadable.saveFileAs - - id: messageRow - - DownloadableContent { - id: downloadable - - width: downloadDelegate.width - height: downloadDelegate.height - - TextDelegate { - id: downloadDelegate - - maximumWidth: messageListView.width - highlighted: !sentByMe - timeLabelVisible: false - authorLabelVisible: false - - displayText: "File: " + content.body - } - } -} diff --git a/qml/component/GenericBubble.qml b/qml/component/GenericBubble.qml new file mode 100644 index 000000000..3c754be2d --- /dev/null +++ b/qml/component/GenericBubble.qml @@ -0,0 +1,22 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.2 +import Matrique.Settings 0.1 + +Control { + property bool highlighted: false + property bool colored: false + + readonly property bool darkBackground: highlighted ? true : MSettings.darkTheme + readonly property color backgroundColor: MSettings.darkTheme ? "#242424" : "lightgrey" + + padding: 12 + + AutoMouseArea { + anchors.fill: parent + + onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/MessageContextMenu.qml").createObject(this) + } + + background: Rectangle { color: colored ? Material.accent : highlighted ? Material.primary : backgroundColor } +} diff --git a/qml/component/ImageBubble.qml b/qml/component/ImageBubble.qml deleted file mode 100644 index 887b37e31..000000000 --- a/qml/component/ImageBubble.qml +++ /dev/null @@ -1,34 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Controls.Material 2.2 - -AvatarContainer { - readonly property var downloadAndOpen: downloadable.downloadAndOpen - readonly property var saveFileAs: downloadable.saveFileAs - - Rectangle { - id: messageRect - - width: messageImage.width + 24 - height: messageImage.height + 24 - - color: sentByMe ? background : Material.accent - - DownloadableContent { - id: downloadable - - width: messageImage.width - height: messageImage.height - anchors.centerIn: parent - - AutoImage { - id: messageImage - z: -4 - sourceSize: 128 - source: "image://mxc/" + (content.thumbnail_url ? content.thumbnail_url : content.url) - - onClicked: downloadAndOpen() - } - } - } -} diff --git a/qml/component/ImageStatus.qml b/qml/component/ImageStatus.qml index 137bb0645..21447838d 100644 --- a/qml/component/ImageStatus.qml +++ b/qml/component/ImageStatus.qml @@ -13,9 +13,11 @@ Item { id: item Image { - id: avatar width: item.width height: item.width + + id: avatar + visible: showImage source: item.source @@ -40,6 +42,7 @@ Item { Label { anchors.fill: parent + color: "white" visible: showInitial text: showInitial ? getInitials(displayText)[0] : "" diff --git a/qml/component/MaterialIcon.qml b/qml/component/MaterialIcon.qml index bfb4ca6b2..d1b2f0ed6 100644 --- a/qml/component/MaterialIcon.qml +++ b/qml/component/MaterialIcon.qml @@ -10,8 +10,9 @@ Item { id: item Text { - id: iconText anchors.fill: parent + + id: iconText font.pointSize: 16 font.family: materialFont.name color: item.color diff --git a/qml/component/MessageBubble.qml b/qml/component/MessageBubble.qml deleted file mode 100644 index 7e87caef1..000000000 --- a/qml/component/MessageBubble.qml +++ /dev/null @@ -1,20 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Layouts 1.3 -import QtQuick.Controls.Material 2.2 - -AvatarContainer { - readonly property bool isNotice: eventType === "notice" - - id: messageRow - - TextDelegate { - maximumWidth: messageListView.width - (!sentByMe ? 40 + messageRow.spacing : 0) - flat: isNotice - highlighted: !sentByMe - timeLabelVisible: Math.abs(time - aboveTime) > 600000 || index == 0 - authorLabelVisible: messageRow.avatarVisible - - displayText: display - } -} diff --git a/qml/component/MessageDelegate.qml b/qml/component/MessageDelegate.qml index e007cbf33..745212d53 100644 --- a/qml/component/MessageDelegate.qml +++ b/qml/component/MessageDelegate.qml @@ -1,53 +1,186 @@ import QtQuick 2.9 import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 import Matrique 0.1 import Matrique.Settings 0.1 -Item { - readonly property bool hidden: marks === EventStatus.Redacted || marks === EventStatus.Hidden - readonly property color background: MSettings.darkTheme ? "#242424" : "lightgrey" +RowLayout { + readonly property bool avatarVisible: !(sentByMe || (aboveAuthor === author && section === aboveSection)) + readonly property bool highlighted: !sentByMe readonly property bool sentByMe: author === currentRoom.localUser - readonly property bool isState: eventType === "state" || eventType === "emote" + readonly property bool isText: eventType === "notice" || eventType === "message" - id: messageDelegate + signal saveFileAs() + signal openExternally() z: -5 - width: delegateLoader.width - height: delegateLoader.height - anchors.right: !isState && sentByMe ? parent.right : undefined - anchors.horizontalCenter: isState ? parent.horizontalCenter : undefined + id: messageRow - AutoMouseArea { - anchors.fill: parent + Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/MessageContextMenu.qml").createObject(this) + spacing: 6 + + ImageStatus { + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignTop + + round: false + visible: avatarVisible + source: author.avatarUrl != "" ? "image://mxc/" + author.avatarUrl : null + displayText: author.displayName } - Loader { - id: delegateLoader + Rectangle { + Layout.preferredWidth: 40 + Layout.preferredHeight: 40 + Layout.alignment: Qt.AlignTop - asynchronous: MSettings.asyncMessageDelegate + color: "transparent" + visible: !(sentByMe || avatarVisible) + } - source: { - if (eventType == "redaction" || hidden) return "" - switch (eventType) { - case "state": - case "emote": - return "StateBubble.qml" - case "message": - case "notice": - return "MessageBubble.qml" - case "image": - return "ImageBubble.qml" - case "audio": - return "AudioBubble.qml" - case "video": - case "file": - return "FileBubble.qml" + GenericBubble { + Layout.maximumWidth: messageListView.width - (!sentByMe ? 40 + messageRow.spacing : 0) + + id: genericBubble + + highlighted: !sentByMe + colored: highlighted && eventType === "notice" + + contentItem: ColumnLayout { + id: messageColumn + + spacing: 0 + + AutoLabel { + visible: messageRow.avatarVisible + text: author.displayName + Material.foreground: Material.accent + coloredBackground: highlighted + font.bold: true + } + + AutoLabel { + Layout.fillWidth: true + + text: display + visible: isText + coloredBackground: highlighted + } + + Loader { + sourceComponent: { + switch (eventType) { + case "image": + return imageComponent + case "file": + return fileComponent + case "audio": + return audioComponent + } + } + + active: eventType === "image" || eventType === "file" || eventType === "audio" + } + + AutoLabel { + Layout.alignment: Qt.AlignRight + visible: Math.abs(time - aboveTime) > 600000 || index == 0 + text: Qt.formatTime(time, "hh:mm") + coloredBackground: highlighted + Material.foreground: "grey" + font.pointSize: 8 + } + } + + Component { + id: imageComponent + + DownloadableContent { + width: messageImage.width + height: messageImage.height + + id: downloadable + + AutoImage { + z: -4 + + id: messageImage + + sourceSize: 128 + source: "image://mxc/" + (content.thumbnail_url ? content.thumbnail_url : content.url) + + onClicked: downloadAndOpen() + } + + Component.onCompleted: { + messageRow.saveFileAs.connect(saveFileAs) + messageRow.openExternally.connect(downloadAndOpen) + } + } + } + + Component { + id: fileComponent + + AutoLabel { + Layout.fillWidth: true + + id: downloadDelegate + + text: "File: " + content.body + coloredBackground: highlighted + + background: DownloadableContent { + id: downloadable + + Component.onCompleted: { + messageRow.saveFileAs.connect(saveFileAs) + messageRow.openExternally.connect(downloadAndOpen) + } + } + } + } + + Component { + id: audioComponent + + AutoLabel { + id: downloadDelegate + + text: content.info.duration / 1000 + '"' + coloredBackground: highlighted + + MouseArea { + anchors.fill: parent + + propagateComposedEvents: true + + onClicked: { + if (downloadable.downloaded) + matriqueController.playAudio(progressInfo.localPath) + else + { + playOnFinished = true + currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_") + ".tmp") + } + } + } + + background: DownloadableContent { + id: downloadable + + onDownloadedChanged: downloaded && playOnFinished ? matriqueController.playAudio(progressInfo.localPath) : {} + + Component.onCompleted: { + messageRow.saveFileAs.connect(saveFileAs) + messageRow.openExternally.connect(downloadAndOpen) + } + } } - return "" } } } diff --git a/qml/component/SideNavButton.qml b/qml/component/SideNavButton.qml index e57d1bce9..8c9bf2749 100644 --- a/qml/component/SideNavButton.qml +++ b/qml/component/SideNavButton.qml @@ -3,41 +3,29 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.3 import QtQuick.Controls.Material 2.2 -Item { +ItemDelegate { property var page - property alias contentItem: buttonDelegate.contentItem - signal clicked - - id: sideNavButton - - Layout.fillWidth: true - Layout.preferredHeight: width + readonly property bool selected: stackView.currentItem === page Rectangle { - width: stackView.currentItem === page ? parent.width : 0 + width: selected ? 4 : 0 height: parent.height - anchors.bottom: buttonDelegate.bottom - color: Qt.lighter(Material.accent) + + color: Material.accent Behavior on width { PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 } } } - ItemDelegate { - id: buttonDelegate - anchors.fill: parent - - onClicked: { - if(page && stackView.currentItem !== page) { - if(stackView.depth === 1) { - stackView.replace(page) - } else { - stackView.clear() - stackView.push(page) - } + onClicked: { + if(page && stackView.currentItem !== page) { + if(stackView.depth === 1) { + stackView.replace(page) + } else { + stackView.clear() + stackView.push(page) } - sideNavButton.clicked() } } } diff --git a/qml/component/StateBubble.qml b/qml/component/StateBubble.qml deleted file mode 100644 index 693c6770c..000000000 --- a/qml/component/StateBubble.qml +++ /dev/null @@ -1,12 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Controls.Material 2.2 - -TextDelegate { - maximumWidth: messageListView.width - highlighted: eventType === "emote" - timeLabelVisible: false - authorLabelVisible: false - - displayText: "" + author.displayName + " " + display -} diff --git a/qml/component/StateDelegate.qml b/qml/component/StateDelegate.qml new file mode 100644 index 000000000..376763fed --- /dev/null +++ b/qml/component/StateDelegate.qml @@ -0,0 +1,23 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 +import Matrique.Settings 0.1 + +Label { + Layout.alignment: Qt.AlignHCenter + + text: "" + author.displayName + " " + display + color: "white" + + padding: 8 + + wrapMode: Label.Wrap + linkColor: "white" + textFormat: MSettings.richText ? Text.RichText : Text.StyledText + onLinkActivated: Qt.openUrlExternally(link) + + background: Rectangle { + color: MSettings.darkTheme ? "#484848" : "grey" + } +} diff --git a/qml/component/TextDelegate.qml b/qml/component/TextDelegate.qml deleted file mode 100644 index f0077324b..000000000 --- a/qml/component/TextDelegate.qml +++ /dev/null @@ -1,61 +0,0 @@ -import QtQuick 2.9 -import QtQuick.Controls 2.2 -import QtQuick.Controls.Material 2.2 -import QtQuick.Layouts 1.3 -import Matrique.Settings 0.1 - -Rectangle { - property bool flat: false - property bool highlighted: false - property string displayText: "" - property alias timeLabelVisible: timeText.visible - property alias authorLabelVisible: authorText.visible - - property int maximumWidth - - readonly property bool darkBackground: highlighted && !flat - - id: messageRect - - width: Math.min(Math.max(messageText.implicitWidth, (timeText.visible ? timeText.implicitWidth : 0), (authorLabelVisible ? authorText.implicitWidth : 0)) + 24, maximumWidth) - height: (authorText.visible ? authorText.implicitHeight : 0) + messageText.implicitHeight + (timeText.visible ? timeText.implicitHeight : 0) + 24 - - color: flat ? "transparent" : highlighted ? Material.accent : background - border.color: Material.accent - border.width: flat ? 2 : 0 - - ColumnLayout { - id: messageColumn - - anchors.fill: parent - anchors.margins: 12 - spacing: 0 - - Label { - id: authorText - text: author.displayName - color: darkBackground ? "white" : Material.accent - font.bold: true - } - - Label { - id: messageText - Layout.maximumWidth: parent.width - text: displayText - color: darkBackground ? "white": Material.foreground - - wrapMode: Label.Wrap - linkColor: darkBackground ? "white" : Material.accent - textFormat: MSettings.richText ? Text.RichText : Text.StyledText - onLinkActivated: Qt.openUrlExternally(link) - } - - Label { - id: timeText - Layout.alignment: Qt.AlignRight - text: Qt.formatTime(time, "hh:mm") - color: darkBackground ? "white" : "grey" - font.pointSize: 8 - } - } -} diff --git a/qml/form/RoomForm.qml b/qml/form/RoomForm.qml index 6e9335331..4ec648d22 100644 --- a/qml/form/RoomForm.qml +++ b/qml/form/RoomForm.qml @@ -22,11 +22,11 @@ Item { } Drawer { - id: roomDrawer - width: Math.min(item.width * 0.7, 480) height: item.height + id: roomDrawer + edge: Qt.RightEdge interactive: false @@ -39,7 +39,6 @@ Item { ColumnLayout { anchors.fill: parent anchors.margins: 32 - spacing: 16 ImageStatus { Layout.preferredWidth: 64 @@ -52,12 +51,14 @@ Item { Label { Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter text: currentRoom && currentRoom.id ? currentRoom.id : "" } Label { Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter text: currentRoom && currentRoom.canonicalAlias ? currentRoom.canonicalAlias : "No Canonical Alias" } @@ -66,8 +67,9 @@ Item { Layout.fillWidth: true TextField { - id: roomNameField Layout.fillWidth: true + + id: roomNameField text: currentRoom && currentRoom.name ? currentRoom.name : "" } @@ -85,9 +87,10 @@ Item { Layout.fillWidth: true TextField { + Layout.fillWidth: true + id: roomTopicField - Layout.fillWidth: true text: currentRoom && currentRoom.topic ? currentRoom.topic : "" } @@ -116,7 +119,7 @@ Item { RowLayout { anchors.fill: parent anchors.margins: 8 - spacing: 16 + spacing: 12 ImageStatus { Layout.preferredWidth: height @@ -159,9 +162,9 @@ Item { Rectangle { Layout.fillWidth: true - Layout.preferredHeight: 80 + Layout.preferredHeight: 64 - color: MSettings.darkTheme ? "#242424" : "#eaeaea" + color: Material.accent ItemDelegate { anchors.fill: parent @@ -170,13 +173,14 @@ Item { RowLayout { anchors.fill: parent - anchors.margins: 16 + anchors.margins: 12 - spacing: 16 + spacing: 12 ImageStatus { Layout.preferredWidth: height Layout.fillHeight: true + source: currentRoom && currentRoom.avatarUrl != "" ? "image://mxc/" + currentRoom.avatarUrl : null displayText: currentRoom ? currentRoom.displayName : "" } @@ -186,14 +190,15 @@ Item { Layout.fillHeight: true Layout.alignment: Qt.AlignHCenter - visible: parent.width > 80 + visible: parent.width > 64 Label { Layout.fillWidth: true Layout.fillHeight: true text: currentRoom ? currentRoom.displayName : "" - font.pointSize: 16 + color: "white" + font.pointSize: 12 elide: Text.ElideRight wrapMode: Text.NoWrap } @@ -203,6 +208,7 @@ Item { Layout.fillHeight: true text: currentRoom ? currentRoom.topic : "" + color: "white" elide: Text.ElideRight wrapMode: Text.NoWrap } @@ -221,80 +227,79 @@ Item { spacing: 0 ListView { - id: messageListView - Layout.fillWidth: true Layout.fillHeight: true + + id: messageListView + + clip: true displayMarginBeginning: 40 displayMarginEnd: 40 verticalLayoutDirection: ListView.BottomToTop spacing: 8 boundsBehavior: Flickable.DragOverBounds + maximumFlickVelocity: 2048 model: MessageEventModel { id: messageEventModel room: currentRoom } - delegate: Column { + delegate: ColumnLayout { readonly property bool hidden: marks === EventStatus.Redacted || marks === EventStatus.Hidden width: parent.width height: hidden ? -8 : undefined + + id: delegateColumn + + clip: true spacing: 8 - RowLayout { - width: parent.width * 0.8 + Label { + Layout.alignment: Qt.AlignHCenter + visible: section !== aboveSection && !hidden - anchors.horizontalCenter: parent.horizontalCenter - spacing: 8 - Rectangle { - Layout.fillWidth: true - height:2 - color: Material.accent - } + text: section + color: "white" + verticalAlignment: Text.AlignVCenter + leftPadding: 8 + rightPadding: 8 + topPadding: 4 + bottomPadding: 4 - Label { - text: section - color: Material.accent - verticalAlignment: Text.AlignVCenter - } - - Rectangle { - Layout.fillWidth: true - height:2 - color: Material.accent + background: Rectangle { + color: MSettings.darkTheme ? "#484848" : "grey" } } - RowLayout { - width: parent.width * 0.8 + Label { + Layout.alignment: Qt.AlignHCenter + visible: readMarker === true && index !== 0 - anchors.horizontalCenter: parent.horizontalCenter - spacing: 8 - Rectangle { - Layout.fillWidth: true - height:2 - color: Material.accent - } + text: "And Now" + color: "white" + verticalAlignment: Text.AlignVCenter + leftPadding: 8 + rightPadding: 8 + topPadding: 4 + bottomPadding: 4 - Label { - text: "And Now" - color: Material.accent - verticalAlignment: Text.AlignVCenter - } - - Rectangle { - Layout.fillWidth: true - height:2 - color: Material.accent - } + background: Rectangle { color: MSettings.darkTheme ? "#484848" : "grey" } } - MessageDelegate {} + MessageDelegate { + visible: eventType === "notice" || eventType === "message" || eventType === "image" || eventType === "video" || eventType === "audio" || eventType === "file" + } + + StateDelegate { + Layout.maximumWidth: messageListView.width * 0.8 + + visible: eventType === "emote" || eventType === "state" + } } ScrollBar.vertical: messageListViewScrollBar @@ -303,9 +308,11 @@ Item { onAtYEndChanged: atYEnd && currentRoom ? currentRoom.markAllMessagesAsRead() : {} RoundButton { - id: goTopFab - width: height + width: 64 height: 64 + + id: goTopFab + visible: !parent.atYEnd anchors.right: parent.right @@ -313,12 +320,12 @@ Item { contentItem: MaterialIcon { anchors.fill: parent + icon: "\ue313" color: "white" } - opacity: pressed ? 1 : hovered ? 0.7 : 0.4 - Material.background: Qt.lighter(Material.accent) + Material.background: Material.accent onClicked: parent.positionViewAtBeginning() @@ -327,159 +334,158 @@ Item { } ScrollBar { - id: messageListViewScrollBar - Layout.preferredWidth: 16 Layout.fillHeight: true + + id: messageListViewScrollBar } } - Pane { - padding: 16 - + RowLayout { Layout.fillWidth: true - Layout.preferredHeight: 80 + Layout.preferredHeight: 48 + Layout.margins: 16 - Timer { - id: timeoutTimer + spacing: 0 - repeat: false - interval: 2000 - onTriggered: { - repeatTimer.stop() - currentRoom.sendTypingNotification(false) + ItemDelegate { + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 + + contentItem: MaterialIcon { icon: "\ue226" } + + onClicked: currentRoom.chooseAndUploadFile() + } + + TextField { + property real progress: 0 + + Layout.fillWidth: true + Layout.preferredHeight: 48 + + id: inputField + + placeholderText: "Send a Message" + leftPadding: 16 + topPadding: 0 + bottomPadding: 0 + selectByMouse: true + + text: currentRoom ? currentRoom.cachedInput : "" + + onTextChanged: { + timeoutTimer.restart() + repeatTimer.start() + currentRoom.cachedInput = text + } + + Keys.onReturnPressed: { + if (inputField.text) { + inputField.postMessage(inputField.text) + inputField.text = "" + } + } + + background: Rectangle { + color: MSettings.darkTheme ? "#282828" : "#eaeaea" + } + + ToolTip.visible: currentRoom && currentRoom.hasUsersTyping + ToolTip.text: currentRoom ? currentRoom.usersTyping : "" + + Timer { + id: timeoutTimer + + repeat: false + interval: 2000 + onTriggered: { + repeatTimer.stop() + currentRoom.sendTypingNotification(false) + } + } + + Timer { + id: repeatTimer + + repeat: true + interval: 5000 + triggeredOnStart: true + onTriggered: currentRoom.sendTypingNotification(true) + } + + function postMessage(text) { + if (text.trim().length === 0) { return } + if(!currentRoom) { return } + + var PREFIX_ME = '/me ' + var PREFIX_NOTICE = '/notice ' + var PREFIX_RAINBOW = '/rainbow ' + var PREFIX_HTML = '/html ' + var PREFIX_MARKDOWN = '/md ' + + if (text.indexOf(PREFIX_ME) === 0) { + text = text.substr(PREFIX_ME.length) + currentRoom.postMessage(text, RoomMessageEvent.Emote) + return + } + if (text.indexOf(PREFIX_NOTICE) === 0) { + text = text.substr(PREFIX_NOTICE.length) + currentRoom.postMessage(text, RoomMessageEvent.Notice) + return + } + if (text.indexOf(PREFIX_RAINBOW) === 0) { + text = text.substr(PREFIX_RAINBOW.length) + + var parsedText = "" + var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] + for (var i = 0; i < text.length; i++) { + parsedText = parsedText + "" + text.charAt(i) + "" + } + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_HTML) === 0) { + text = text.substr(PREFIX_HTML.length) + var re = new RegExp("<.*?>") + var plainText = text.replace(re, "") + currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) + return + } + if (text.indexOf(PREFIX_MARKDOWN) === 0) { + text = text.substr(PREFIX_MARKDOWN.length) + var parsedText = Markdown.markdown_parser(text) + currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) + return + } + + currentRoom.postPlainText(text) } } - Timer { - id: repeatTimer + ItemDelegate { + Layout.preferredWidth: 48 + Layout.preferredHeight: 48 - repeat: true - interval: 5000 - triggeredOnStart: true - onTriggered: currentRoom.sendTypingNotification(true) - } + id: emojiButton - RowLayout { - anchors.fill: parent - spacing: 0 + contentItem: MaterialIcon { icon: "\ue24e" } - ItemDelegate { - Layout.preferredWidth: height - Layout.fillHeight: true + background: Rectangle { color: MSettings.darkTheme ? "#282828" : "#eaeaea" } - contentItem: MaterialIcon { icon: "\ue226" } + onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.open() - onClicked: currentRoom.chooseAndUploadFile() - } + EmojiPicker { + x: window.width - 370 + y: window.height - 440 - TextField { - property real progress: 0 + width: 360 + height: 360 - id: inputField - Layout.fillWidth: true - Layout.fillHeight: true - placeholderText: "Send a Message" - leftPadding: 16 - topPadding: 0 - bottomPadding: 0 - selectByMouse: true + id: emojiPicker - text: currentRoom ? currentRoom.cachedInput : "" - onTextChanged: { - timeoutTimer.restart() - repeatTimer.start() - currentRoom.cachedInput = text - } + parent: ApplicationWindow.overlay - Keys.onReturnPressed: { - if (inputField.text) { - inputField.postMessage(inputField.text) - inputField.text = "" - } - } - - background: Rectangle { - color: MSettings.darkTheme ? "#242424" : "#eaeaea" - } - - ToolTip.visible: currentRoom && currentRoom.hasUsersTyping - ToolTip.text: currentRoom ? currentRoom.usersTyping : "" - - function postMessage(text) { - if (text.trim().length === 0) { return } - if(!currentRoom) { return } - - var PREFIX_ME = '/me ' - var PREFIX_NOTICE = '/notice ' - var PREFIX_RAINBOW = '/rainbow ' - var PREFIX_HTML = '/html ' - var PREFIX_MARKDOWN = '/md ' - - if (text.indexOf(PREFIX_ME) === 0) { - text = text.substr(PREFIX_ME.length) - currentRoom.postMessage(text, RoomMessageEvent.Emote) - return - } - if (text.indexOf(PREFIX_NOTICE) === 0) { - text = text.substr(PREFIX_NOTICE.length) - currentRoom.postMessage(text, RoomMessageEvent.Notice) - return - } - if (text.indexOf(PREFIX_RAINBOW) === 0) { - text = text.substr(PREFIX_RAINBOW.length) - - var parsedText = "" - var rainbowColor = ["#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00", "#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff", "#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"] - for (var i = 0; i < text.length; i++) { - parsedText = parsedText + "" + text.charAt(i) + "" - } - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_HTML) === 0) { - text = text.substr(PREFIX_HTML.length) - var re = new RegExp("<.*?>") - var plainText = text.replace(re, "") - currentRoom.postHtmlMessage(plainText, text, RoomMessageEvent.Text) - return - } - if (text.indexOf(PREFIX_MARKDOWN) === 0) { - text = text.substr(PREFIX_MARKDOWN.length) - var parsedText = Markdown.markdown_parser(text) - currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text) - return - } - - currentRoom.postPlainText(text) - } - } - - ItemDelegate { - id: emojiButton - - Layout.preferredWidth: height - Layout.fillHeight: true - - contentItem: MaterialIcon { icon: "\ue24e" } - - background: Rectangle { color: MSettings.darkTheme ? "#242424" : "#eaeaea" } - - onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.open() - - EmojiPicker { - id: emojiPicker - - parent: ApplicationWindow.overlay - - x: window.width - 370 - y: window.height - 440 - - width: 360 - height: 360 - - textArea: inputField - } + textArea: inputField } } } diff --git a/qml/form/RoomListForm.qml b/qml/form/RoomListForm.qml index bab3097ab..8448898ea 100644 --- a/qml/form/RoomListForm.qml +++ b/qml/form/RoomListForm.qml @@ -14,215 +14,201 @@ Item { property alias listModel: roomListProxyModel.sourceModel property var enteredRoom: null + Label { + z: 10 + + text: MSettings.miniMode ? "Empty" : "Here? No, not here." + anchors.centerIn: parent + visible: listView.count === 0 + } + ColumnLayout { anchors.fill: parent spacing: 0 - Rectangle { - z: 10 + TextField { Layout.fillWidth: true - Layout.preferredHeight: 80 - color: Qt.tint(Material.accent, "#20FFFFFF") + Layout.preferredHeight: 40 + Layout.margins: 12 - TextField { - id: searchField - width: parent.width - 18 - height: 36 - color: "white" - leftPadding: MSettings.miniMode ? 4 : 32 - topPadding: 0 - bottomPadding: 0 - anchors.centerIn: parent + id: searchField - background: Row { - visible: !parent.text + leftPadding: MSettings.miniMode ? 4 : 32 + topPadding: 0 + bottomPadding: 0 + placeholderText: "Search..." - MaterialIcon { - icon: "\ue8b6" - color: "white" + background: Rectangle { color: MSettings.darkTheme ? "#282828" : "#fafafa" } - width: MSettings.miniMode ? parent.width : parent.height - height: parent.height - } - - Label { - height: parent.height - visible: !MSettings.miniMode - text: "Search" - color: "white" - font.pointSize: 12 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - - Shortcut { - sequence: StandardKey.Find - onActivated: searchField.forceActiveFocus() - } + Shortcut { + sequence: StandardKey.Find + onActivated: searchField.forceActiveFocus() } } - Rectangle { + SortFilterProxyModel { + id: roomListProxyModel + + filters: RegExpFilter { + roleName: "name" + pattern: searchField.text + caseSensitivity: Qt.CaseInsensitive + } + proxyRoles: ExpressionRole { + name: "display" + expression: { + switch (category) { + case 1: return "Invited" + case 2: return "Favorites" + case 3: return "Rooms" + case 4: return "People" + case 5: return "Low Priorities" + } + } + } + + sorters: [ + RoleSorter { roleName: "category" }, + RoleSorter { + enabled: MSettings.rearrangeByActivity + roleName: "unreadCount" + sortOrder: Qt.DescendingOrder + }, + StringSorter { roleName: "name" } + ] + } + + ListView { Layout.fillWidth: true Layout.fillHeight: true - color: MSettings.darkTheme ? "#242424" : "#eaeaea" + id: listView - Label { - z: 10 - text: MSettings.miniMode ? "Empty" : "Here? No, not here." - anchors.centerIn: parent - visible: listView.count === 0 - } + spacing: 1 + clip: true - SortFilterProxyModel { - id: roomListProxyModel - filters: RegExpFilter { - roleName: "name" - pattern: searchField.text - caseSensitivity: Qt.CaseInsensitive - } - proxyRoles: ExpressionRole { - name: "display" - expression: { - switch (category) { - case 1: return "Invited" - case 2: return "Favorites" - case 3: return "Rooms" - case 4: return "People" - case 5: return "Low Priorities" - } - } + model: roomListProxyModel + + currentIndex: -1 + + highlightFollowsCurrentItem: true + + highlightMoveDuration: 200 + highlightResizeDuration: 0 + + boundsBehavior: Flickable.DragOverBounds + + ScrollBar.vertical: ScrollBar {} + + delegate: Rectangle { + readonly property bool highlighted: currentRoom === enteredRoom + + width: parent.width + height: 64 + + color: MSettings.darkTheme ? "#282828" : "#fafafa" + + AutoMouseArea { + anchors.fill: parent + + hoverEnabled: MSettings.miniMode + + onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/RoomContextMenu.qml").createObject(this) + onPrimaryClicked: category === RoomType.Invited ? inviteDialog.open() : enteredRoom = currentRoom + + ToolTip.visible: MSettings.miniMode && containsMouse + ToolTip.text: name } - sorters: [ - RoleSorter { roleName: "category" }, - RoleSorter { - enabled: MSettings.rearrangeByActivity - roleName: "unreadCount" - sortOrder: Qt.DescendingOrder - }, - StringSorter { roleName: "name" } - ] - } + Rectangle { + anchors.fill: parent - ListView { - id: listView - anchors.fill: parent + visible: highlighted + color: Material.accent + opacity: 0.1 + } - model: roomListProxyModel + Rectangle { + width: 4 + height: parent.height - currentIndex: -1 + color: Material.accent + visible: unreadCount > 0 || highlighted + } - boundsBehavior: Flickable.DragOverBounds + RowLayout { + anchors.fill: parent + anchors.margins: 12 - ScrollBar.vertical: ScrollBar { id: scrollBar } + spacing: 12 - delegate: Rectangle { - readonly property bool highlighted: currentRoom === enteredRoom + ImageStatus { + Layout.preferredWidth: height + Layout.fillHeight: true - id: swipeDelegate - width: parent.width - height: 80 - - color: highlighted ? Material.background : "transparent" - - AutoMouseArea { - anchors.fill: parent - - hoverEnabled: MSettings.miniMode - - onSecondaryClicked: Qt.createComponent("qrc:/qml/menu/RoomContextMenu.qml").createObject(this) - onPrimaryClicked: category === RoomType.Invited ? inviteDialog.open() : enteredRoom = currentRoom - - ToolTip.visible: MSettings.miniMode && containsMouse - ToolTip.text: name + source: avatar ? "image://mxc/" + avatar : "" + displayText: name } - Rectangle { - width: 4 - height: parent.height - color: Qt.tint(Material.accent, "#20FFFFFF") - visible: unreadCount > 0 || highlighted - } + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter - RowLayout { - anchors.fill: parent - anchors.margins: 16 - spacing: 16 + visible: parent.width > 64 - ImageStatus { - Layout.preferredWidth: height - Layout.fillHeight: true - - source: avatar ? "image://mxc/" + avatar : "" - displayText: name - } - - ColumnLayout { + Label { Layout.fillWidth: true Layout.fillHeight: true - Layout.alignment: Qt.AlignHCenter - visible: parent.width > 80 + text: name || "No Name" + font.pointSize: 12 + elide: Text.ElideRight + wrapMode: Text.NoWrap + } - Label { - Layout.fillWidth: true - Layout.fillHeight: true + Label { + Layout.fillWidth: true + Layout.fillHeight: true - text: name || "No Name" - font.pointSize: 16 - elide: Text.ElideRight - wrapMode: Text.NoWrap - } - - Label { - Layout.fillWidth: true - Layout.fillHeight: true - - text: lastEvent || topic - elide: Text.ElideRight - wrapMode: Text.NoWrap - } + text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm,""); + elide: Text.ElideRight + wrapMode: Text.NoWrap } } } + } - section.property: "display" - section.criteria: ViewSection.FullString - section.delegate: Label { - width: parent.width - height: 24 - text: section - color: "grey" - leftPadding: MSettings.miniMode ? undefined : 16 - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - horizontalAlignment: MSettings.miniMode ? Text.AlignHCenter : undefined - background: Rectangle { - anchors.fill: parent - color: MSettings.darkTheme ? "#363636" : "#dbdbdb" - } - } + section.property: "display" + section.criteria: ViewSection.FullString + section.delegate: Label { + width: parent.width + height: 24 - Dialog { - id: inviteDialog - parent: ApplicationWindow.overlay + text: section + color: "grey" + leftPadding: MSettings.miniMode ? undefined : 16 + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + horizontalAlignment: MSettings.miniMode ? Text.AlignHCenter : undefined + } - x: (window.width - width) / 2 - y: (window.height - height) / 2 - width: 360 + Dialog { + id: inviteDialog + parent: ApplicationWindow.overlay - title: "Action Required" - modal: true - standardButtons: Dialog.Ok | Dialog.Cancel + x: (window.width - width) / 2 + y: (window.height - height) / 2 + width: 360 - contentItem: Label { text: "Accept this invitation?" } + title: "Action Required" + modal: true + standardButtons: Dialog.Ok | Dialog.Cancel - onAccepted: currentRoom.acceptInvitation() - onRejected: currentRoom.forget() - } + contentItem: Label { text: "Accept this invitation?" } + + onAccepted: currentRoom.acceptInvitation() + onRejected: currentRoom.forget() } } } diff --git a/qml/main.qml b/qml/main.qml index 83d917d98..a0a3d0a2c 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -13,12 +13,14 @@ import "form" ApplicationWindow { readonly property var connection: matriqueController.connection - id: window - visible: true width: 960 height: 640 minimumWidth: 800 minimumHeight: 480 + + id: window + + visible: true title: qsTr("Matrique") Material.theme: MSettings.darkTheme ? Material.Dark : Material.Light @@ -40,10 +42,11 @@ ApplicationWindow { Popup { property bool busy: matriqueController.busy - id: busyPopup - x: (window.width - width) / 2 y: (window.height - height) / 2 + + id: busyPopup + modal: true focus: true @@ -65,7 +68,7 @@ ApplicationWindow { parent: null - connection: matriqueController.isLogin ? window.connection : null + connection: window.connection } Setting { @@ -81,20 +84,24 @@ ApplicationWindow { spacing: 0 Rectangle { - id: sideNav - Layout.preferredWidth: 80 + Layout.preferredWidth: 64 Layout.fillHeight: true - color: Material.accent + + id: sideNav + + color: Material.primary ColumnLayout { anchors.fill: parent spacing: 0 SideNavButton { - id: statusNavButton - contentItem: ImageStatus { + Layout.fillWidth: true + Layout.preferredHeight: width + + ImageStatus { anchors.fill: parent - anchors.margins: 15 + anchors.margins: 12 source: matriqueController.isLogin ? connection.localUser && connection.localUser.avatarUrl ? "image://mxc/" + connection.localUser.avatarUrl : "" : "qrc:/asset/img/avatar.png" displayText: matriqueController.isLogin && connection.localUser.displayName ? connection.localUser.displayName : "" @@ -104,16 +111,27 @@ ApplicationWindow { } Rectangle { - color: "transparent" Layout.fillHeight: true + + color: "transparent" } SideNavButton { - contentItem: MaterialIcon { icon: "\ue145"; color: "white" } + Layout.fillWidth: true + Layout.preferredHeight: width + + MaterialIcon { + anchors.fill: parent + + icon: "\ue145" + color: "white" + } + onClicked: addRoomMenu.popup() Menu { id: addRoomMenu + MenuItem { text:"New Room" onTriggered: addRoomDialog.open() @@ -132,14 +150,17 @@ ApplicationWindow { contentItem: Column { TextField { - id: addRoomDialogNameTextField width: parent.width + id: addRoomDialogNameTextField + placeholderText: "Name" } TextField { - id: addRoomDialogTopicTextField width: parent.width + + id: addRoomDialogTopicTextField + placeholderText: "Topic" } } @@ -149,16 +170,18 @@ ApplicationWindow { } MenuItem { text: "Join Room" + onTriggered: joinRoomDialog.open() Dialog { - id: joinRoomDialog - parent: ApplicationWindow.overlay - x: (window.width - width) / 2 y: (window.height - height) / 2 width: 360 + id: joinRoomDialog + + parent: ApplicationWindow.overlay + title: "Input Room Alias or ID" modal: true standardButtons: Dialog.Ok | Dialog.Cancel @@ -171,18 +194,21 @@ ApplicationWindow { onAccepted: matriqueController.joinRoom(joinRoomDialogTextField.text) } } + MenuItem { text: "Direct Chat" + onTriggered: directChatDialog.open() Dialog { - id: directChatDialog - parent: ApplicationWindow.overlay - x: (window.width - width) / 2 y: (window.height - height) / 2 width: 360 + id: directChatDialog + + parent: ApplicationWindow.overlay + title: "Input User ID" modal: true standardButtons: Dialog.Ok | Dialog.Cancel @@ -199,23 +225,41 @@ ApplicationWindow { } SideNavButton { - contentItem: MaterialIcon { icon: "\ue8b8"; color: "white" } + Layout.fillWidth: true + Layout.preferredHeight: width + + MaterialIcon { + anchors.fill: parent + + icon: "\ue8b8" + color: parent.highlighted ? Material.accent : "white" + } page: settingPage } SideNavButton { - contentItem: MaterialIcon { icon: "\ue879"; color: "white" } + Layout.fillWidth: true + Layout.preferredHeight: width + + MaterialIcon { + anchors.fill: parent + + icon: "\ue879" + color: "white" + } + onClicked: Qt.quit() } } } StackView { - id: stackView - initialItem: roomPage - Layout.fillWidth: true Layout.fillHeight: true + + id: stackView + + initialItem: roomPage } } diff --git a/qml/menu/MessageContextMenu.qml b/qml/menu/MessageContextMenu.qml index dd2b92de2..1fbfa1194 100644 --- a/qml/menu/MessageContextMenu.qml +++ b/qml/menu/MessageContextMenu.qml @@ -8,28 +8,33 @@ Menu { MenuItem { text: "Copy" + onTriggered: matriqueController.copyToClipboard(plainText) } MenuItem { text: "Copy Source" + onTriggered: matriqueController.copyToClipboard(toolTip) } MenuItem { visible: isFile height: visible ? undefined : 0 text: "Open Externally" - onTriggered: delegateLoader.item.downloadAndOpen() + + onTriggered: messageRow.openExternally() } MenuItem { visible: isFile height: visible ? undefined : 0 text: "Save As" - onTriggered: delegateLoader.item.saveFileAs() + + onTriggered: messageRow.saveFileAs() } MenuItem { visible: sentByMe height: visible ? undefined : 0 text: "Redact" + onTriggered: currentRoom.redactEvent(eventId) } diff --git a/qml/menu/RoomContextMenu.qml b/qml/menu/RoomContextMenu.qml index 280947b28..9aff67fab 100644 --- a/qml/menu/RoomContextMenu.qml +++ b/qml/menu/RoomContextMenu.qml @@ -8,21 +8,25 @@ Menu { text: "Favourite" checkable: true checked: currentRoom && currentRoom.isFavourite + onTriggered: currentRoom.isFavourite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", "1") } MenuItem { text: "Deprioritize" checkable: true checked: currentRoom && currentRoom.isLowPriority + onTriggered: currentRoom.isLowPriority ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", "1") } MenuSeparator {} MenuItem { text: "Mark as Read" + onTriggered: currentRoom.markAllMessagesAsRead() } MenuItem { text: "Leave Room" + onTriggered: currentRoom.forget() } diff --git a/qtquickcontrols2.conf b/qtquickcontrols2.conf index fdec96c3a..0cc53197c 100644 --- a/qtquickcontrols2.conf +++ b/qtquickcontrols2.conf @@ -7,9 +7,7 @@ Style=Material [Material] Theme=Light -Primary=#00695c -Accent=#00695c -;Primary=#1565c0 -;Accent=#1565c0 +Primary=#344955 +Accent=#498882 ;Foreground=Black ;Background=#161616 diff --git a/res.qrc b/res.qrc index e2c99a155..71c1cf500 100644 --- a/res.qrc +++ b/res.qrc @@ -14,16 +14,9 @@ asset/img/icon.png js/md.js qml/component/MessageDelegate.qml - qml/component/MessageBubble.qml - qml/component/ImageBubble.qml - qml/component/StateBubble.qml qml/component/DownloadableContent.qml - qml/component/FileBubble.qml - qml/component/AvatarContainer.qml qml/form/RoomListForm.qml - qml/component/AudioBubble.qml qml/Setting.qml - qml/component/TextDelegate.qml qml/component/EmojiPicker.qml qml/component/EmojiButton.qml qml/component/AutoImage.qml @@ -33,5 +26,8 @@ qml/MatriqueSettings.qml qml/menu/MessageContextMenu.qml qml/menu/RoomContextMenu.qml + qml/component/GenericBubble.qml + qml/component/StateDelegate.qml + qml/component/AutoLabel.qml diff --git a/src/matriqueroom.cpp b/src/matriqueroom.cpp index dcd79ae2d..faa36c836 100644 --- a/src/matriqueroom.cpp +++ b/src/matriqueroom.cpp @@ -83,6 +83,7 @@ void MatriqueRoom::sendTypingNotification(bool isTyping) { QString MatriqueRoom::lastEvent() { if (timelineSize() == 0) return ""; const RoomEvent* lastEvent = messageEvents().rbegin()->get(); + if (lastEvent->contentJson().value("body").toString() == "") return ""; return user(lastEvent->senderId())->displayname() + ": " + lastEvent->contentJson().value("body").toString(); }