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;