Refactor delegates

This commit is contained in:
Tobias Fella
2021-12-14 22:27:29 +00:00
parent ff707b7a58
commit 599ab11656
13 changed files with 583 additions and 586 deletions

View File

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

View File

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

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

View File

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

View File

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

View 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)
}
}

View 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)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View 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 {};
} }