First attempt.

This commit is contained in:
Black
2020-02-12 00:37:35 -08:00
parent 29e6933b4f
commit 5c4d0a969d
17 changed files with 561 additions and 1663 deletions

View File

@@ -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 {}
}
}

View File

@@ -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: "<style>pre {white-space: pre-wrap} a{color: " + color + ";} .user-pill{}</style>" + (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: "<style>pre {white-space: pre-wrap} a{color: " + color + ";} .user-pill{}</style>" + (replyVisible ? reply.display : "")
id: contentLabel
color: Kirigami.Theme.textColor
selectionColor: Kirigami.Theme.highlightColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
text: "<style>pre {white-space: pre-wrap} a{color: " + color + ";} .user-pill{}</style>" + 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)
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,23 @@
import QtQuick 2.12
import org.kde.kirigami 2.4 as Kirigami
TextEdit {
text: "<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + 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
}

View File

@@ -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
}
}
}

View File

@@ -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