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,10 +15,15 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
Control {
id: root
TimelineContainer {
id: audioDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: Control {
Layout.fillWidth: true
Layout.maximumWidth: audioDelegate.bubbleMaxWidth
Audio {
id: audio
@@ -26,6 +31,15 @@ Control {
autoLoad: false
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
}
contentItem: ColumnLayout {
RowLayout {
ToolButton {
@@ -59,4 +73,5 @@ Control {
}
}
}
}
}

View File

@@ -3,11 +3,16 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
TextEdit {
TimelineContainer {
id: encryptedDelegate
width: ListView.view.width
innerObject: TextEdit {
text: i18n("This message is encrypted and the sender has not shared the key with this device.")
color: Kirigami.Theme.disabledTextColor
font.pointSize: Kirigami.Theme.defaultFont.pointSize
@@ -15,4 +20,6 @@ TextEdit {
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,11 +14,19 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
RowLayout {
id: root
TimelineContainer {
id: fileDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: RowLayout {
property bool openOnFinished: false
readonly property bool downloaded: progressInfo && progressInfo.completed
Layout.fillWidth: true
Layout.maximumWidth: fileDelegate.bubbleMaxWidth
Layout.margins: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
@@ -113,6 +121,15 @@ RowLayout {
}
}
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()
@@ -123,4 +140,5 @@ RowLayout {
if (Qt.openUrlExternally(progressInfo.localPath)) return;
if (Qt.openUrlExternally(progressInfo.localDir)) return;
}
}
}

View File

@@ -12,7 +12,16 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
Image {
TimelineContainer {
id: imageDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: Image {
id: img
property var content: model.content
@@ -25,8 +34,8 @@ Image {
// 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
property bool readonly: false
Layout.maximumWidth: imageDelegate.bubbleMaxWidth
source: "image://mxc/" + mediaId
Image {
@@ -42,7 +51,6 @@ Image {
HoverHandler {
id: hoverHandler
enabled: img.readonly
}
Rectangle {
@@ -81,6 +89,25 @@ Image {
}
}
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()
{
if (downloaded) openSavedFile()
@@ -96,4 +123,5 @@ Image {
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
RowLayout {
id: row
x: Kirigami.Units.gridUnit * 1.5 + Kirigami.Units.smallSpacing
height: label.contentHeight
width: ListView.view.width - Kirigami.Units.largeSpacing - x
Kirigami.Avatar {
id: icon

View File

@@ -15,7 +15,15 @@ import NeoChat.Component 1.0
import NeoChat.Dialog 1.0
import NeoChat.Menu.Timeline 1.0
Video {
TimelineContainer {
id: videoDelegate
width: ListView.view.width
onReplyClicked: ListView.view.goToEvent(eventID)
hoverComponent: hoverActions
innerObject: Video {
id: vid
property bool playOnFinished: false
@@ -23,6 +31,11 @@ Video {
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
@@ -34,7 +47,6 @@ Video {
}
}
readonly property int maxWidth: 1000 // TODO messageListView.width
Layout.preferredWidth: content.info.w > maxWidth ? maxWidth : content.info.w
@@ -54,28 +66,22 @@ Video {
onDurationChanged: {
if (!duration) {
supportStreaming = false;
vid.supportStreaming = false;
}
}
onErrorChanged: {
if (error != MediaPlayer.NoError) {
supportStreaming = false;
vid.supportStreaming = false;
}
}
Image {
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)
visible: vid.playbackState == MediaPlayer.StoppedState || vid.error != MediaPlayer.NoError
source: "image://mxc/" + (isThumbnail ? content.thumbnailMediaId : "")
sourceSize.width: info.w
sourceSize.height: info.h
source: "image://mxc/" + content.thumbnailMediaId
fillMode: Image.PreserveAspectFit
}
@@ -100,7 +106,7 @@ Video {
Rectangle {
anchors.fill: parent
visible: progressInfo.active && !downloaded
visible: progressInfo.active && !vid.downloaded
color: "#BB000000"
@@ -117,19 +123,29 @@ Video {
TapHandler {
acceptedButtons: Qt.LeftButton
onTapped: if (supportStreaming || progressInfo.completed) {
onTapped: if (vid.supportStreaming || progressInfo.completed) {
if (vid.playbackState == MediaPlayer.PlayingState) {
vid.pause()
} else {
vid.play()
}
} else {
downloadAndPlay()
vid.downloadAndPlay()
}
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: openFileContext(model, parent)
}
TapHandler {
acceptedButtons: Qt.LeftButton
onLongPressed: openFileContext(model, parent)
}
function downloadAndPlay() {
if (downloaded) {
if (vid.downloaded) {
playSavedFile()
} else {
playOnFinished = true
@@ -141,4 +157,5 @@ Video {
vid.stop()
vid.play()
}
}
}

View File

@@ -9,3 +9,6 @@ VideoDelegate 1.0 VideoDelegate.qml
ReactionDelegate 1.0 ReactionDelegate.qml
AudioDelegate 1.0 AudioDelegate.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
}
delegate: DelegateChooser {
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 {}
}
}
delegate: EventDelegate {}
QQC2.RoundButton {
anchors.right: parent.right
@@ -631,7 +356,7 @@ Kirigami.ScrollablePage {
visible: currentRoom && currentRoom.hasUnreadMessages && currentRoom.readMarkerLoaded
action: Kirigami.Action {
onTriggered: {
goToEvent(currentRoom.readMarkerEventId)
messageListView.goToEvent(currentRoom.readMarkerEventId)
}
icon.name: "go-up"
}
@@ -739,6 +464,9 @@ Kirigami.ScrollablePage {
}
headerPositioning: ListView.OverlayHeader
function goToEvent(eventID) {
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
}
}
@@ -820,10 +548,6 @@ Kirigami.ScrollablePage {
messageListView.positionViewAtIndex(0, ListView.End)
}
function goToEvent(eventID) {
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
}
function eventToIndex(eventID) {
const index = messageEventModel.eventIDToIndex(eventID)
if (index === -1)

View File

@@ -42,6 +42,9 @@
<file>imports/NeoChat/Component/Timeline/FileDelegate.qml</file>
<file>imports/NeoChat/Component/Timeline/ImageDelegate.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/LoginStep.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;
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
}
case SpecialMarksRole:
return EventStatus::Hidden;
}
return {};
}