diff --git a/CMakeLists.txt b/CMakeLists.txt index 06bf3a90c..407b43fa8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -86,6 +86,8 @@ if (NOT USE_INTREE_LIBQMC) endif () endif () +find_package(KF5Kirigami2 REQUIRED) + find_package(Qt5Keychain REQUIRED) find_package(cmark REQUIRED) @@ -209,6 +211,7 @@ add_executable(${PROJECT_NAME} WIN32 MACOSX_BUNDLE target_link_libraries(${PROJECT_NAME} Qt5::Widgets Qt5::Quick Qt5::Qml Qt5::Gui Qt5::Network Qt5::Svg Qt5::QuickControls2 + KF5::Kirigami2 Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} diff --git a/assets/font/material.ttf b/assets/font/material.ttf deleted file mode 100644 index 7015564ad..000000000 Binary files a/assets/font/material.ttf and /dev/null differ diff --git a/imports/Spectral/Component/Timeline/ImageDelegate.qml b/imports/Spectral/Component/Timeline/ImageDelegate.qml index 445c18cfb..d1e025a81 100644 --- a/imports/Spectral/Component/Timeline/ImageDelegate.qml +++ b/imports/Spectral/Component/Timeline/ImageDelegate.qml @@ -14,185 +14,130 @@ import Spectral.Menu.Timeline 2.0 import Spectral.Effect 2.0 import Spectral.Font 0.1 -RowLayout { - readonly property bool avatarVisible: showAuthor && !sentByMe - readonly property bool sentByMe: author.isLocalUser +Image { readonly property bool isAnimated: contentType === "image/gif" property bool openOnFinished: false readonly property bool downloaded: progressInfo && progressInfo.completed - id: root + readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null) + // readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info + readonly property var info: content.info + readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId - spacing: 4 + id: img - z: -5 + source: "image://mxc/" + mediaId - onDownloadedChanged: { - if (downloaded && openOnFinished) { - openSavedFile() - openOnFinished = false + sourceSize.width: info.w + sourceSize.height: info.h + + fillMode: Image.PreserveAspectCrop + + layer.enabled: true + layer.effect: OpacityMask { + maskSource: Rectangle { + width: img.width + height: img.height + radius: 18 } } - Avatar { - Layout.preferredWidth: 36 - Layout.preferredHeight: 36 - Layout.alignment: Qt.AlignBottom + Control { + anchors.bottom: parent.bottom + anchors.bottomMargin: 8 + anchors.right: parent.right + anchors.rightMargin: 8 - visible: avatarVisible - hint: author.displayName - source: author.avatarMediaId - color: author.color + horizontalPadding: 8 + verticalPadding: 4 + + contentItem: RowLayout { + Label { + text: Qt.formatTime(time) + color: "white" + font.pixelSize: 12 + } + + Label { + text: author.displayName + color: "white" + font.pixelSize: 12 + } + } + + background: Rectangle { + radius: height / 2 + color: "black" + opacity: 0.3 + } + } + + Rectangle { + anchors.fill: parent + + visible: progressInfo.active && !downloaded + + color: "#BB000000" + + ProgressBar { + anchors.centerIn: parent + + width: parent.width * 0.8 + + from: 0 + to: progressInfo.total + value: progressInfo.progress + } + } + + RippleEffect { + anchors.fill: parent + + id: messageMouseArea + + onPrimaryClicked: fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen() + + onSecondaryClicked: { + var contextMenu = imageDelegateContextMenu.createObject(root) + contextMenu.viewSource.connect(function() { + messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open() + }) + contextMenu.downloadAndOpen.connect(downloadAndOpen) + contextMenu.saveFileAs.connect(saveFileAs) + contextMenu.reply.connect(function() { + roomPanelInput.replyModel = Object.assign({}, model) + roomPanelInput.isReply = true + roomPanelInput.focus() + }) + contextMenu.redact.connect(function() { + currentRoom.redactEvent(eventId) + }) + contextMenu.popup() + } Component { - id: userDetailDialog + id: messageSourceDialog - UserDetailDialog {} + MessageSourceDialog {} } - RippleEffect { - anchors.fill: parent + Component { + id: openFolderDialog - circular: true - - onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open() - } - } - - Item { - Layout.preferredWidth: 36 - Layout.preferredHeight: 36 - - visible: !(sentByMe || avatarVisible) - } - - Image { - readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null) - readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info - readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId - readonly property int maxWidth: messageListView.width - (!sentByMe ? 36 + root.spacing : 0) - 48 - - Layout.minimumWidth: 256 - Layout.minimumHeight: 64 - - Layout.preferredWidth: info.w > maxWidth ? maxWidth : info.w - Layout.preferredHeight: info.w > maxWidth ? (info.h * maxWidth / info.w) : info.h - - id: img - - source: "image://mxc/" + mediaId - - sourceSize.width: info.w - sourceSize.height: info.h - - fillMode: Image.PreserveAspectCrop - - layer.enabled: true - layer.effect: OpacityMask { - maskSource: Rectangle { - width: img.width - height: img.height - radius: 18 - } + OpenFolderDialog {} } - Control { - anchors.bottom: parent.bottom - anchors.bottomMargin: 8 - anchors.right: parent.right - anchors.rightMargin: 8 + Component { + id: imageDelegateContextMenu - horizontalPadding: 8 - verticalPadding: 4 - - contentItem: RowLayout { - Label { - text: Qt.formatTime(time) - color: "white" - font.pixelSize: 12 - } - - Label { - text: author.displayName - color: "white" - font.pixelSize: 12 - } - } - - background: Rectangle { - radius: height / 2 - color: "black" - opacity: 0.3 - } + FileDelegateContextMenu {} } - Rectangle { - anchors.fill: parent + Component { + id: fullScreenImage - visible: progressInfo.active && !downloaded - - color: "#BB000000" - - ProgressBar { - anchors.centerIn: parent - - width: parent.width * 0.8 - - from: 0 - to: progressInfo.total - value: progressInfo.progress - } - } - - RippleEffect { - anchors.fill: parent - - id: messageMouseArea - - onPrimaryClicked: fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen() - - onSecondaryClicked: { - var contextMenu = imageDelegateContextMenu.createObject(root) - contextMenu.viewSource.connect(function() { - messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open() - }) - contextMenu.downloadAndOpen.connect(downloadAndOpen) - contextMenu.saveFileAs.connect(saveFileAs) - contextMenu.reply.connect(function() { - roomPanelInput.replyModel = Object.assign({}, model) - roomPanelInput.isReply = true - roomPanelInput.focus() - }) - contextMenu.redact.connect(function() { - currentRoom.redactEvent(eventId) - }) - contextMenu.popup() - } - - Component { - id: messageSourceDialog - - MessageSourceDialog {} - } - - Component { - id: openFolderDialog - - OpenFolderDialog {} - } - - Component { - id: imageDelegateContextMenu - - FileDelegateContextMenu {} - } - - Component { - id: fullScreenImage - - FullScreenImage {} - } + FullScreenImage {} } } diff --git a/imports/Spectral/Component/Timeline/MessageDelegate.qml b/imports/Spectral/Component/Timeline/MessageDelegate.qml index a17a312f3..ec03a5888 100644 --- a/imports/Spectral/Component/Timeline/MessageDelegate.qml +++ b/imports/Spectral/Component/Timeline/MessageDelegate.qml @@ -1,309 +1,108 @@ import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick.Controls 2.12 as Controls import QtQuick.Layouts 1.12 -import QtQuick.Controls.Material 2.12 + +import org.kde.kirigami 2.4 as Kirigami import Spectral 0.1 import Spectral.Setting 0.1 - import Spectral.Component 2.0 -import Spectral.Dialog 2.0 -import Spectral.Menu.Timeline 2.0 -import Spectral.Effect 2.0 -ColumnLayout { - readonly property bool avatarVisible: !sentByMe && showAuthor +RowLayout { + default property alias innerObject : column.children + readonly property bool sentByMe: author.isLocalUser - readonly property bool darkBackground: !sentByMe readonly property bool replyVisible: reply || false readonly property bool failed: marks === EventStatus.SendingFailed - readonly property color authorColor: eventType === "notice" ? MPalette.primary : author.color - readonly property color replyAuthorColor: replyVisible ? reply.author.color : MPalette.accent - - signal saveFileAs() - signal openExternally() id: root - z: -5 + spacing: Kirigami.Units.largeSpacing - spacing: 0 + Avatar { + Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5 + Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5 - RowLayout { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft + Layout.alignment: Qt.AlignTop - id: messageRow + visible: showAuthor + hint: author.displayName + source: author.avatarMediaId + color: author.color + } - spacing: 4 + Item { + Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5 + Layout.preferredHeight: 1 - Avatar { - Layout.preferredWidth: 36 - Layout.preferredHeight: 36 - Layout.alignment: Qt.AlignBottom + visible: !showAuthor + } - visible: avatarVisible - hint: author.displayName - source: author.avatarMediaId - color: author.color + ColumnLayout { + Layout.fillWidth: true - Component { - id: userDetailDialog + id: column - UserDetailDialog {} - } + spacing: Kirigami.Units.smallSpacing - RippleEffect { - anchors.fill: parent + Controls.Label { + Layout.fillWidth: true - circular: true + visible: showAuthor - onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open() - } + text: author.displayName + font.bold: true + color: Kirigami.Theme.activeTextColor + wrapMode: Text.Wrap } - Item { - Layout.preferredWidth: 36 - Layout.preferredHeight: 36 + RowLayout { + Layout.fillWidth: true - visible: !(sentByMe || avatarVisible) - } + visible: replyVisible - Control { - Layout.maximumWidth: messageListView.width - (!sentByMe ? 36 + messageRow.spacing : 0) - 48 - Layout.minimumHeight: 36 + Rectangle { + Layout.preferredWidth: 4 + Layout.fillHeight: true - padding: 0 - - background: AutoRectangle { - readonly property int minorRadius: 2 - - id: bubbleBackground - - color: sentByMe ? MPalette.background : authorColor - radius: 18 - - topLeftVisible: !sentByMe && (bubbleShape == 3 || bubbleShape == 2) - topRightVisible: sentByMe && (bubbleShape == 3 || bubbleShape == 2) - bottomLeftVisible: !sentByMe && (bubbleShape == 1 || bubbleShape == 2) - bottomRightVisible: sentByMe && (bubbleShape == 1 || bubbleShape == 2) - - topLeftRadius: minorRadius - topRightRadius: minorRadius - bottomLeftRadius: minorRadius - bottomRightRadius: minorRadius - - AutoMouseArea { - anchors.fill: parent - - id: messageMouseArea - - onSecondaryClicked: { - var contextMenu = messageDelegateContextMenu.createObject(root) - contextMenu.viewSource.connect(function() { - messageSourceDialog.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open() - }) - contextMenu.reply.connect(function() { - roomPanelInput.replyModel = Object.assign({}, model) - roomPanelInput.isReply = true - roomPanelInput.focus() - }) - contextMenu.redact.connect(function() { - currentRoom.redactEvent(eventId) - }) - contextMenu.popup() - } - - - Component { - id: messageDelegateContextMenu - - MessageDelegateContextMenu {} - } - - Component { - id: messageSourceDialog - - MessageSourceDialog {} - } - } + color: Kirigami.Theme.highlightColor } - contentItem: ColumnLayout { - spacing: 0 + Avatar { + Layout.preferredWidth: Kirigami.Units.gridUnit * 1.5 + Layout.preferredHeight: Kirigami.Units.gridUnit * 1.5 + Layout.alignment: Qt.AlignTop - Control { + source: replyVisible ? reply.author.avatarMediaId : "" + hint: replyVisible ? reply.author.displayName : "H" + color: replyVisible ? reply.author.color : MPalette.accent + } + + ColumnLayout { + Layout.fillWidth: true + + Controls.Label { Layout.fillWidth: true - Layout.topMargin: 8 - Layout.leftMargin: 8 - Layout.rightMargin: 8 - - id: replyControl - - padding: 4 - rightPadding: 12 - - visible: replyVisible - - contentItem: RowLayout { - Avatar { - Layout.preferredWidth: 28 - Layout.preferredHeight: 28 - Layout.alignment: Qt.AlignTop - - source: replyVisible ? reply.author.avatarMediaId : "" - hint: replyVisible ? reply.author.displayName : "H" - color: replyVisible ? reply.author.color : MPalette.accent - - RippleEffect { - anchors.fill: parent - - circular: true - - onClicked: userDetailDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom, "user": reply.author.object, "displayName": reply.author.displayName, "avatarMediaId": reply.author.avatarMediaId, "avatarUrl": reply.author.avatarUrl}).open() - } - } - - TextEdit { - Layout.fillWidth: true - - color: !sentByMe ? MPalette.foreground : "white" - text: "" + (replyVisible ? reply.display : "") - - font.family: window.font.family - selectByMouse: true - readOnly: true - wrapMode: Label.Wrap - selectedTextColor: darkBackground ? "white" : replyAuthorColor - selectionColor: darkBackground ? replyAuthorColor : "white" - textFormat: Text.RichText - } - } - - background: Rectangle { - color: sentByMe ? replyAuthorColor : MPalette.background - radius: 18 - - AutoMouseArea { - anchors.fill: parent - - onClicked: goToEvent(reply.eventId) - } - } + text: replyVisible ? reply.author.displayName : "" + color: Kirigami.Theme.activeTextColor + wrapMode: Text.Wrap } TextEdit { Layout.fillWidth: true - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.topMargin: 8 - Layout.bottomMargin: 8 + text: "" + (replyVisible ? reply.display : "") - id: contentLabel + color: Kirigami.Theme.textColor + selectionColor: Kirigami.Theme.highlightColor + selectedTextColor: Kirigami.Theme.highlightedTextColor - text: "" + display - - color: darkBackground ? "white" : MPalette.foreground - - font.family: window.font.family - font.pixelSize: (message.length === 2 && /(?:[\u2700-\u27bf]|(?:\ud83c[\udde6-\uddff]){2}|[\ud800-\udbff][\udc00-\udfff]|[\u0023-\u0039]\ufe0f?\u20e3|\u3299|\u3297|\u303d|\u3030|\u24c2|\ud83c[\udd70-\udd71]|\ud83c[\udd7e-\udd7f]|\ud83c\udd8e|\ud83c[\udd91-\udd9a]|\ud83c[\udde6-\uddff]|\ud83c[\ude01-\ude02]|\ud83c\ude1a|\ud83c\ude2f|\ud83c[\ude32-\ude3a]|\ud83c[\ude50-\ude51]|\u203c|\u2049|[\u25aa-\u25ab]|\u25b6|\u25c0|[\u25fb-\u25fe]|\u00a9|\u00ae|\u2122|\u2139|\ud83c\udc04|[\u2600-\u26FF]|\u2b05|\u2b06|\u2b07|\u2b1b|\u2b1c|\u2b50|\u2b55|\u231a|\u231b|\u2328|\u23cf|[\u23e9-\u23f3]|[\u23f8-\u23fa]|\ud83c\udccf|\u2934|\u2935|[\u2190-\u21ff])/g.test(message)) ? 48 : 14 selectByMouse: true readOnly: true - wrapMode: Label.Wrap - selectedTextColor: darkBackground ? authorColor : "white" - selectionColor: darkBackground ? "white" : authorColor + wrapMode: Text.Wrap textFormat: Text.RichText - - onLinkActivated: { - if (link.startsWith("https://matrix.to/")) { - var result = link.replace(/\?.*/, "").match("https://matrix.to/#/(!.*:.*)/(\\$.*:.*)") - if (!result || result.length < 3) return - if (result[1] != currentRoom.id) return - if (!result[2]) return - goToEvent(result[2]) - } else { - Qt.openUrlExternally(link) - } - } - - MouseArea { - anchors.fill: parent - acceptedButtons: Qt.NoButton - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor - } } - - ReactionDelegate { - Layout.fillWidth: true - - Layout.topMargin: 0 - Layout.bottomMargin: 8 - Layout.leftMargin: 16 - Layout.rightMargin: 16 - } - } - } - } - - RowLayout { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - Layout.leftMargin: sentByMe ? undefined : 36 + messageRow.spacing + 12 - Layout.rightMargin: sentByMe ? 12 : undefined - Layout.bottomMargin: 4 - - visible: showAuthor && !failed - - Label { - visible: !sentByMe - - text: author.displayName - color: MPalette.lighter - } - - Label { - text: Qt.formatTime(time) - color: MPalette.lighter - } - } - - RowLayout { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - Layout.leftMargin: sentByMe ? undefined : 36 + messageRow.spacing + 12 - Layout.rightMargin: sentByMe ? 12 : undefined - Layout.bottomMargin: 4 - - visible: failed - - Label { - text: "Send failed:" - color: MPalette.lighter - } - - Label { - text: "Resend" - color: MPalette.lighter - - MouseArea { - anchors.fill: parent - - onClicked: currentRoom.retryMessage(eventId) - } - } - - Label { - text: "|" - color: MPalette.lighter - } - - Label { - text: "Discard" - color: MPalette.lighter - - MouseArea { - anchors.fill: parent - - onClicked: currentRoom.discardMessage(eventId) } } } diff --git a/imports/Spectral/Component/Timeline/SectionDelegate.qml b/imports/Spectral/Component/Timeline/SectionDelegate.qml index 8b6b514e2..6fdebb151 100644 --- a/imports/Spectral/Component/Timeline/SectionDelegate.qml +++ b/imports/Spectral/Component/Timeline/SectionDelegate.qml @@ -1,11 +1,10 @@ import QtQuick 2.12 -import QtQuick.Controls 2.12 -import Spectral.Setting 0.1 +import QtQuick.Controls 2.12 as Controls -Label { +import org.kde.kirigami 2.4 as Kirigami + +Controls.Label { text: section + " • " + Qt.formatTime(time) - color: MPalette.foreground - font.pixelSize: 13 font.weight: Font.Medium font.capitalization: Font.AllUppercase verticalAlignment: Text.AlignVCenter diff --git a/imports/Spectral/Component/Timeline/TextDelegate.qml b/imports/Spectral/Component/Timeline/TextDelegate.qml new file mode 100644 index 000000000..4700b4a2a --- /dev/null +++ b/imports/Spectral/Component/Timeline/TextDelegate.qml @@ -0,0 +1,23 @@ +import QtQuick 2.12 + +import org.kde.kirigami 2.4 as Kirigami + + +TextEdit { + text: "" + display + + font { + pixelSize: Kirigami.Theme.defaultFont.pixelSize * 1.2 + family: Kirigami.Theme.defaultFont.family + } + + color: Kirigami.Theme.textColor + selectionColor: Kirigami.Theme.highlightColor + selectedTextColor: Kirigami.Theme.highlightedTextColor + + selectByMouse: true + readOnly: true + wrapMode: Text.Wrap + textFormat: Text.RichText +} + diff --git a/imports/Spectral/Component/Timeline/TimelineContainer.qml b/imports/Spectral/Component/Timeline/TimelineContainer.qml new file mode 100644 index 000000000..7d14711a8 --- /dev/null +++ b/imports/Spectral/Component/Timeline/TimelineContainer.qml @@ -0,0 +1,23 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 as Controls +import QtQuick.Layouts 1.12 + +import org.kde.kirigami 2.4 as Kirigami + +Controls.Control { + default property alias innerObject : column.children + + horizontalPadding: Kirigami.Units.largeSpacing + verticalPadding: Kirigami.Units.smallSpacing + + contentItem: Column { + id: column + + SectionDelegate { + anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(implicitWidth, parent.width) + + visible: showSection + } + } +} diff --git a/imports/Spectral/Component/Timeline/qmldir b/imports/Spectral/Component/Timeline/qmldir index 60b807c93..95d71ccec 100644 --- a/imports/Spectral/Component/Timeline/qmldir +++ b/imports/Spectral/Component/Timeline/qmldir @@ -1,5 +1,7 @@ module Spectral.Component.Timeline +TimelineContainer 2.0 TimelineContainer.qml MessageDelegate 2.0 MessageDelegate.qml +TextDelegate 2.0 TextDelegate.qml StateDelegate 2.0 StateDelegate.qml SectionDelegate 2.0 SectionDelegate.qml ImageDelegate 2.0 ImageDelegate.qml diff --git a/imports/Spectral/Panel/RoomListPanel.qml b/imports/Spectral/Panel/RoomListPanel.qml index cbd64b636..7797b8230 100644 --- a/imports/Spectral/Panel/RoomListPanel.qml +++ b/imports/Spectral/Panel/RoomListPanel.qml @@ -1,434 +1,141 @@ import QtQuick 2.12 -import QtQuick.Controls 2.12 - +import QtQuick.Controls 2.12 as Controls import QtQuick.Layouts 1.12 -import QtQuick.Controls.Material 2.12 -import Spectral.Component 2.0 -import Spectral.Dialog 2.0 -import Spectral.Menu 2.0 -import Spectral.Effect 2.0 - -import Spectral 0.1 -import Spectral.Setting 0.1 +import org.kde.kirigami 2.4 as Kirigami import SortFilterProxyModel 0.2 -Item { - property var connection: null - readonly property var user: connection ? connection.localUser : null +import Spectral.Component 2.0 +import Spectral 0.1 - property int filter: 0 - property var enteredRoom: null +Kirigami.ScrollablePage { + property var roomListModel + property var enteredRoom signal enterRoom(var room) signal leaveRoom(var room) - id: root - - RoomListModel { - id: roomListModel - - connection: root.connection - - onNewMessage: if (!window.active && MSettings.showNotification) notificationsManager.postNotification(roomId, eventId, roomName, senderName, text, icon) - } - - Binding { - target: trayIcon - property: "notificationCount" - value: roomListModel.notificationCount - } - - SortFilterProxyModel { - id: sortedRoomListModel - - sourceModel: roomListModel - - proxyRoles: ExpressionRole { - name: "display" - expression: { - switch (category) { - case 1: return "Invited" - case 2: return "Favorites" - case 3: return "People" - case 4: return "Rooms" - case 5: return "Low Priority" - } - } + title: "Spectral" + actions { + main: Kirigami.Action { + iconName: "document-edit" } - - sorters: [ - RoleSorter { roleName: "category" }, - ExpressionSorter { - expression: { - return modelLeft.highlightCount > 0; - } - }, - ExpressionSorter { - expression: { - return modelLeft.notificationCount > 0; - } - }, - RoleSorter { - roleName: "lastActiveTime" - sortOrder: Qt.DescendingOrder - } - ] - - filters: [ - ExpressionFilter { - expression: joinState != "upgraded" - }, - RegExpFilter { - roleName: "name" - pattern: searchField.text - caseSensitivity: Qt.CaseInsensitive - }, - ExpressionFilter { - enabled: filter === 0 - expression: category !== 5 && notificationCount > 0 || currentRoom === enteredRoom - }, - ExpressionFilter { - enabled: filter === 1 - expression: category === 1 || category === 3 - }, - ExpressionFilter { - enabled: filter === 2 - expression: category !== 3 - } - ] + contextualActions: [] } - Shortcut { - sequence: "Ctrl+F" - onActivated: searchField.forceActiveFocus() - } + ListView { + id: messageListView - ColumnLayout { - anchors.fill: parent - spacing: 0 + model: SortFilterProxyModel { + id: sortedRoomListModel - Control { - Layout.fillWidth: true - Layout.preferredHeight: 64 + sourceModel: roomListModel - id: roomListHeader - - topPadding: 12 - bottomPadding: 12 - leftPadding: 12 - rightPadding: 18 - - contentItem: RowLayout { - ToolButton { - Layout.preferredWidth: height - Layout.fillHeight: true - - visible: !searchField.active - - contentItem: MaterialIcon { - icon: "\ue8b6" + proxyRoles: ExpressionRole { + name: "display" + expression: { + switch (category) { + case 1: return "Invited" + case 2: return "Favorites" + case 3: return "People" + case 4: return "Rooms" + case 5: return "Low Priority" } } + } - ToolButton { - Layout.preferredWidth: height - Layout.fillHeight: true - - visible: searchField.active - - contentItem: MaterialIcon { icon: "\ue5cd" } - - onClicked: searchField.clear() + sorters: [ + RoleSorter { roleName: "category" }, + ExpressionSorter { + expression: { + return modelLeft.highlightCount > 0; + } + }, + ExpressionSorter { + expression: { + return modelLeft.notificationCount > 0; + } + }, + RoleSorter { + roleName: "lastActiveTime" + sortOrder: Qt.DescendingOrder } + ] - AutoTextField { - readonly property bool active: text - - Layout.fillWidth: true - Layout.alignment: Qt.AlignVCenter - - id: searchField - - placeholderText: "Search..." - color: MPalette.lighter + filters: [ + ExpressionFilter { + expression: joinState != "upgraded" } + ] + } + + delegate: Kirigami.SwipeListItem { + padding: Kirigami.Units.largeSpacing + + actions: [ + Kirigami.Action { + text:"Action for buttons" + iconName: "bookmarks" + onTriggered: print("Action 1 clicked") + }, + Kirigami.Action { + text:"Action 2" + iconName: "folder" + enabled: false + } + ] + + contentItem: RowLayout { + spacing: Kirigami.Units.largeSpacing Avatar { Layout.preferredWidth: height Layout.fillHeight: true - Layout.alignment: Qt.AlignRight - visible: !searchField.active - - source: root.user ? root.user.avatarMediaId : null - hint: root.user ? root.user.displayName : "?" - - RippleEffect { - anchors.fill: parent - - circular: true - - onClicked: accountDetailDialog.createObject(ApplicationWindow.overlay).open() - } - } - } - - background: Rectangle { - color: Material.background - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 2 - } - } - } - - AutoListView { - Layout.fillWidth: true - Layout.fillHeight: true - - id: listView - - z: -1 - - spacing: 0 - - model: sortedRoomListModel - - boundsBehavior: Flickable.DragOverBounds - - ScrollBar.vertical: ScrollBar {} - - delegate: Item { - width: listView.width - height: 64 - - Rectangle { - anchors.fill: parent - - visible: currentRoom === enteredRoom - color: Material.accent - opacity: 0.1 + source: avatar + hint: name || "No Name" } - RowLayout { - anchors.fill: parent - anchors.margins: 12 + ColumnLayout { + Layout.fillWidth: true + Layout.fillHeight: true + Layout.alignment: Qt.AlignHCenter - spacing: 12 + spacing: Kirigami.Units.smallSpacing - Avatar { - Layout.preferredWidth: height - Layout.fillHeight: true - - source: avatar - hint: name || "No Name" - } - - ColumnLayout { + Controls.Label { Layout.fillWidth: true Layout.fillHeight: true - Layout.alignment: Qt.AlignHCenter - Label { - Layout.fillWidth: true - Layout.fillHeight: true - - text: name || "No Name" - color: MPalette.foreground - font.pixelSize: 16 - font.bold: unreadCount >= 0 - elide: Text.ElideRight - wrapMode: Text.NoWrap - } - - Label { - Layout.fillWidth: true - Layout.fillHeight: true - - text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm," ") - color: MPalette.lighter - font.pixelSize: 13 - elide: Text.ElideRight - wrapMode: Text.NoWrap - } + text: name || "No Name" + font.pixelSize: 16 + font.bold: unreadCount >= 0 + elide: Text.ElideRight + wrapMode: Text.NoWrap } - Label { - visible: notificationCount > 0 && highlightCount == 0 - color: MPalette.background - text: notificationCount - leftPadding: 12 - rightPadding: 12 - topPadding: 4 - bottomPadding: 4 - font.bold: true + Controls.Label { + Layout.fillWidth: true + Layout.fillHeight: true - background: Rectangle { - radius: height / 2 - color: MPalette.lighter - } + text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm," ") + font.pixelSize: 13 + elide: Text.ElideRight + wrapMode: Text.NoWrap } - - Label { - visible: highlightCount > 0 - color: "white" - text: highlightCount - leftPadding: 12 - rightPadding: 12 - topPadding: 4 - bottomPadding: 4 - font.bold: true - - background: Rectangle { - radius: height / 2 - color: MPalette.accent - } - } - } - - AutoMouseArea { - anchors.fill: parent - - onPrimaryClicked: { - if (category === RoomType.Invited) { - acceptInvitationDialog.createObject(ApplicationWindow.overlay, {"room": currentRoom}).open() - } else { - joinRoom(currentRoom) - } - } - onSecondaryClicked: roomListContextMenu.createObject(parent, {"room": currentRoom}).popup() - } - - Component { - id: roomListContextMenu - - RoomListContextMenu {} } } - section.property: "display" - section.criteria: ViewSection.FullString - section.delegate: Label { - width: parent.width - height: 24 - - text: section - color: MPalette.lighter - leftPadding: 16 - elide: Text.ElideRight - verticalAlignment: Text.AlignVCenter - } - - ColumnLayout { - anchors.centerIn: parent - - visible: sortedRoomListModel.count == 0 - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - - icon: "\ue5ca" - font.pixelSize: 48 - color: MPalette.lighter + onClicked: { + if (enteredRoom) { + leaveRoom(enteredRoom) } - Label { - Layout.alignment: Qt.AlignHCenter + enteredRoom = currentRoom - text: "You're all caught up!" - - color: MPalette.foreground - } + enterRoom(enteredRoom) } } - - Control { - Layout.fillWidth: true - Layout.preferredHeight: 48 - Layout.margins: 16 - - padding: 8 - - contentItem: RowLayout { - id: tabBar - - MaterialIcon { - Layout.fillWidth: true - - icon: "\ue7f5" - color: filter == 0 ? MPalette.accent : MPalette.lighter - - MouseArea { - anchors.fill: parent - - onClicked: filter = 0 - } - } - - MaterialIcon { - Layout.fillWidth: true - - icon: "\ue7ff" - color: filter == 1 ? MPalette.accent : MPalette.lighter - - MouseArea { - anchors.fill: parent - - onClicked: filter = 1 - } - } - - MaterialIcon { - Layout.fillWidth: true - - icon: "\ue7fc" - color: filter == 2 ? MPalette.accent : MPalette.lighter - - MouseArea { - anchors.fill: parent - - onClicked: filter = 2 - } - } - } - - background: AutoRectangle { - color: MPalette.background - - radius: 24 - - topLeftRadius: 8 - topRightRadius: 8 - - topLeftVisible: true - topRightVisible: true - bottomLeftVisible: false - bottomRightVisible: false - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 1 - } - } - } - } - - Component { - id: acceptInvitationDialog - - AcceptInvitationDialog {} - } - - function joinRoom(room) { - if (enteredRoom) { - leaveRoom(enteredRoom) - enteredRoom.displayed = false - } - enterRoom(room) - enteredRoom = room - room.displayed = true } } diff --git a/imports/Spectral/Panel/RoomPanel.qml b/imports/Spectral/Panel/RoomPanel.qml index a25a8b7ce..506efa5b9 100644 --- a/imports/Spectral/Panel/RoomPanel.qml +++ b/imports/Spectral/Panel/RoomPanel.qml @@ -1,658 +1,236 @@ import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick.Controls 2.12 as Controls import QtQuick.Layouts 1.12 -import QtQuick.Controls.Material 2.12 import Qt.labs.qmlmodels 1.0 -import Qt.labs.platform 1.0 -import QtGraphicalEffects 1.0 -import Spectral.Component 2.0 -import Spectral.Component.Emoji 2.0 -import Spectral.Component.Timeline 2.0 -import Spectral.Dialog 2.0 -import Spectral.Effect 2.0 +import org.kde.kirigami 2.4 as Kirigami -import Spectral 0.1 -import Spectral.Setting 0.1 import SortFilterProxyModel 0.2 -Item { - property var currentRoom: null +import Spectral.Component 2.0 +import Spectral.Component.Timeline 2.0 +import Spectral 0.1 - id: root +Kirigami.ScrollablePage { + property var currentRoom + + id: page + + title: "Messages" + + actions { + main: Kirigami.Action { + iconName: "document-edit" + } + contextualActions: [] + } MessageEventModel { id: messageEventModel + room: currentRoom } - DropArea { - anchors.fill: parent + ListView { + readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1 + readonly property bool noNeedMoreContent: !currentRoom || currentRoom.eventsHistoryJob || currentRoom.allHistoryLoaded - enabled: currentRoom + id: messageListView - onDropped: { - if (!drop.hasUrls) return + spacing: Kirigami.Units.smallSpacing - roomPanelInput.attach(drop.urls[0]) - } - } + displayMarginBeginning: 100 + displayMarginEnd: 100 + verticalLayoutDirection: ListView.BottomToTop + highlightMoveDuration: 500 - ImageClipboard { - id: imageClipboard - } + model: SortFilterProxyModel { + id: sortedMessageEventModel - Popup { - anchors.centerIn: parent + sourceModel: messageEventModel - id: attachDialog - - padding: 16 - - contentItem: RowLayout { - Control { - Layout.preferredWidth: 160 - Layout.fillHeight: true - - padding: 16 - - contentItem: ColumnLayout { - spacing: 16 - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - - icon: "\ue2c8" - font.pixelSize: 64 - color: MPalette.lighter - } - - Label { - Layout.alignment: Qt.AlignHCenter - - text: "Choose local file" - color: MPalette.foreground - } + filters: [ + ExpressionFilter { + expression: marks !== 0x10 && eventType !== "other" } + ] - background: RippleEffect { - onClicked: { - attachDialog.close() + onModelReset: { + if (currentRoom) { + if (currentRoom.timelineSize < 20) + currentRoom.getPreviousContent(50) + } + } + } - var fileDialog = openFileDialog.createObject(ApplicationWindow.overlay) + onContentYChanged: { + if(!noNeedMoreContent && contentY - 5000 < originY) + currentRoom.getPreviousContent(20); + } - fileDialog.chosen.connect(function(path) { - if (!path) return + populate: Transition { + NumberAnimation { + property: "opacity"; from: 0; to: 1 + duration: 200 + } + } - roomPanelInput.attach(path) - }) + add: Transition { + NumberAnimation { + property: "opacity"; from: 0; to: 1 + duration: 200 + } + } - fileDialog.open() + move: Transition { + NumberAnimation { + property: "y"; duration: 200 + } + NumberAnimation { + property: "opacity"; to: 1 + } + } + + displaced: Transition { + NumberAnimation { + property: "y"; duration: 200 + easing.type: Easing.OutQuad + } + NumberAnimation { + property: "opacity"; to: 1 + } + } + + delegate: DelegateChooser { + role: "eventType" + + DelegateChoice { + roleValue: "state" + delegate: TimelineContainer { + width: page.width + + StateDelegate {} + } + } + + DelegateChoice { + roleValue: "emote" + delegate: TimelineContainer { + width: page.width + + innerObject: StateDelegate {} + } + } + + DelegateChoice { + roleValue: "message" + delegate: TimelineContainer { + width: page.width + + innerObject: MessageDelegate { + width: parent.width + + innerObject: TextDelegate { + Layout.fillWidth: true + } } } } - Rectangle { - Layout.preferredWidth: 1 - Layout.fillHeight: true + DelegateChoice { + roleValue: "notice" + delegate: TimelineContainer { + width: page.width - color: MPalette.banner + innerObject: MessageDelegate { + width: parent.width + + innerObject: TextDelegate { + Layout.fillWidth: true + } + } + } } - Control { - Layout.preferredWidth: 160 - Layout.fillHeight: true + DelegateChoice { + roleValue: "image" + delegate: TimelineContainer { + width: page.width - padding: 16 + innerObject: MessageDelegate { + width: parent.width - contentItem: ColumnLayout { - spacing: 16 - - MaterialIcon { - Layout.alignment: Qt.AlignHCenter - - icon: "\ue410" - font.pixelSize: 64 - color: MPalette.lighter - } - - Label { - Layout.alignment: Qt.AlignHCenter - - text: "Clipboard image" - color: MPalette.foreground + innerObject: ImageDelegate { + Layout.fillWidth: true + Layout.preferredHeight: info.h + } } } + } - background: RippleEffect { - onClicked: { - var localPath = StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/screenshots/" + (new Date()).getTime() + ".png" - if (!imageClipboard.saveImage(localPath)) return - roomPanelInput.attach(localPath) - attachDialog.close() + DelegateChoice { + roleValue: "audio" + delegate: TimelineContainer { + width: page.width + + innerObject: MessageDelegate { + width: parent.width + + innerObject: AudioDelegate { + Layout.fillWidth: true + } } } } + + DelegateChoice { + roleValue: "video" + delegate: TimelineContainer { + width: page.width + + innerObject: MessageDelegate { + width: parent.width + + innerObject: AudioDelegate { + Layout.fillWidth: true + } + } + } + } + + DelegateChoice { + roleValue: "file" + delegate: TimelineContainer { + width: page.width + + innerObject: MessageDelegate { + width: parent.width + + innerObject: FileDelegate { + Layout.fillWidth: true + } + } + } + } + + DelegateChoice { + roleValue: "other" + delegate: Item {} + } } } - Component { - id: openFileDialog - - OpenFileDialog {} - } - - Column { - anchors.centerIn: parent - - spacing: 16 - - visible: !currentRoom - - Image { - anchors.horizontalCenter: parent.horizontalCenter - - width: 240 - - fillMode: Image.PreserveAspectFit - - source: "qrc:/assets/img/matrix.svg" + footer: RowLayout { + Controls.ToolButton { + contentItem: Kirigami.Icon { + source: "mail-attachment" + } } - Label { - anchors.horizontalCenter: parent.horizontalCenter - - text: "Welcome to Matrix, a new era of instant messaging." - wrapMode: Label.Wrap - } - - Label { - anchors.horizontalCenter: parent.horizontalCenter - - text: "To start chatting, select a room from the room list." - wrapMode: Label.Wrap - } - } - - Rectangle { - anchors.fill: parent - - visible: currentRoom - color: MSettings.darkTheme ? "#242424" : "#EBEFF2" - } - - ColumnLayout { - anchors.fill: parent - spacing: 0 - - visible: currentRoom - - RoomHeader { + Controls.TextField { Layout.fillWidth: true - Layout.preferredHeight: 64 - z: 10 - - id: roomHeader - - onClicked: roomDrawer.visible ? roomDrawer.close() : roomDrawer.open() - } - - ColumnLayout { - Layout.fillWidth: true - Layout.fillHeight: true - Layout.maximumWidth: 960 - Layout.alignment: Qt.AlignHCenter - Layout.leftMargin: 16 - Layout.rightMargin: 16 - Layout.bottomMargin: 16 - - width: Math.min(parent.width - 32, 960) - - spacing: 16 - - AutoListView { - readonly property int largestVisibleIndex: count > 0 ? indexAt(contentX + (width / 2), contentY + height - 1) : -1 - readonly property bool noNeedMoreContent: !currentRoom || currentRoom.eventsHistoryJob || currentRoom.allHistoryLoaded - - Layout.fillWidth: true - Layout.fillHeight: true - - id: messageListView - - spacing: 2 - - displayMarginBeginning: 100 - displayMarginEnd: 100 - verticalLayoutDirection: ListView.BottomToTop - highlightMoveDuration: 500 - - boundsBehavior: Flickable.DragOverBounds - - model: SortFilterProxyModel { - id: sortedMessageEventModel - - sourceModel: messageEventModel - - filters: [ - ExpressionFilter { - expression: marks !== 0x10 && eventType !== "other" - } - ] - - onModelReset: { - movingTimer.stop() - if (currentRoom) { - movingTimer.restart() - - // var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex()) - // if (lastScrollPosition === 0) { - // messageListView.positionViewAtBeginning() - // } else { - // messageListView.currentIndex = lastScrollPosition - // } - - if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize < 20) - currentRoom.getPreviousContent(50) - - messageListView.positionViewAtBeginning() - } - } - } - - onContentYChanged: { - if(!noNeedMoreContent && contentY - 5000 < originY) - currentRoom.getPreviousContent(20); - } - - populate: Transition { - NumberAnimation { - property: "opacity"; from: 0; to: 1 - duration: 200 - } - } - - add: Transition { - NumberAnimation { - property: "opacity"; from: 0; to: 1 - duration: 200 - } - } - - move: Transition { - NumberAnimation { - property: "y"; duration: 200 - } - NumberAnimation { - property: "opacity"; to: 1 - } - } - - displaced: Transition { - NumberAnimation { - property: "y"; duration: 200 - easing.type: Easing.OutQuad - } - NumberAnimation { - property: "opacity"; to: 1 - } - } - - delegate: DelegateChooser { - role: "eventType" - - DelegateChoice { - roleValue: "state" - delegate: StateDelegate { - anchors.horizontalCenter: parent.horizontalCenter - } - } - - DelegateChoice { - roleValue: "emote" - delegate: StateDelegate { - anchors.horizontalCenter: parent.horizontalCenter - } - } - - DelegateChoice { - roleValue: "message" - delegate: ColumnLayout { - width: messageListView.width - - SectionDelegate { - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: parent.width - - visible: showSection - } - - MessageDelegate { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 - - visible: readMarker - - color: MPalette.primary - } - } - } - - DelegateChoice { - roleValue: "notice" - delegate: ColumnLayout { - width: messageListView.width - - SectionDelegate { - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: parent.width - - visible: showSection - } - - MessageDelegate { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 - - visible: readMarker - - color: MPalette.primary - } - } - } - - DelegateChoice { - roleValue: "image" - delegate: ColumnLayout { - width: messageListView.width - - SectionDelegate { - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: parent.width - - visible: showSection - } - - ImageDelegate { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 - - visible: readMarker - - color: MPalette.primary - } - } - } - - DelegateChoice { - roleValue: "audio" - delegate: ColumnLayout { - width: messageListView.width - - SectionDelegate { - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: parent.width - - visible: showSection - } - - AudioDelegate { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 - - visible: readMarker - - color: MPalette.primary - } - } - } - - DelegateChoice { - roleValue: "video" - delegate: ColumnLayout { - width: messageListView.width - - SectionDelegate { - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: parent.width - - visible: showSection - } - - VideoDelegate { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 - - visible: readMarker - - color: MPalette.primary - } - } - } - - DelegateChoice { - roleValue: "file" - delegate: ColumnLayout { - width: messageListView.width - - SectionDelegate { - Layout.alignment: Qt.AlignHCenter - Layout.maximumWidth: parent.width - - visible: showSection - } - - FileDelegate { - Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft - } - - Rectangle { - Layout.fillWidth: true - Layout.preferredHeight: 2 - - visible: readMarker - - color: MPalette.primary - } - } - } - - DelegateChoice { - roleValue: "other" - delegate: Item {} - } - } - - Control { - anchors.right: parent.right - anchors.top: parent.top - anchors.topMargin: 16 - - padding: 8 - - id: goReadMarkerFab - - visible: currentRoom && currentRoom.hasUnreadMessages - - contentItem: MaterialIcon { - icon: "\ue316" - font.pixelSize: 28 - } - - background: Rectangle { - color: MPalette.background - radius: height / 2 - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 2 - } - - RippleEffect { - anchors.fill: parent - - circular: true - - onClicked: goToEvent(currentRoom.readMarkerEventId) - } - } - } - - Control { - anchors.right: parent.right - anchors.bottom: parent.bottom - - padding: 8 - - id: goTopFab - - visible: !messageListView.atYEnd - - contentItem: MaterialIcon { - icon: "\ue313" - font.pixelSize: 28 - } - - background: Rectangle { - color: MPalette.background - radius: height / 2 - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 2 - } - - RippleEffect { - anchors.fill: parent - - circular: true - - onClicked: { - currentRoom.markAllMessagesAsRead() - messageListView.positionViewAtBeginning() - } - } - } - } - - - Control { - anchors.left: parent.left - anchors.bottom: parent.bottom - - visible: currentRoom && currentRoom.usersTyping.length > 0 - padding: 4 - - contentItem: RowLayout { - spacing: 0 - - RowLayout { - spacing: -8 - - Repeater { - model: currentRoom && currentRoom.usersTyping.length > 0 ? currentRoom.usersTyping : null - - delegate: Rectangle { - Layout.preferredWidth: 28 - Layout.preferredHeight: 28 - - color: "white" - radius: 14 - - Avatar { - anchors.fill: parent - anchors.margins: 2 - - source: modelData.avatarMediaId - hint: modelData.displayName - color: modelData.color - } - } - } - } - - Item { - Layout.preferredWidth: 28 - Layout.preferredHeight: 28 - - BusyIndicator { - anchors.centerIn: parent - - width: 32 - height: 32 - } - } - } - - background: Rectangle { - color: MPalette.background - radius: height / 2 - - layer.enabled: true - layer.effect: ElevationEffect { - elevation: 1 - } - } - } - - Keys.onUpPressed: scrollBar.decrease() - Keys.onDownPressed: scrollBar.increase() - - ScrollBar.vertical: ScrollBar { - id: scrollBar - } - } - - RoomPanelInput { - Layout.fillWidth: true - - id: roomPanelInput - } } } - Timer { - id: movingTimer - - interval: 10000 - repeat: true - running: false - - onTriggered: saveReadMarker() - } - - function goToEvent(eventID) { - var index = messageEventModel.eventIDToIndex(eventID) - if (index === -1) return - messageListView.positionViewAtIndex(sortedMessageEventModel.mapFromSource(index), ListView.Contain) - } - - function saveViewport() { - currentRoom.saveViewport(sortedMessageEventModel.mapToSource(messageListView.indexAt(messageListView.contentX + (messageListView.width / 2), messageListView.contentY)), sortedMessageEventModel.mapToSource(messageListView.largestVisibleIndex)) - } - - function saveReadMarker() { - var readMarker = sortedMessageEventModel.get(messageListView.largestVisibleIndex).eventId - if (!readMarker) return - currentRoom.readMarkerEventId = readMarker - } + background: Item {} } diff --git a/qml/main.qml b/qml/main.qml index 11a55e4e0..78487a312 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -1,204 +1,51 @@ import QtQuick 2.12 -import QtQuick.Controls 2.12 +import QtQuick.Controls 2.12 as Controls import QtQuick.Layouts 1.12 -import QtQuick.Controls.Material 2.12 -import Qt.labs.settings 1.1 -import Qt.labs.platform 1.1 as Platform -import Spectral.Panel 2.0 -import Spectral.Component 2.0 -import Spectral.Dialog 2.0 -import Spectral.Effect 2.0 +import org.kde.kirigami 2.4 as Kirigami import Spectral 0.1 -import Spectral.Setting 0.1 +import Spectral.Component 2.0 +import Spectral.Panel 2.0 -ApplicationWindow { - readonly property bool inPortrait: window.width < 640 +Kirigami.ApplicationWindow { + id: root - Material.theme: MPalette.theme - Material.background: MPalette.background - - width: 960 - height: 640 - minimumWidth: 480 - minimumHeight: 360 - - id: window - - visible: true - title: qsTr("Spectral") - - font.family: MSettings.fontFamily - - background: Rectangle { - color: MSettings.darkTheme ? "#303030" : "#FFFFFF" - } - - TrayIcon { - id: trayIcon - - visible: MSettings.showTray - - iconSource: ":/assets/img/icon.png" - - isOnline: spectralController.isOnline - - onShowWindow: window.showWindow() - } - - Platform.MenuBar { - id: menuBar - - Platform.Menu { - id: fileMenu - title: "File" - - Platform.MenuItem { - text: "Preferences" - - shortcut: StandardKey.Preferences - role: Platform.MenuItem.PreferencesRole - - onTriggered: accountDetailDialog.createObject(window).open() + globalDrawer: Kirigami.GlobalDrawer { + title: "Hello App" + titleIcon: "applications-graphics" + actions: [ + Kirigami.Action { + text: "View" + iconName: "view-list-icons" + Kirigami.Action { + text: "action 1" + } + Kirigami.Action { + text: "action 2" + } + Kirigami.Action { + text: "action 3" + } + }, + Kirigami.Action { + text: "action 3" } - - Platform.MenuItem { - text: "Quit" - - shortcut: StandardKey.Quit - role: Platform.MenuItem.QuitRole - - onTriggered: Qt.quit() - } - } + ] } + contextDrawer: Kirigami.ContextDrawer { + id: contextDrawer + } + + pageStack.initialPage: roomListPanelComponent + Controller { id: spectralController - quitOnLastWindowClosed: !MSettings.showTray + quitOnLastWindowClosed: true - onErrorOccured: errorControl.show(error + ": " + detail, 3000) - } - - NotificationsManager { - id: notificationsManager - - onNotificationClicked: { - roomListForm.enteredRoom = spectralController.connection.room(roomId) - roomForm.goToEvent(eventId) - showWindow() - } - } - - Shortcut { - sequence: "Ctrl+Q" - context: Qt.ApplicationShortcut - onActivated: Qt.quit() - } - - ToolTip { - id: busyIndicator - - parent: ApplicationWindow.overlay - - visible: spectralController.busy - text: "Loading, please wait" - - font.pixelSize: 14 - } - - ToolTip { - id: errorControl - - parent: ApplicationWindow.overlay - - font.pixelSize: 14 - } - - Component { - id: accountDetailDialog - - AccountDetailDialog {} - } - - Component { - id: loginDialog - - LoginDialog {} - } - - Component { - id: joinRoomDialog - - JoinRoomDialog {} - } - - Component { - id: startChatDialog - - StartChatDialog {} - } - - Component { - id: createRoomDialog - - CreateRoomDialog {} - } - - Component { - id: fontFamilyDialog - - FontFamilyDialog {} - } - - Drawer { - width: Math.min((inPortrait ? 0.67 : 0.3) * window.width, 360) - height: window.height - modal: inPortrait - interactive: inPortrait - position: inPortrait ? 0 : 1 - visible: !inPortrait - - id: roomListDrawer - - RoomListPanel { - anchors.fill: parent - - id: roomListForm - - clip: true - - connection: spectralController.connection - - onLeaveRoom: roomForm.saveViewport() - } - } - - RoomPanel { - anchors.fill: parent - anchors.leftMargin: !inPortrait ? roomListDrawer.width : undefined - anchors.rightMargin: !inPortrait && roomDrawer.visible ? roomDrawer.width : undefined - - id: roomForm - - clip: true - - currentRoom: roomListForm.enteredRoom - } - - RoomDrawer { - width: Math.min((inPortrait ? 0.67 : 0.3) * window.width, 360) - height: window.height - modal: inPortrait - interactive: inPortrait - - edge: Qt.RightEdge - - id: roomDrawer - - room: roomListForm.enteredRoom + onErrorOccured: showPassiveNotification(error + ": " + detail) } Binding { @@ -207,19 +54,34 @@ ApplicationWindow { value: spectralController.connection } - function showWindow() { - window.show() - window.raise() - window.requestActivate() + RoomListModel { + id: spectralRoomListModel + + connection: spectralController.connection } - function hideWindow() { - window.hide() + Component { + id: roomPanelComponent + + RoomPanel { + currentRoom: root.currentRoom + } } - Component.onCompleted: { - spectralController.initiated.connect(function() { - if (spectralController.accountCount == 0) loginDialog.createObject(window).open() - }) + Component { + id: roomListPanelComponent + + RoomListPanel { + roomListModel: spectralRoomListModel + + onEnterRoom: { + applicationWindow().pageStack.push(roomPanelComponent, {"currentRoom": room}) + } + onLeaveRoom: { + var stack = applicationWindow().pageStack; + + stack.removePage(stack.lastItem) + } + } } } diff --git a/qtquickcontrols2.conf b/qtquickcontrols2.conf deleted file mode 100644 index 1cc3690a9..000000000 --- a/qtquickcontrols2.conf +++ /dev/null @@ -1,12 +0,0 @@ -; This file can be edited to change the style of the application -; Read "Qt Quick Controls 2 Configuration File" for details: -; http://doc.qt.io/qt-5/qtquickcontrols2-configuration.html - -[Controls] -Style=Material - -[Material] -Theme=Light -Variant=Dense -Primary=#344955 -Accent=#4286F5 diff --git a/res.qrc b/res.qrc index 9bb99b5b2..871f0b376 100644 --- a/res.qrc +++ b/res.qrc @@ -1,6 +1,5 @@ - qtquickcontrols2.conf qml/main.qml imports/Spectral/Component/Emoji/EmojiPicker.qml imports/Spectral/Component/Emoji/qmldir @@ -12,7 +11,6 @@ imports/Spectral/Component/qmldir imports/Spectral/Effect/ElevationEffect.qml imports/Spectral/Effect/qmldir - assets/font/material.ttf assets/img/icon.png imports/Spectral/Setting/Setting.qml imports/Spectral/Font/MaterialFont.qml @@ -59,5 +57,7 @@ imports/Spectral/Component/Timeline/ReactionDelegate.qml imports/Spectral/Component/Timeline/AudioDelegate.qml imports/Spectral/Dialog/StartChatDialog.qml + imports/Spectral/Component/Timeline/TextDelegate.qml + imports/Spectral/Component/Timeline/TimelineContainer.qml diff --git a/src/matriximageprovider.cpp b/src/matriximageprovider.cpp index e99870f1a..497bf474a 100644 --- a/src/matriximageprovider.cpp +++ b/src/matriximageprovider.cpp @@ -21,6 +21,9 @@ ThumbnailResponse::ThumbnailResponse(Quotient::Connection* c, QString::number(requestedSize.width()), QString::number(requestedSize.height()))), errorStr("Image request hasn't started") { + if (!c) { + return; + } if (requestedSize.isEmpty()) { errorStr.clear(); emit finished(); diff --git a/src/messageeventmodel.cpp b/src/messageeventmodel.cpp index b023f44fc..b8056cf91 100644 --- a/src/messageeventmodel.cpp +++ b/src/messageeventmodel.cpp @@ -34,7 +34,6 @@ QHash MessageEventModel::roleNames() const { roles[UserMarkerRole] = "userMarker"; roles[ShowAuthorRole] = "showAuthor"; roles[ShowSectionRole] = "showSection"; - roles[BubbleShapeRole] = "bubbleShape"; roles[ReactionRole] = "reaction"; return roles; } @@ -83,7 +82,7 @@ void MessageEventModel::setRoom(SpectralRoom* room) { auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1; refreshEventRoles(rowBelowInserted, - {ShowAuthorRole, BubbleShapeRole}); + {ShowAuthorRole}); } for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) @@ -91,10 +90,7 @@ void MessageEventModel::setRoom(SpectralRoom* room) { }); connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] { beginInsertRows({}, 0, 0); }); - connect(m_currentRoom, &Room::pendingEventAdded, this, [=] { - endInsertRows(); - refreshEventRoles(1, {ShowAuthorRole, BubbleShapeRole}); - }); + connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows); connect(m_currentRoom, &Room::pendingEventAboutToMerge, this, [this](RoomEvent*, int i) { if (i == 0) @@ -116,7 +112,7 @@ void MessageEventModel::setRoom(SpectralRoom* room) { refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole}); if (timelineBaseIndex() > 0) // Refresh below, see #312 refreshEventRoles(timelineBaseIndex() - 1, - {ShowAuthorRole, BubbleShapeRole}); + {ShowAuthorRole}); }); connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow); @@ -463,7 +459,7 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { } if (role == ShowAuthorRole) { - for (auto r = row - 1; r >= 0; --r) { + for (auto r = row + 1; r < 0; ++r) { auto i = index(r); if (data(i, SpecialMarksRole) != EventStatus::Hidden) { return data(i, AuthorRole) != data(idx, AuthorRole) || @@ -490,34 +486,6 @@ QVariant MessageEventModel::data(const QModelIndex& idx, int role) const { return true; } - if (role == BubbleShapeRole) { // TODO: Convoluted logic. - int aboveRow = -1; // Invalid - - for (auto r = row + 1; r < rowCount(); ++r) { - auto i = index(r); - if (data(i, SpecialMarksRole) != EventStatus::Hidden) { - aboveRow = r; - break; - } - } - - bool aboveShow, belowShow; - if (aboveRow == -1) { - aboveShow = true; - } else { - aboveShow = data(index(aboveRow), ShowAuthorRole).toBool(); - } - belowShow = data(idx, ShowAuthorRole).toBool(); - - if (aboveShow && belowShow) - return BubbleShapes::NoShape; - if (aboveShow && !belowShow) - return BubbleShapes::BeginShape; - if (belowShow) - return BubbleShapes::EndShape; - return BubbleShapes::MiddleShape; - } - if (role == ReactionRole) { const auto& annotations = m_currentRoom->relatedEvents(evt, EventRelation::Annotation()); diff --git a/src/messageeventmodel.h b/src/messageeventmodel.h index 8b773667e..f10b80b93 100644 --- a/src/messageeventmodel.h +++ b/src/messageeventmodel.h @@ -32,8 +32,6 @@ class MessageEventModel : public QAbstractListModel { ShowAuthorRole, ShowSectionRole, - BubbleShapeRole, - ReactionRole, // For debugging diff --git a/src/spectralroom.cpp b/src/spectralroom.cpp index c21d92c58..72e02e633 100644 --- a/src/spectralroom.cpp +++ b/src/spectralroom.cpp @@ -244,7 +244,7 @@ QString SpectralRoom::eventToString(const RoomEvent& evt, if (removeReply) { htmlBody.remove(utils::removeRichReplyRegex); } - htmlBody.replace(utils::userPillRegExp, "\\1"); + htmlBody.replace(utils::userPillRegExp, "\\1"); htmlBody.replace(utils::strikethroughRegExp, "\\1"); return htmlBody;