Refactor delegates
This commit is contained in:
@@ -15,47 +15,62 @@ import NeoChat.Component 1.0
|
|||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
|
|
||||||
Control {
|
TimelineContainer {
|
||||||
id: root
|
id: audioDelegate
|
||||||
|
|
||||||
Layout.fillWidth: true
|
width: ListView.view.width
|
||||||
|
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
innerObject: Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: audioDelegate.bubbleMaxWidth
|
||||||
|
|
||||||
Audio {
|
Audio {
|
||||||
id: audio
|
id: audio
|
||||||
source: currentRoom.urlToMxcUrl(content.url)
|
source: currentRoom.urlToMxcUrl(content.url)
|
||||||
autoLoad: false
|
autoLoad: false
|
||||||
}
|
}
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
TapHandler {
|
||||||
RowLayout {
|
acceptedButtons: Qt.RightButton
|
||||||
ToolButton {
|
onTapped: openFileContext(model, parent)
|
||||||
icon.name: audio.playbackState == Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openFileContext(model, parent)
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: {
|
contentItem: ColumnLayout {
|
||||||
if (audio.playbackState == Audio.PlayingState) {
|
RowLayout {
|
||||||
audio.pause()
|
ToolButton {
|
||||||
} else {
|
icon.name: audio.playbackState == Audio.PlayingState ? "media-playback-pause" : "media-playback-start"
|
||||||
audio.play()
|
|
||||||
|
onClicked: {
|
||||||
|
if (audio.playbackState == Audio.PlayingState) {
|
||||||
|
audio.pause()
|
||||||
|
} else {
|
||||||
|
audio.play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Label {
|
||||||
|
text: model.display
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Label {
|
RowLayout {
|
||||||
text: model.display
|
visible: audio.hasAudio
|
||||||
}
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
}
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
RowLayout {
|
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
||||||
visible: audio.hasAudio
|
ProgressBar {
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
from: 0
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
to: audio.duration
|
||||||
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
value: audio.position
|
||||||
ProgressBar {
|
}
|
||||||
from: 0
|
|
||||||
to: audio.duration
|
|
||||||
value: audio.position
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: Controller.formatDuration(audio.position) + "/" + Controller.formatDuration(audio.duration)
|
text: Controller.formatDuration(audio.position) + "/" + Controller.formatDuration(audio.duration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,16 +3,23 @@
|
|||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15
|
import QtQuick.Controls 2.15
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
TextEdit {
|
TimelineContainer {
|
||||||
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
|
id: encryptedDelegate
|
||||||
color: Kirigami.Theme.disabledTextColor
|
width: ListView.view.width
|
||||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
|
||||||
selectByMouse: !Kirigami.Settings.isMobile
|
innerObject: TextEdit {
|
||||||
readOnly: true
|
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
|
||||||
wrapMode: Text.WordWrap
|
color: Kirigami.Theme.disabledTextColor
|
||||||
textFormat: Text.RichText
|
font.pointSize: Kirigami.Theme.defaultFont.pointSize
|
||||||
|
selectByMouse: !Kirigami.Settings.isMobile
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
textFormat: Text.RichText
|
||||||
|
Layout.maximumWidth: encryptedDelegate.bubbleMaxWidth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
imports/NeoChat/Component/Timeline/EventDelegate.qml
Normal file
79
imports/NeoChat/Component/Timeline/EventDelegate.qml
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import Qt.labs.qmlmodels 1.0
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
DelegateChooser {
|
||||||
|
role: "eventType"
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "state"
|
||||||
|
delegate: StateDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "emote"
|
||||||
|
delegate: MessageDelegate {
|
||||||
|
isEmote: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "message"
|
||||||
|
delegate: MessageDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "notice"
|
||||||
|
delegate: MessageDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "image"
|
||||||
|
delegate: ImageDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "sticker"
|
||||||
|
delegate: ImageDelegate {
|
||||||
|
cardBackground: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "audio"
|
||||||
|
delegate: AudioDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "video"
|
||||||
|
delegate: VideoDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "file"
|
||||||
|
delegate: FileDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "encrypted"
|
||||||
|
delegate: EncryptedDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "readMarker"
|
||||||
|
delegate: ReadMarkerDelegate {}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "other"
|
||||||
|
delegate: Item {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,113 +14,131 @@ import NeoChat.Component 1.0
|
|||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
|
|
||||||
RowLayout {
|
TimelineContainer {
|
||||||
id: root
|
id: fileDelegate
|
||||||
property bool openOnFinished: false
|
width: ListView.view.width
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
|
||||||
|
|
||||||
Layout.margins: Kirigami.Units.largeSpacing
|
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
innerObject: RowLayout {
|
||||||
|
property bool openOnFinished: false
|
||||||
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
states: [
|
|
||||||
State {
|
|
||||||
name: "downloaded"
|
|
||||||
when: progressInfo.completed
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: downloadButton
|
|
||||||
|
|
||||||
icon.name: "document-open"
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
|
||||||
|
|
||||||
onClicked: openSavedFile()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "downloading"
|
|
||||||
when: progressInfo.active
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: sizeLabel
|
|
||||||
text: i18nc("file download progress", "%1 / %2", Controller.formatByteSize(progressInfo.progress), Controller.formatByteSize(progressInfo.total))
|
|
||||||
}
|
|
||||||
PropertyChanges {
|
|
||||||
target: downloadButton
|
|
||||||
icon.name: "media-playback-stop"
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
|
||||||
onClicked: currentRoom.cancelFileTransfer(eventId)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
State {
|
|
||||||
name: "raw"
|
|
||||||
when: true
|
|
||||||
|
|
||||||
PropertyChanges {
|
|
||||||
target: downloadButton
|
|
||||||
|
|
||||||
onClicked: root.saveFileAs()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
Kirigami.Icon {
|
|
||||||
id: ikon
|
|
||||||
source: model.fileMimetypeIcon
|
|
||||||
fallback: "unknown"
|
|
||||||
}
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: fileDelegate.bubbleMaxWidth
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
spacing: 0
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
QQC2.Label {
|
states: [
|
||||||
text: model.display
|
State {
|
||||||
wrapMode: Text.Wrap
|
name: "downloaded"
|
||||||
|
when: progressInfo.completed
|
||||||
|
|
||||||
Layout.fillWidth: true
|
PropertyChanges {
|
||||||
|
target: downloadButton
|
||||||
|
|
||||||
|
icon.name: "document-open"
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
||||||
|
|
||||||
|
onClicked: openSavedFile()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "downloading"
|
||||||
|
when: progressInfo.active
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
target: sizeLabel
|
||||||
|
text: i18nc("file download progress", "%1 / %2", Controller.formatByteSize(progressInfo.progress), Controller.formatByteSize(progressInfo.total))
|
||||||
|
}
|
||||||
|
PropertyChanges {
|
||||||
|
target: downloadButton
|
||||||
|
icon.name: "media-playback-stop"
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
||||||
|
onClicked: currentRoom.cancelFileTransfer(eventId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State {
|
||||||
|
name: "raw"
|
||||||
|
when: true
|
||||||
|
|
||||||
|
PropertyChanges {
|
||||||
|
target: downloadButton
|
||||||
|
|
||||||
|
onClicked: root.saveFileAs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
id: ikon
|
||||||
|
source: model.fileMimetypeIcon
|
||||||
|
fallback: "unknown"
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
ColumnLayout {
|
||||||
id: sizeLabel
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
|
||||||
opacity: 0.7
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.Button {
|
spacing: 0
|
||||||
id: downloadButton
|
|
||||||
icon.name: "download"
|
|
||||||
|
|
||||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
QQC2.Label {
|
||||||
QQC2.ToolTip.visible: hovered
|
text: model.display
|
||||||
}
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
Component {
|
Layout.fillWidth: true
|
||||||
id: fileDialog
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
id: sizeLabel
|
||||||
|
|
||||||
FileDialog {
|
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
||||||
fileMode: FileDialog.SaveFile
|
opacity: 0.7
|
||||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
|
||||||
onAccepted: {
|
Layout.fillWidth: true
|
||||||
currentRoom.downloadFile(eventId, file)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function saveFileAs() {
|
QQC2.Button {
|
||||||
var dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
id: downloadButton
|
||||||
dialog.open()
|
icon.name: "download"
|
||||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSavedFile() {
|
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
QQC2.ToolTip.visible: hovered
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: fileDialog
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
fileMode: FileDialog.SaveFile
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||||
|
onAccepted: {
|
||||||
|
currentRoom.downloadFile(eventId, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openFileContext(model, parent)
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openFileContext(model, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveFileAs() {
|
||||||
|
var dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||||
|
dialog.open()
|
||||||
|
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSavedFile() {
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,88 +12,116 @@ import NeoChat.Component 1.0
|
|||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
|
|
||||||
Image {
|
|
||||||
id: img
|
|
||||||
|
|
||||||
property var content: model.content
|
TimelineContainer {
|
||||||
readonly property bool isAnimated: contentType === "image/gif"
|
id: imageDelegate
|
||||||
|
|
||||||
property bool openOnFinished: false
|
width: ListView.view.width
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
|
||||||
|
|
||||||
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
|
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||||
// readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
hoverComponent: hoverActions
|
||||||
readonly property var info: content.info
|
|
||||||
readonly property string mediaId: isThumbnail ? content.thumbnailMediaId : content.mediaId
|
|
||||||
property bool readonly: false
|
|
||||||
|
|
||||||
source: "image://mxc/" + mediaId
|
innerObject: Image {
|
||||||
|
id: img
|
||||||
|
|
||||||
Image {
|
property var content: model.content
|
||||||
anchors.fill: parent
|
readonly property bool isAnimated: contentType === "image/gif"
|
||||||
source: content.info["xyz.amorgan.blurhash"] ? ("image://blurhash/" + content.info["xyz.amorgan.blurhash"]) : ""
|
|
||||||
visible: parent.status !== Image.Ready
|
|
||||||
}
|
|
||||||
|
|
||||||
fillMode: Image.PreserveAspectFit
|
property bool openOnFinished: false
|
||||||
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
ToolTip.text: display
|
readonly property bool isThumbnail: !(content.info.thumbnail_info == null || content.thumbnailMediaId == null)
|
||||||
ToolTip.visible: hoverHandler.hovered
|
// 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
|
||||||
|
|
||||||
HoverHandler {
|
Layout.maximumWidth: imageDelegate.bubbleMaxWidth
|
||||||
id: hoverHandler
|
source: "image://mxc/" + mediaId
|
||||||
enabled: img.readonly
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Image {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
source: content.info["xyz.amorgan.blurhash"] ? ("image://blurhash/" + content.info["xyz.amorgan.blurhash"]) : ""
|
||||||
visible: progressInfo.active && !downloaded
|
visible: parent.status !== Image.Ready
|
||||||
|
|
||||||
color: "#BB000000"
|
|
||||||
|
|
||||||
ProgressBar {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
width: parent.width * 0.8
|
|
||||||
|
|
||||||
from: 0
|
|
||||||
to: progressInfo.total
|
|
||||||
value: progressInfo.progress
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function saveFileAs() {
|
fillMode: Image.PreserveAspectFit
|
||||||
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
|
|
||||||
dialog.open()
|
|
||||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
ToolTip.text: display
|
||||||
id: fileDialog
|
ToolTip.visible: hoverHandler.hovered
|
||||||
|
|
||||||
FileDialog {
|
HoverHandler {
|
||||||
fileMode: FileDialog.SaveFile
|
id: hoverHandler
|
||||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
}
|
||||||
onAccepted: {
|
|
||||||
currentRoom.downloadFile(eventId, file)
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndOpen()
|
function saveFileAs() {
|
||||||
{
|
var dialog = fileDialog.createObject(ApplicationWindow.overlay)
|
||||||
if (downloaded) openSavedFile()
|
dialog.open()
|
||||||
else
|
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: fileDialog
|
||||||
|
|
||||||
|
FileDialog {
|
||||||
|
fileMode: FileDialog.SaveFile
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||||
|
onAccepted: {
|
||||||
|
currentRoom.downloadFile(eventId, file)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openFileContext(model, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openFileContext(model, parent)
|
||||||
|
onTapped: {
|
||||||
|
fullScreenImage.createObject(parent, {
|
||||||
|
filename: eventId,
|
||||||
|
localPath: currentRoom.urlToDownload(eventId),
|
||||||
|
blurhash: model.content.info["xyz.amorgan.blurhash"],
|
||||||
|
imageWidth: content.info.w,
|
||||||
|
imageHeight: content.info.h
|
||||||
|
}).showFullScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadAndOpen()
|
||||||
{
|
{
|
||||||
openOnFinished = true
|
if (downloaded) openSavedFile()
|
||||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
else
|
||||||
|
{
|
||||||
|
openOnFinished = true
|
||||||
|
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSavedFile()
|
||||||
|
{
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSavedFile()
|
|
||||||
{
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
imports/NeoChat/Component/Timeline/MessageDelegate.qml
Normal file
27
imports/NeoChat/Component/Timeline/MessageDelegate.qml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import Qt.labs.qmlmodels 1.0
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
TimelineContainer {
|
||||||
|
id: messageDelegate
|
||||||
|
|
||||||
|
width: ListView.view.width
|
||||||
|
property bool isEmote: false
|
||||||
|
|
||||||
|
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
|
innerObject: TextDelegate {
|
||||||
|
isEmote: messageDelegate.isEmote
|
||||||
|
Layout.maximumWidth: messageDelegate.bubbleMaxWidth
|
||||||
|
onRequestOpenMessageContext: openMessageContext(model, parent.selectedText)
|
||||||
|
}
|
||||||
|
}
|
||||||
78
imports/NeoChat/Component/Timeline/ReadMarkerDelegate.qml
Normal file
78
imports/NeoChat/Component/Timeline/ReadMarkerDelegate.qml
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import Qt.labs.qmlmodels 1.0
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
QQC2.ItemDelegate {
|
||||||
|
padding: Kirigami.Units.largeSpacing
|
||||||
|
topInset: Kirigami.Units.largeSpacing
|
||||||
|
topPadding: Kirigami.Units.largeSpacing * 2
|
||||||
|
width: ListView.view.width - Kirigami.Units.gridUnit
|
||||||
|
x: Kirigami.Units.gridUnit / 2
|
||||||
|
contentItem: QQC2.Label {
|
||||||
|
text: i18nc("Relative time since the room was last read", "Last read: %1", time)
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
opacity: 0.6
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: makeMeDisapearTimer
|
||||||
|
interval: Kirigami.Units.humanMoment * 2
|
||||||
|
onTriggered: if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
|
||||||
|
currentRoom.markAllMessagesAsRead();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView.onPooled: makeMeDisapearTimer.stop()
|
||||||
|
|
||||||
|
ListView.onAdd: {
|
||||||
|
const view = ListView.view;
|
||||||
|
if (view.atYEnd) {
|
||||||
|
makeMeDisapearTimer.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the read marker is visible and we are at the end of the list,
|
||||||
|
// start the makeMeDisapearTimer
|
||||||
|
Connections {
|
||||||
|
target: ListView.view
|
||||||
|
function onAtYEndChanged() {
|
||||||
|
makeMeDisapearTimer.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
ListView.onRemove: {
|
||||||
|
const view = ListView.view;
|
||||||
|
|
||||||
|
if (view.atYEnd) {
|
||||||
|
// easy case just mark everything as read
|
||||||
|
if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
|
||||||
|
currentRoom.markAllMessagesAsRead();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// mark the last visible index
|
||||||
|
const lastVisibleIdx = lastVisibleIndex();
|
||||||
|
|
||||||
|
if (lastVisibleIdx < index) {
|
||||||
|
currentRoom.readMarkerEventId = sortedMessageEventModel.data(sortedMessageEventModel.index(lastVisibleIdx, 0), MessageEventModel.EventIdRole)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,9 @@ import NeoChat.Component 1.0
|
|||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
id: row
|
x: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
height: label.contentHeight
|
height: label.contentHeight
|
||||||
|
width: ListView.view.width - Kirigami.Units.largeSpacing - x
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
id: icon
|
id: icon
|
||||||
|
|||||||
@@ -15,130 +15,147 @@ import NeoChat.Component 1.0
|
|||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import NeoChat.Menu.Timeline 1.0
|
import NeoChat.Menu.Timeline 1.0
|
||||||
|
|
||||||
Video {
|
TimelineContainer {
|
||||||
id: vid
|
id: videoDelegate
|
||||||
|
|
||||||
property bool playOnFinished: false
|
width: ListView.view.width
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
|
||||||
|
|
||||||
property bool supportStreaming: true
|
onReplyClicked: ListView.view.goToEvent(eventID)
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
onDownloadedChanged: {
|
innerObject: Video {
|
||||||
if (downloaded) {
|
id: vid
|
||||||
vid.source = progressInfo.localPath
|
|
||||||
|
property bool playOnFinished: false
|
||||||
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
|
property bool supportStreaming: true
|
||||||
|
|
||||||
|
Layout.maximumWidth: videoDelegate.bubbleMaxWidth
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
|
||||||
|
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
||||||
|
|
||||||
|
onDownloadedChanged: {
|
||||||
|
if (downloaded) {
|
||||||
|
vid.source = progressInfo.localPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if (downloaded && playOnFinished) {
|
||||||
|
playSavedFile()
|
||||||
|
playOnFinished = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloaded && playOnFinished) {
|
readonly property int maxWidth: 1000 // TODO messageListView.width
|
||||||
playSavedFile()
|
|
||||||
playOnFinished = false
|
Layout.preferredWidth: content.info.w > maxWidth ? maxWidth : content.info.w
|
||||||
|
Layout.preferredHeight: content.info.w > maxWidth ? (content.info.h / content.info.w * maxWidth) : content.info.h
|
||||||
|
|
||||||
|
loops: MediaPlayer.Infinite
|
||||||
|
|
||||||
|
fillMode: VideoOutput.PreserveAspectFit
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
if (downloaded) {
|
||||||
|
source = progressInfo.localPath
|
||||||
|
} else {
|
||||||
|
source = currentRoom.urlToMxcUrl(content.url)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
onDurationChanged: {
|
||||||
readonly property int maxWidth: 1000 // TODO messageListView.width
|
if (!duration) {
|
||||||
|
vid.supportStreaming = false;
|
||||||
Layout.preferredWidth: content.info.w > maxWidth ? maxWidth : content.info.w
|
}
|
||||||
Layout.preferredHeight: content.info.w > maxWidth ? (content.info.h / content.info.w * maxWidth) : content.info.h
|
|
||||||
|
|
||||||
loops: MediaPlayer.Infinite
|
|
||||||
|
|
||||||
fillMode: VideoOutput.PreserveAspectFit
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
if (downloaded) {
|
|
||||||
source = progressInfo.localPath
|
|
||||||
} else {
|
|
||||||
source = currentRoom.urlToMxcUrl(content.url)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onDurationChanged: {
|
onErrorChanged: {
|
||||||
if (!duration) {
|
if (error != MediaPlayer.NoError) {
|
||||||
supportStreaming = false;
|
vid.supportStreaming = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onErrorChanged: {
|
Image {
|
||||||
if (error != MediaPlayer.NoError) {
|
anchors.fill: parent
|
||||||
supportStreaming = false;
|
|
||||||
|
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
|
||||||
|
|
||||||
|
source: "image://mxc/" + content.thumbnailMediaId
|
||||||
|
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Image {
|
Label {
|
||||||
readonly property bool isThumbnail: content.info.thumbnail_info && content.thumbnailMediaId
|
|
||||||
readonly property var info: isThumbnail ? content.info.thumbnail_info : content.info
|
|
||||||
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
visible: isThumbnail && (vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError)
|
|
||||||
|
|
||||||
source: "image://mxc/" + (isThumbnail ? content.thumbnailMediaId : "")
|
|
||||||
|
|
||||||
sourceSize.width: info.w
|
|
||||||
sourceSize.height: info.h
|
|
||||||
|
|
||||||
fillMode: Image.PreserveAspectFit
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
|
|
||||||
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
|
|
||||||
color: "white"
|
|
||||||
text: i18n("Video")
|
|
||||||
font.pixelSize: 16
|
|
||||||
|
|
||||||
padding: 8
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
radius: Kirigami.Units.smallSpacing
|
|
||||||
color: "black"
|
|
||||||
opacity: 0.3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
visible: progressInfo.active && !downloaded
|
|
||||||
|
|
||||||
color: "#BB000000"
|
|
||||||
|
|
||||||
ProgressBar {
|
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
width: parent.width * 0.8
|
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
|
||||||
|
color: "white"
|
||||||
|
text: i18n("Video")
|
||||||
|
font.pixelSize: 16
|
||||||
|
|
||||||
from: 0
|
padding: 8
|
||||||
to: progressInfo.total
|
|
||||||
value: progressInfo.progress
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TapHandler {
|
background: Rectangle {
|
||||||
acceptedButtons: Qt.LeftButton
|
radius: Kirigami.Units.smallSpacing
|
||||||
onTapped: if (supportStreaming || progressInfo.completed) {
|
color: "black"
|
||||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
opacity: 0.3
|
||||||
vid.pause()
|
|
||||||
} else {
|
|
||||||
vid.play()
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
downloadAndPlay()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndPlay() {
|
Rectangle {
|
||||||
if (downloaded) {
|
anchors.fill: parent
|
||||||
playSavedFile()
|
|
||||||
} else {
|
visible: progressInfo.active && !vid.downloaded
|
||||||
playOnFinished = true
|
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
color: "#BB000000"
|
||||||
|
|
||||||
|
ProgressBar {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
width: parent.width * 0.8
|
||||||
|
|
||||||
|
from: 0
|
||||||
|
to: progressInfo.total
|
||||||
|
value: progressInfo.progress
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function playSavedFile() {
|
TapHandler {
|
||||||
vid.stop()
|
acceptedButtons: Qt.LeftButton
|
||||||
vid.play()
|
onTapped: if (vid.supportStreaming || progressInfo.completed) {
|
||||||
|
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||||
|
vid.pause()
|
||||||
|
} else {
|
||||||
|
vid.play()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vid.downloadAndPlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openFileContext(model, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openFileContext(model, parent)
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadAndPlay() {
|
||||||
|
if (vid.downloaded) {
|
||||||
|
playSavedFile()
|
||||||
|
} else {
|
||||||
|
playOnFinished = true
|
||||||
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function playSavedFile() {
|
||||||
|
vid.stop()
|
||||||
|
vid.play()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ VideoDelegate 1.0 VideoDelegate.qml
|
|||||||
ReactionDelegate 1.0 ReactionDelegate.qml
|
ReactionDelegate 1.0 ReactionDelegate.qml
|
||||||
AudioDelegate 1.0 AudioDelegate.qml
|
AudioDelegate 1.0 AudioDelegate.qml
|
||||||
EncryptedDelegate 1.0 EncryptedDelegate.qml
|
EncryptedDelegate 1.0 EncryptedDelegate.qml
|
||||||
|
EventDelegate 1.0 EventDelegate.qml
|
||||||
|
MessageDelegate 1.0 MessageDelegate.qml
|
||||||
|
ReadMarkerDelegate 1.0 ReadMarkerDelegate.qml
|
||||||
|
|||||||
@@ -341,282 +341,7 @@ Kirigami.ScrollablePage {
|
|||||||
sourceModel: messageEventModel
|
sourceModel: messageEventModel
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: DelegateChooser {
|
delegate: EventDelegate {}
|
||||||
id: timelineDelegateChooser
|
|
||||||
role: "eventType"
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "state"
|
|
||||||
delegate: QQC2.Control {
|
|
||||||
leftPadding: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
height: contentItem.height
|
|
||||||
contentItem: StateDelegate { }
|
|
||||||
implicitWidth: messageListView.width - Kirigami.Units.largeSpacing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "emote"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: emoteContainer
|
|
||||||
width: messageListView.width
|
|
||||||
isEmote: true
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
hoverComponent: hoverActions
|
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
|
||||||
isEmote: true
|
|
||||||
Layout.maximumWidth: emoteContainer.bubbleMaxWidth
|
|
||||||
onRequestOpenMessageContext: openMessageContext(model, parent.selectedText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "message"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: messageContainer
|
|
||||||
width: messageListView.width
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
hoverComponent: hoverActions
|
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
|
||||||
Layout.maximumWidth: messageContainer.bubbleMaxWidth
|
|
||||||
onRequestOpenMessageContext: openMessageContext(model, parent.selectedText)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "notice"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: noticeContainer
|
|
||||||
width: messageListView.width
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
|
||||||
Layout.fillWidth: !Config.compactLayout
|
|
||||||
hasContextMenu: false
|
|
||||||
Layout.maximumWidth: noticeContainer.bubbleMaxWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "image"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: imageContainer
|
|
||||||
width: messageListView.width
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
hoverComponent: hoverActions
|
|
||||||
|
|
||||||
innerObject: ImageDelegate {
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 15
|
|
||||||
Layout.maximumWidth: imageContainer.bubbleMaxWidth
|
|
||||||
Layout.preferredHeight: info.h / info.w * width
|
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 20
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onTapped: openFileContext(model, parent)
|
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onLongPressed: openFileContext(model, parent)
|
|
||||||
onTapped: {
|
|
||||||
fullScreenImage.createObject(parent, {
|
|
||||||
filename: eventId,
|
|
||||||
localPath: currentRoom.urlToDownload(eventId),
|
|
||||||
blurhash: model.content.info["xyz.amorgan.blurhash"],
|
|
||||||
imageWidth: content.info.w,
|
|
||||||
imageHeight: content.info.h
|
|
||||||
}).showFullScreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "sticker"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
width: messageListView.width
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
hoverComponent: hoverActions
|
|
||||||
cardBackground: false
|
|
||||||
|
|
||||||
innerObject: ImageDelegate {
|
|
||||||
readonly: true
|
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 10
|
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 10
|
|
||||||
Layout.preferredHeight: info.h / info.w * width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "audio"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: audioContainer
|
|
||||||
width: messageListView.width
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
hoverComponent: hoverActions
|
|
||||||
|
|
||||||
innerObject: AudioDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: audioContainer.bubbleMaxWidth
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onTapped: openFileContext(model, parent)
|
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onLongPressed: openFileContext(model, parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "video"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: videoContainer
|
|
||||||
width: messageListView.width
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
hoverComponent: hoverActions
|
|
||||||
|
|
||||||
innerObject: VideoDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: videoContainer.bubbleMaxWidth
|
|
||||||
Layout.preferredHeight: content.info.h / content.info.w * width
|
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
|
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
|
||||||
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onTapped: openFileContext(model, parent)
|
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onLongPressed: openFileContext(model, parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "file"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: fileContainer
|
|
||||||
width: messageListView.width
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
|
|
||||||
innerObject: FileDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: fileContainer.bubbleMaxWidth
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.RightButton
|
|
||||||
onTapped: openFileContext(model, parent)
|
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onLongPressed: openFileContext(model, parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "encrypted"
|
|
||||||
delegate: TimelineContainer {
|
|
||||||
id: encryptedContainer
|
|
||||||
width: messageListView.width
|
|
||||||
|
|
||||||
innerObject: EncryptedDelegate {
|
|
||||||
Layout.fillWidth: Config.compactLayout
|
|
||||||
Layout.maximumWidth: encryptedContainer.bubbleMaxWidth
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.leftMargin: Config.showAvatarInTimeline ? Kirigami.Units.largeSpacing : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "readMarker"
|
|
||||||
delegate: QQC2.ItemDelegate {
|
|
||||||
padding: Kirigami.Units.largeSpacing
|
|
||||||
topInset: Kirigami.Units.largeSpacing
|
|
||||||
topPadding: Kirigami.Units.largeSpacing * 2
|
|
||||||
width: ListView.view.width - Kirigami.Units.gridUnit
|
|
||||||
x: Kirigami.Units.gridUnit / 2
|
|
||||||
contentItem: QQC2.Label {
|
|
||||||
text: i18nc("Relative time since the room was last read", "Last read: %1", time)
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
opacity: 0.6
|
|
||||||
radius: Kirigami.Units.smallSpacing
|
|
||||||
shadow.size: Kirigami.Units.smallSpacing
|
|
||||||
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
|
||||||
border.width: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: makeMeDisapearTimer
|
|
||||||
interval: Kirigami.Units.humanMoment * 2
|
|
||||||
onTriggered: if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
|
|
||||||
currentRoom.markAllMessagesAsRead();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ListView.onPooled: makeMeDisapearTimer.stop()
|
|
||||||
|
|
||||||
ListView.onAdd: {
|
|
||||||
const view = ListView.view;
|
|
||||||
if (view.atYEnd) {
|
|
||||||
makeMeDisapearTimer.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the read marker is visible and we are at the end of the list,
|
|
||||||
// start the makeMeDisapearTimer
|
|
||||||
Connections {
|
|
||||||
target: ListView.view
|
|
||||||
function onAtYEndChanged() {
|
|
||||||
makeMeDisapearTimer.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
ListView.onRemove: {
|
|
||||||
const view = ListView.view;
|
|
||||||
|
|
||||||
if (view.atYEnd) {
|
|
||||||
// easy case just mark everything as read
|
|
||||||
if (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden) {
|
|
||||||
currentRoom.markAllMessagesAsRead();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// mark the last visible index
|
|
||||||
const lastVisibleIdx = lastVisibleIndex();
|
|
||||||
|
|
||||||
if (lastVisibleIdx < index) {
|
|
||||||
currentRoom.readMarkerEventId = sortedMessageEventModel.data(sortedMessageEventModel.index(lastVisibleIdx, 0), MessageEventModel.EventIdRole)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
DelegateChoice {
|
|
||||||
roleValue: "other"
|
|
||||||
delegate: Item {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.RoundButton {
|
QQC2.RoundButton {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
@@ -631,7 +356,7 @@ Kirigami.ScrollablePage {
|
|||||||
visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded
|
visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
goToEvent(currentRoom.readMarkerEventId)
|
messageListView.goToEvent(currentRoom.readMarkerEventId)
|
||||||
}
|
}
|
||||||
icon.name: "go-up"
|
icon.name: "go-up"
|
||||||
}
|
}
|
||||||
@@ -739,6 +464,9 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
headerPositioning: ListView.OverlayHeader
|
headerPositioning: ListView.OverlayHeader
|
||||||
|
|
||||||
|
function goToEvent(eventID) {
|
||||||
|
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -820,10 +548,6 @@ Kirigami.ScrollablePage {
|
|||||||
messageListView.positionViewAtIndex(0, ListView.End)
|
messageListView.positionViewAtIndex(0, ListView.End)
|
||||||
}
|
}
|
||||||
|
|
||||||
function goToEvent(eventID) {
|
|
||||||
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
|
|
||||||
}
|
|
||||||
|
|
||||||
function eventToIndex(eventID) {
|
function eventToIndex(eventID) {
|
||||||
const index = messageEventModel.eventIDToIndex(eventID)
|
const index = messageEventModel.eventIDToIndex(eventID)
|
||||||
if (index === -1)
|
if (index === -1)
|
||||||
|
|||||||
3
res.qrc
3
res.qrc
@@ -42,6 +42,9 @@
|
|||||||
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/ImageDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/EncryptedDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/EncryptedDelegate.qml</file>
|
||||||
|
<file>imports/NeoChat/Component/Timeline/EventDelegate.qml</file>
|
||||||
|
<file>imports/NeoChat/Component/Timeline/MessageDelegate.qml</file>
|
||||||
|
<file>imports/NeoChat/Component/Timeline/ReadMarkerDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Login/qmldir</file>
|
<file>imports/NeoChat/Component/Login/qmldir</file>
|
||||||
<file>imports/NeoChat/Component/Login/LoginStep.qml</file>
|
<file>imports/NeoChat/Component/Login/LoginStep.qml</file>
|
||||||
<file>imports/NeoChat/Component/Login/Login.qml</file>
|
<file>imports/NeoChat/Component/Login/Login.qml</file>
|
||||||
|
|||||||
@@ -423,8 +423,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
const KFormat format;
|
const KFormat format;
|
||||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||||
}
|
}
|
||||||
case SpecialMarksRole:
|
|
||||||
return EventStatus::Hidden;
|
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user