Start implementing bubbles
This commit is contained in:
@@ -31,8 +31,8 @@ Rectangle {
|
|||||||
|
|
||||||
// backgroundColor
|
// backgroundColor
|
||||||
color: Kirigami.Theme.backgroundColor
|
color: Kirigami.Theme.backgroundColor
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
Kirigami.Theme.inherit: true
|
Kirigami.Theme.inherit: false
|
||||||
|
|
||||||
// Confetti
|
// Confetti
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import NeoChat.Setting 1.0
|
|||||||
import NeoChat.Component 1.0
|
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
|
||||||
|
import org.kde.kcoreaddons 1.0 as KCA
|
||||||
|
|
||||||
Control {
|
Control {
|
||||||
id: root
|
id: root
|
||||||
@@ -29,27 +30,6 @@ Control {
|
|||||||
autoLoad: false
|
autoLoad: false
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Action {
|
|
||||||
id: saveFileAction
|
|
||||||
onTriggered: {
|
|
||||||
let contextMenu = fileDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
|
||||||
contextMenu.viewSource.connect(function() {
|
|
||||||
messagerSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
|
||||||
})
|
|
||||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
|
||||||
contextMenu.saveFileAs.connect(saveFileAs)
|
|
||||||
contextMenu.reply.connect(function() {
|
|
||||||
roomPanelInput.replyModel = Object.assign({}, model)
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.focus()
|
|
||||||
})
|
|
||||||
contextMenu.redact.connect(function() {
|
|
||||||
currentRoom.redactEvent(eventId)
|
|
||||||
})
|
|
||||||
contextMenu.popup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
RowLayout {
|
RowLayout {
|
||||||
ToolButton {
|
ToolButton {
|
||||||
@@ -69,6 +49,8 @@ Control {
|
|||||||
}
|
}
|
||||||
RowLayout {
|
RowLayout {
|
||||||
visible: audio.hasAudio
|
visible: audio.hasAudio
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
// Server doesn't support seeking, so use ProgressBar instead of Slider :(
|
||||||
ProgressBar {
|
ProgressBar {
|
||||||
from: 0
|
from: 0
|
||||||
@@ -77,72 +59,8 @@ Control {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
text: humanSize(audio.position) + "/" + humanSize(audio.duration)
|
text: KCA.Format.formatDuration(audio.position) + "/" + KCA.Format.formatDuration(audio.duration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
background: AutoMouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
id: messageMouseArea
|
|
||||||
|
|
||||||
onSecondaryClicked: saveFileAction.trigger()
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: messagerSourceSheet
|
|
||||||
|
|
||||||
MessageSourceSheet {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFolderDialog
|
|
||||||
|
|
||||||
OpenFolderDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: fileDelegateContextMenu
|
|
||||||
|
|
||||||
FileDelegateContextMenu {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveFileAs() {
|
|
||||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
|
||||||
|
|
||||||
folderDialog.chosen.connect(function(path) {
|
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
|
||||||
})
|
|
||||||
|
|
||||||
folderDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndOpen() {
|
|
||||||
if (downloaded) {
|
|
||||||
openSavedFile()
|
|
||||||
} else {
|
|
||||||
openOnFinished = true
|
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function openSavedFile() {
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function humanSize(duration) {
|
|
||||||
if (!duration) {
|
|
||||||
return i18n("Unknown duration")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (duration > 1000 * 60 * 60) {
|
|
||||||
return new Date(duration).toLocaleTimeString(Qt.locale(), "hh:mm:ss")
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Date(duration).toLocaleTimeString(Qt.locale(), "mm:ss")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import QtQuick.Controls.Material 2.12
|
|||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
import Qt.labs.platform 1.0
|
import Qt.labs.platform 1.0
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
import org.kde.kirigami 2.13 as Kirigami
|
||||||
|
import org.kde.kcoreaddons 1.0 as KCA
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
import NeoChat.Setting 1.0
|
import NeoChat.Setting 1.0
|
||||||
@@ -18,88 +19,51 @@ 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 {
|
RowLayout {
|
||||||
|
id: root
|
||||||
property bool openOnFinished: false
|
property bool openOnFinished: false
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
id: root
|
|
||||||
|
|
||||||
spacing: 4
|
spacing: 4
|
||||||
|
|
||||||
onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile()
|
onDownloadedChanged: if (downloaded && openOnFinished) {
|
||||||
|
openSavedFile();
|
||||||
|
}
|
||||||
|
|
||||||
z: -5
|
z: -5
|
||||||
|
|
||||||
Control {
|
ToolButton {
|
||||||
contentItem: RowLayout {
|
icon.name: progressInfo.completed ? "document-open" : "document-save"
|
||||||
ToolButton {
|
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
|
||||||
icon.name: progressInfo.completed ? "document-open" : "document-save"
|
}
|
||||||
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
level: 4
|
level: 4
|
||||||
text: display
|
text: display
|
||||||
wrapMode: Label.Wrap
|
wrapMode: Label.Wrap
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: !progressInfo.completed && progressInfo.active ? (humanSize(progressInfo.progress) + "/" + humanSize(progressInfo.total)) : humanSize(content.info ? content.info.size : 0)
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
wrapMode: Label.Wrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
background: Item {
|
Label {
|
||||||
MouseArea {
|
Layout.fillWidth: true
|
||||||
id: messageMouseArea
|
text: !progressInfo.completed && progressInfo.active ? (KCA.Format.formatByteSize(progressInfo.progress) + "/" + KCA.Format.formatByteSize(progressInfo.total)) : KCA.Format.formatByteSize(content.info ? content.info.size : 0)
|
||||||
anchors.fill: parent
|
color: Kirigami.Theme.disabledTextColor
|
||||||
acceptedButtons: Qt.RightButton
|
wrapMode: Label.Wrap
|
||||||
onClicked: {
|
}
|
||||||
var contextMenu = fileDelegateContextMenu.createObject(root, {'room': currentRoom, 'author': author});
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
contextMenu.viewSource.connect(function() {
|
}
|
||||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
|
||||||
})
|
|
||||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
|
||||||
contextMenu.saveFileAs.connect(saveFileAs)
|
|
||||||
contextMenu.reply.connect(function() {
|
|
||||||
roomPanelInput.replyModel = Object.assign({}, model)
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.focus()
|
|
||||||
})
|
|
||||||
contextMenu.redact.connect(function() {
|
|
||||||
currentRoom.redactEvent(eventId)
|
|
||||||
})
|
|
||||||
contextMenu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: messageSourceSheet
|
id: fileDialog
|
||||||
|
|
||||||
MessageSourceSheet {}
|
FileDialog {
|
||||||
}
|
fileMode: FileDialog.SaveFile
|
||||||
|
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||||
Component {
|
onAccepted: {
|
||||||
id: fileDialog
|
currentRoom.downloadFile(eventId, file)
|
||||||
|
|
||||||
FileDialog {
|
|
||||||
fileMode: FileDialog.SaveFile
|
|
||||||
folder: StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
|
||||||
onAccepted: {
|
|
||||||
currentRoom.downloadFile(eventId, file)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: fileDelegateContextMenu
|
|
||||||
|
|
||||||
FileDelegateContextMenu {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,34 +74,18 @@ RowLayout {
|
|||||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
function downloadAndOpen()
|
function downloadAndOpen() {
|
||||||
{
|
if (downloaded) {
|
||||||
if (downloaded) openSavedFile()
|
openSavedFile();
|
||||||
else
|
} else {
|
||||||
{
|
openOnFinished = true;
|
||||||
openOnFinished = true
|
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/"
|
||||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
+ eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSavedFile()
|
function openSavedFile() {
|
||||||
{
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function humanSize(bytes)
|
|
||||||
{
|
|
||||||
if (!bytes)
|
|
||||||
return i18nc("Unknown attachment size", "Unknown")
|
|
||||||
if (bytes < 4000)
|
|
||||||
return i18np("%1 byte", "%1 bytes", bytes)
|
|
||||||
bytes = Math.round(bytes / 100) / 10
|
|
||||||
if (bytes < 2000)
|
|
||||||
return i18nc("KB as in kilobytes", "%1 KB", bytes)
|
|
||||||
bytes = Math.round(bytes / 100) / 10
|
|
||||||
if (bytes < 2000)
|
|
||||||
return i18nc("MB as in megabytes", "%1 MB", bytes)
|
|
||||||
return i18nc("GB as in gigabytes", "%1 GB", Math.round(bytes / 100) / 10)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,158 +0,0 @@
|
|||||||
/**
|
|
||||||
* SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
|
||||||
* SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
|
||||||
*/
|
|
||||||
import QtQuick 2.12
|
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
|
||||||
import QtQuick.Layouts 1.12
|
|
||||||
import QtGraphicalEffects 1.12
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
|
||||||
import NeoChat.Setting 1.0
|
|
||||||
import NeoChat.Component 1.0
|
|
||||||
import NeoChat.Dialog 1.0
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
default property alias innerObject : column.children
|
|
||||||
|
|
||||||
readonly property bool sentByMe: author.isLocalUser
|
|
||||||
readonly property bool darkBackground: !sentByMe
|
|
||||||
readonly property bool replyVisible: reply ?? false
|
|
||||||
readonly property bool failed: marks == EventStatus.SendingFailed
|
|
||||||
readonly property color authorColor: eventType == "notice" ? Kirigami.Theme.activeTextColor : author.color
|
|
||||||
readonly property color replyAuthorColor: replyVisible ? reply.author.color : Kirigami.Theme.focusColor
|
|
||||||
|
|
||||||
property alias mouseArea: controlContainer.children
|
|
||||||
property bool isEmote: false
|
|
||||||
|
|
||||||
signal saveFileAs()
|
|
||||||
signal openExternally()
|
|
||||||
signal replyClicked(string eventID)
|
|
||||||
signal replyToMessageClicked(var replyUser, string replyContent, string eventID)
|
|
||||||
|
|
||||||
id: root
|
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
|
||||||
Layout.bottomMargin: 0
|
|
||||||
Layout.topMargin: showAuthor ? Kirigami.Units.smallSpacing : 0
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.maximumWidth: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.maximumHeight: Kirigami.Units.gridUnit * 2
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
|
|
||||||
visible: showAuthor && Config.showAvatarInTimeline
|
|
||||||
name: author.name ?? author.displayName
|
|
||||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
|
||||||
color: author.color
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: userDetailDialog
|
|
||||||
|
|
||||||
UserDetailDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
visible: !showAuthor && Config.showAvatarInTimeline
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
QQC2.Control {
|
|
||||||
id: controlContainer
|
|
||||||
Layout.fillWidth: true
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
hoverEnabled: true
|
|
||||||
contentItem: ColumnLayout {
|
|
||||||
id: column
|
|
||||||
spacing: showAuthor ? Kirigami.Units.smallSpacing : 0
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
id: rowLayout
|
|
||||||
Layout.fillWidth: true
|
|
||||||
QQC2.Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
topInset: 0
|
|
||||||
|
|
||||||
visible: showAuthor && !isEmote
|
|
||||||
|
|
||||||
text: author.displayName
|
|
||||||
font.weight: Font.Bold
|
|
||||||
color: author.color
|
|
||||||
wrapMode: Text.Wrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
visible: showAuthor && !isEmote
|
|
||||||
text: time.toLocaleTimeString(Locale.ShortFormat)
|
|
||||||
color: Kirigami.Theme.disabledTextColor
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loader {
|
|
||||||
id: replyLoader
|
|
||||||
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
|
||||||
active: replyVisible
|
|
||||||
}
|
|
||||||
Connections {
|
|
||||||
target: replyLoader.item
|
|
||||||
function onClicked() {
|
|
||||||
replyClicked(reply.eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RowLayout {
|
|
||||||
z: 2
|
|
||||||
anchors.bottom: controlContainer.top
|
|
||||||
anchors.bottomMargin: -Kirigami.Units.gridUnit
|
|
||||||
anchors.right: controlContainer.right
|
|
||||||
spacing: 0
|
|
||||||
QQC2.Button {
|
|
||||||
QQC2.ToolTip.text: i18n("React")
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
visible: controlContainer.hovered
|
|
||||||
icon.name: "preferences-desktop-emoticons"
|
|
||||||
onClicked: emojiDialog.open();
|
|
||||||
EmojiDialog {
|
|
||||||
id: emojiDialog
|
|
||||||
onReact: currentRoom.toggleReaction(eventId, emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.Button {
|
|
||||||
QQC2.ToolTip.text: i18n("Edit")
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
visible: controlContainer.hovered && author.id === Controller.activeConnection.localUserId && (model.eventType === "emote" || model.eventType === "message")
|
|
||||||
icon.name: "document-edit"
|
|
||||||
onClicked: chatTextInput.edit(message, model.formattedBody, eventId)
|
|
||||||
}
|
|
||||||
QQC2.Button {
|
|
||||||
QQC2.ToolTip.text: i18n("Reply")
|
|
||||||
QQC2.ToolTip.visible: hovered
|
|
||||||
visible: controlContainer.hovered
|
|
||||||
icon.name: "mail-replied-symbolic"
|
|
||||||
onClicked: replyToMessage(author, message, eventId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
background: Rectangle {
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
|
||||||
color: !model.isHighlighted ? Kirigami.Theme.backgroundColor : Kirigami.Theme.positiveBackgroundColor
|
|
||||||
opacity: controlContainer.hovered || model.isHighlighted ? 1 : 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -10,12 +10,9 @@ import QtQuick.Layouts 1.12
|
|||||||
import org.kde.kirigami 2.13 as Kirigami
|
import org.kde.kirigami 2.13 as Kirigami
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
visible: (reaction && reaction.length > 0) ?? false
|
|
||||||
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: reaction
|
model: reaction ?? null
|
||||||
|
|
||||||
delegate: AbstractButton {
|
delegate: AbstractButton {
|
||||||
width: Math.max(implicitWidth, height)
|
width: Math.max(implicitWidth, height)
|
||||||
@@ -31,8 +28,8 @@ Flow {
|
|||||||
radius: height / 2
|
radius: height / 2
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
Kirigami.Theme.colorSet: Kirigami.Theme.Button
|
||||||
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||||
border.color: checked ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
|
border.color: checked ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.textColor
|
||||||
border.width: 1
|
border.width: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
checkable: true
|
checkable: true
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ QQC2.AbstractButton {
|
|||||||
Layout.alignment: Qt.AlignTop
|
Layout.alignment: Qt.AlignTop
|
||||||
visible: Config.showAvatarInTimeline
|
visible: Config.showAvatarInTimeline
|
||||||
source: replyVisible && reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
source: replyVisible && reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : ""
|
||||||
name: replyVisible ? reply.author.name : "H"
|
name: replyVisible ? (reply.author.name || "") : "H"
|
||||||
color: replyVisible ? reply.author.color : Kirigami.Theme.highlightColor
|
color: replyVisible ? reply.author.color : Kirigami.Theme.highlightColor
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +48,7 @@ QQC2.AbstractButton {
|
|||||||
|
|
||||||
TextDelegate {
|
TextDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: 0
|
||||||
text: replyVisible ? ("<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + reply.display) : ""
|
text: replyVisible ? ("<style>pre {white-space: pre-wrap} a{color: " + Kirigami.Theme.linkColor + ";} .user-pill{}</style>" + reply.display) : ""
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
wrapMode: Text.WordWrap
|
wrapMode: Text.WordWrap
|
||||||
|
|||||||
@@ -16,11 +16,6 @@ import NeoChat.Setting 1.0
|
|||||||
RowLayout {
|
RowLayout {
|
||||||
id: row
|
id: row
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.minimumWidth: Kirigami.Units.iconSizes.medium
|
|
||||||
Layout.preferredHeight: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
Layout.preferredWidth: Kirigami.Units.iconSizes.small
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
Layout.preferredHeight: Kirigami.Units.iconSizes.small
|
||||||
|
|||||||
@@ -5,12 +5,16 @@
|
|||||||
*/
|
*/
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
import org.kde.kirigami 2.4 as Kirigami
|
import org.kde.kirigami 2.4 as Kirigami
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
id: contentLabel
|
id: contentLabel
|
||||||
|
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
Layout.topMargin: 0
|
||||||
|
|
||||||
readonly property var isEmoji: /^(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+$/
|
readonly property var isEmoji: /^(\u00a9|\u00ae|[\u2000-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+$/
|
||||||
|
|
||||||
property bool isEmote: false
|
property bool isEmote: false
|
||||||
|
|||||||
@@ -3,34 +3,240 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import QtQuick 2.12
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.12 as Controls
|
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
|
import QtQuick.Controls 2.12 as QQC2
|
||||||
|
import QtGraphicalEffects 1.12
|
||||||
|
|
||||||
import org.kde.kirigami 2.4 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
import NeoChat.Setting 1.0
|
||||||
|
import NeoChat.Component 1.0
|
||||||
|
import NeoChat.Dialog 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
default property alias innerObject : column.children
|
default property alias innerObject : column.children
|
||||||
|
readonly property bool sentByMe: author.isLocalUser
|
||||||
|
readonly property bool darkBackground: !sentByMe
|
||||||
|
readonly property bool replyVisible: reply ?? false
|
||||||
|
readonly property bool failed: marks == EventStatus.SendingFailed
|
||||||
|
readonly property color authorColor: eventType == "notice" ? Kirigami.Theme.activeTextColor : author.color
|
||||||
|
readonly property color replyAuthorColor: replyVisible ? reply.author.color : Kirigami.Theme.focusColor
|
||||||
|
|
||||||
height: column.implicitHeight + (readMarker ? 2 * Kirigami.Units.smallSpacing : 0)
|
property alias mouseArea: controlContainer.children
|
||||||
|
property bool isEmote: false
|
||||||
|
property bool cardBackground: true
|
||||||
|
property bool isLoaded
|
||||||
|
|
||||||
|
property var hoverComponent
|
||||||
|
|
||||||
|
signal saveFileAs()
|
||||||
|
signal openExternally()
|
||||||
|
signal replyClicked(string eventID)
|
||||||
|
signal replyToMessageClicked(var replyUser, string replyContent, string eventID)
|
||||||
|
|
||||||
|
property alias hovered: controlContainer.hovered
|
||||||
|
|
||||||
|
implicitHeight: mainColumn.implicitHeight + (readMarker ? Kirigami.Units.smallSpacing : 0)
|
||||||
|
|
||||||
|
property int hoverComponentX: column.width - hoverComponent.childWidth + Kirigami.Units.largeSpacing
|
||||||
|
property int hoverComponentY: -Kirigami.Units.largeSpacing - hoverComponent.childHeight * 1.5
|
||||||
|
|
||||||
|
// show hover actions
|
||||||
|
onHoveredChanged: {
|
||||||
|
if (hovered && !Kirigami.Settings.isMobile) {
|
||||||
|
hoverComponent.x = Qt.binding(() => column.mapToItem(page, hoverComponentX, hoverComponentY).x);
|
||||||
|
hoverComponent.y = Qt.binding(() => column.mapToItem(page, hoverComponentX, hoverComponentY).y);
|
||||||
|
hoverComponent.hovered = Qt.binding(() => controlContainer.hovered);
|
||||||
|
hoverComponent.showEdit = author.id === Controller.activeConnection.localUserId && (model.eventType === "emote" || model.eventType === "message");
|
||||||
|
|
||||||
|
hoverComponent.editClicked = () => {
|
||||||
|
chatTextInput.edit(message, model.formattedBody, eventId);
|
||||||
|
};
|
||||||
|
hoverComponent.replyClicked = () => {
|
||||||
|
replyToMessage(author, message, eventId);
|
||||||
|
};
|
||||||
|
hoverComponent.reacted = emoji => {
|
||||||
|
currentRoom.toggleReaction(eventId, emoji);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DragHandler {
|
||||||
|
enabled: Kirigami.Settings.isMobile
|
||||||
|
yAxis.enabled: false
|
||||||
|
xAxis.enabled: true
|
||||||
|
xAxis.maximum: 0
|
||||||
|
xAxis.minimum: -Kirigami.Units.gridUnit * 4
|
||||||
|
onActiveChanged: {
|
||||||
|
applicationWindow().pageStack.interactive = true;
|
||||||
|
if (!active && parent.x < -Kirigami.Units.gridUnit * 3) {
|
||||||
|
replyToMessage(author, message, eventId)
|
||||||
|
}
|
||||||
|
parent.x = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onXChanged: if (x !== 0) {
|
||||||
|
applicationWindow().pageStack.interactive = false;
|
||||||
|
} else {
|
||||||
|
applicationWindow().pageStack.interactive = true;
|
||||||
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: column
|
id: mainColumn
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
SectionDelegate {
|
SectionDelegate {
|
||||||
|
id: sectionDelegate
|
||||||
Layout.maximumWidth: parent.width
|
Layout.maximumWidth: parent.width
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
visible: showSection
|
visible: showSection
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.bottomMargin: 0
|
||||||
|
Layout.topMargin: showAuthor ? Kirigami.Units.smallSpacing : 0
|
||||||
|
|
||||||
|
Kirigami.Avatar {
|
||||||
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
||||||
|
Layout.minimumHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 2
|
||||||
|
Layout.maximumHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
visible: showAuthor && Config.showAvatarInTimeline
|
||||||
|
name: author.name ?? author.displayName
|
||||||
|
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||||
|
color: author.color
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: userDetailDialog
|
||||||
|
|
||||||
|
UserDetailDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {"room": currentRoom, "user": author.object, "displayName": author.displayName, "avatarMediaId": author.avatarMediaId, "avatarUrl": author.avatarUrl}).open()
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 2
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
visible: !showAuthor && Config.showAvatarInTimeline
|
||||||
|
}
|
||||||
|
|
||||||
|
// bubble
|
||||||
|
QQC2.Control {
|
||||||
|
id: controlContainer
|
||||||
|
//Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: mainColumn.width - Kirigami.Units.gridUnit * 2 - Kirigami.Units.largeSpacing * 2
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
hoverEnabled: true
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
id: column
|
||||||
|
spacing: 0
|
||||||
|
Item { // top padding
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
}
|
||||||
|
// HACK: reload author when the delegate is reloaded, since there are strange issues with ListView reuseItems and the author displayName disappearing
|
||||||
|
Loader {
|
||||||
|
id: topRow
|
||||||
|
active: isLoaded && showAuthor && !isEmote
|
||||||
|
visible: active
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.bottomMargin: visible ? Kirigami.Units.smallSpacing : 0
|
||||||
|
|
||||||
|
sourceComponent: RowLayout {
|
||||||
|
id: rowLayout
|
||||||
|
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
topInset: 0
|
||||||
|
|
||||||
|
visible: showAuthor && !isEmote
|
||||||
|
|
||||||
|
text: author.displayName
|
||||||
|
font.weight: Font.Bold
|
||||||
|
color: author.color
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
visible: showAuthor && !isEmote
|
||||||
|
text: time.toLocaleTimeString(Locale.ShortFormat)
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
id: replyLoader
|
||||||
|
source: 'qrc:imports/NeoChat/Component/Timeline/ReplyComponent.qml'
|
||||||
|
active: replyVisible
|
||||||
|
visible: active
|
||||||
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: replyLoader.item
|
||||||
|
function onClicked() {
|
||||||
|
replyClicked(reply.eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
visible: cardBackground
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: !model.isHighlighted ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: Kirigami.Units.devicePixelRatio
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loader {
|
||||||
|
id: loader
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing * 2
|
||||||
|
Layout.topMargin: active ? Kirigami.Units.smallSpacing : 0
|
||||||
|
active: eventType !== "state" && eventType !== "notice" && reaction != undefined && reaction.length > 0
|
||||||
|
visible: active
|
||||||
|
sourceComponent: ReactionDelegate { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
id: replyButton
|
||||||
|
visible: parent.x < - Kirigami.Units.gridUnit * 1
|
||||||
|
opacity: -(parent.x + Kirigami.Units.gridUnit) / Kirigami.Units.gridUnit / 3
|
||||||
|
anchors.left: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
source: "mail-replied-symbolic"
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width * 0.9
|
width: parent.width * 0.9
|
||||||
x: parent.width * 0.05
|
x: parent.width * 0.05
|
||||||
height: Kirigami.Units.smallSpacing
|
height: Kirigami.Units.smallSpacing / 2
|
||||||
anchors.top: column.bottom
|
anchors.top: mainColumn.bottom
|
||||||
anchors.topMargin: Kirigami.Units.smallSpacing
|
anchors.topMargin: Kirigami.Units.smallSpacing
|
||||||
visible: readMarker
|
visible: readMarker
|
||||||
color: Kirigami.Theme.positiveTextColor
|
color: Kirigami.Theme.positiveTextColor
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import NeoChat.Menu.Timeline 1.0
|
|||||||
Video {
|
Video {
|
||||||
id: vid
|
id: vid
|
||||||
|
|
||||||
property bool openOnFinished: false
|
|
||||||
property bool playOnFinished: false
|
property bool playOnFinished: false
|
||||||
readonly property bool downloaded: progressInfo && progressInfo.completed
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
@@ -32,10 +31,6 @@ Video {
|
|||||||
vid.source = progressInfo.localPath
|
vid.source = progressInfo.localPath
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloaded && openOnFinished) {
|
|
||||||
openSavedFile()
|
|
||||||
openOnFinished = false
|
|
||||||
}
|
|
||||||
if (downloaded && playOnFinished) {
|
if (downloaded && playOnFinished) {
|
||||||
playSavedFile()
|
playSavedFile()
|
||||||
playOnFinished = false
|
playOnFinished = false
|
||||||
@@ -105,36 +100,6 @@ Video {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Control {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.bottomMargin: 8
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.rightMargin: 8
|
|
||||||
|
|
||||||
horizontalPadding: 8
|
|
||||||
verticalPadding: 4
|
|
||||||
|
|
||||||
contentItem: RowLayout {
|
|
||||||
Label {
|
|
||||||
text: Qt.formatTime(time)
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: author.displayName
|
|
||||||
color: "white"
|
|
||||||
font.pixelSize: 12
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
radius: height / 2
|
|
||||||
color: "black"
|
|
||||||
opacity: 0.3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
@@ -153,100 +118,29 @@ Video {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AutoMouseArea {
|
TapHandler {
|
||||||
anchors.fill: parent
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onTapped: if (supportStreaming || progressInfo.completed) {
|
||||||
id: messageMouseArea
|
if (vid.playbackState == MediaPlayer.PlayingState) {
|
||||||
|
vid.pause()
|
||||||
onPrimaryClicked: {
|
|
||||||
if (supportStreaming || progressInfo.completed) {
|
|
||||||
if (vid.playbackState == MediaPlayer.PlayingState) {
|
|
||||||
vid.pause()
|
|
||||||
} else {
|
|
||||||
vid.play()
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
downloadAndPlay()
|
vid.play()
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
downloadAndPlay()
|
||||||
onSecondaryClicked: {
|
|
||||||
var contextMenu = imageDelegateContextMenu.createObject(vid, {'room': currentRoom, 'author': author})
|
|
||||||
contextMenu.viewSource.connect(function() {
|
|
||||||
messageSourceSheet.createObject(ApplicationWindow.overlay, {"sourceText": toolTip}).open()
|
|
||||||
})
|
|
||||||
contextMenu.downloadAndOpen.connect(downloadAndOpen)
|
|
||||||
contextMenu.saveFileAs.connect(saveFileAs)
|
|
||||||
contextMenu.reply.connect(function() {
|
|
||||||
roomPanelInput.replyModel = Object.assign({}, model)
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.focus()
|
|
||||||
})
|
|
||||||
contextMenu.redact.connect(function() {
|
|
||||||
currentRoom.redactEvent(eventId)
|
|
||||||
})
|
|
||||||
contextMenu.popup()
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: messageSourceSheet
|
|
||||||
|
|
||||||
MessageSourceSheet {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: openFolderDialog
|
|
||||||
|
|
||||||
OpenFolderDialog {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: imageDelegateContextMenu
|
|
||||||
|
|
||||||
FileDelegateContextMenu {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveFileAs() {
|
function downloadAndPlay() {
|
||||||
var folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
if (downloaded) {
|
||||||
|
playSavedFile()
|
||||||
folderDialog.chosen.connect(function(path) {
|
} else {
|
||||||
if (!path) return
|
|
||||||
|
|
||||||
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
|
||||||
})
|
|
||||||
|
|
||||||
folderDialog.open()
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndOpen()
|
|
||||||
{
|
|
||||||
if (downloaded) openSavedFile()
|
|
||||||
else
|
|
||||||
{
|
|
||||||
openOnFinished = true
|
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function downloadAndPlay()
|
|
||||||
{
|
|
||||||
if (downloaded) playSavedFile()
|
|
||||||
else
|
|
||||||
{
|
|
||||||
playOnFinished = true
|
playOnFinished = true
|
||||||
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function openSavedFile()
|
function playSavedFile() {
|
||||||
{
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
|
||||||
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
function playSavedFile()
|
|
||||||
{
|
|
||||||
vid.stop()
|
vid.stop()
|
||||||
vid.play()
|
vid.play()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
module NeoChat.Component.Timeline
|
module NeoChat.Component.Timeline
|
||||||
TimelineContainer 1.0 TimelineContainer.qml
|
TimelineContainer 1.0 TimelineContainer.qml
|
||||||
MessageDelegate 1.0 MessageDelegate.qml
|
|
||||||
TextDelegate 1.0 TextDelegate.qml
|
TextDelegate 1.0 TextDelegate.qml
|
||||||
StateDelegate 1.0 StateDelegate.qml
|
StateDelegate 1.0 StateDelegate.qml
|
||||||
SectionDelegate 1.0 SectionDelegate.qml
|
SectionDelegate 1.0 SectionDelegate.qml
|
||||||
|
|||||||
@@ -5,50 +5,43 @@
|
|||||||
*/
|
*/
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
|
import NeoChat.Menu 1.0
|
||||||
|
|
||||||
Menu {
|
MessageDelegateContextMenu {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
required property var room
|
|
||||||
required property var author
|
|
||||||
|
|
||||||
signal viewSource()
|
|
||||||
signal downloadAndOpen()
|
signal downloadAndOpen()
|
||||||
signal saveFileAs()
|
signal saveFileAs()
|
||||||
signal reply()
|
|
||||||
signal redact()
|
|
||||||
|
|
||||||
MenuItem {
|
property list<Kirigami.Action> actions: [
|
||||||
text: i18n("View Source")
|
Kirigami.Action {
|
||||||
|
text: i18n("Open Externally")
|
||||||
onTriggered: viewSource()
|
icon.name: "document-open"
|
||||||
}
|
onTriggered: downloadAndOpen()
|
||||||
|
},
|
||||||
MenuItem {
|
Kirigami.Action {
|
||||||
text: i18n("Open Externally")
|
text: i18n("Save As")
|
||||||
|
icon.name: "document-save"
|
||||||
onTriggered: downloadAndOpen()
|
onTriggered: saveFileAs()
|
||||||
}
|
},
|
||||||
|
Kirigami.Action {
|
||||||
MenuItem {
|
text: i18n("Reply")
|
||||||
text: i18n("Save As")
|
icon.name: "mail-replied-symbolic"
|
||||||
|
onTriggered: reply(author, message)
|
||||||
onTriggered: saveFileAs()
|
},
|
||||||
}
|
Kirigami.Action {
|
||||||
|
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||||
MenuItem {
|
text: i18n("Remove")
|
||||||
text: i18n("Reply")
|
icon.name: "edit-delete-remove"
|
||||||
|
icon.color: "red"
|
||||||
onTriggered: reply()
|
onTriggered: remove()
|
||||||
}
|
},
|
||||||
|
Kirigami.Action {
|
||||||
MenuItem {
|
text: i18n("View Source")
|
||||||
visible: room.canSendState("redact") || room.localUser.id === author.id
|
icon.name: "code-context"
|
||||||
text: i18n("Redact")
|
onTriggered: viewSource()
|
||||||
onTriggered: redact()
|
}
|
||||||
}
|
]
|
||||||
|
|
||||||
onClosed: destroy()
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,16 @@
|
|||||||
*
|
*
|
||||||
* SPDX-License-Identifier: GPL-3.0-only
|
* SPDX-License-Identifier: GPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import QtQuick 2.12
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.12 as QQC2
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
import org.kde.kirigami 2.13 as Kirigami
|
import org.kde.kirigami 2.14 as Kirigami
|
||||||
|
|
||||||
import NeoChat.Dialog 1.0
|
import NeoChat.Dialog 1.0
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
Loader {
|
||||||
id: root
|
id: loadRoot
|
||||||
|
|
||||||
required property var author
|
required property var author
|
||||||
required property string message
|
required property string message
|
||||||
@@ -23,99 +23,235 @@ Kirigami.OverlaySheet {
|
|||||||
signal reply(var author, string message)
|
signal reply(var author, string message)
|
||||||
signal remove()
|
signal remove()
|
||||||
|
|
||||||
parent: applicationWindow().overlay
|
property list<Kirigami.Action> actions: [
|
||||||
|
Kirigami.Action {
|
||||||
leftPadding: 0
|
text: i18n("Reply")
|
||||||
rightPadding: 0
|
icon.name: "mail-replied-symbolic"
|
||||||
|
onTriggered: reply(author, message)
|
||||||
ColumnLayout {
|
},
|
||||||
spacing: 0
|
Kirigami.Action {
|
||||||
RowLayout {
|
|
||||||
id: headerLayout
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
Kirigami.Avatar {
|
|
||||||
id: avatar
|
|
||||||
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
}
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Kirigami.Heading {
|
|
||||||
level: 3
|
|
||||||
Layout.fillWidth: true
|
|
||||||
text: author.displayName
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
}
|
|
||||||
QQC2.Label {
|
|
||||||
text: message
|
|
||||||
Layout.fillWidth: true
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
|
|
||||||
onLinkActivated: {
|
|
||||||
applicationWindow().handleLink(link, currentRoom)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row {
|
|
||||||
spacing: 0
|
|
||||||
Repeater {
|
|
||||||
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
|
||||||
delegate: QQC2.ItemDelegate {
|
|
||||||
width: 32
|
|
||||||
height: 32
|
|
||||||
|
|
||||||
contentItem: QQC2.Label {
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
|
|
||||||
font.pixelSize: 16
|
|
||||||
font.family: "emoji"
|
|
||||||
text: modelData
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
currentRoom.toggleReaction(eventId, modelData)
|
|
||||||
root.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
action: Kirigami.Action {
|
|
||||||
text: i18n("Reply")
|
|
||||||
icon.name: "mail-replied-symbolic"
|
|
||||||
onTriggered: reply(author, message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Kirigami.BasicListItem {
|
|
||||||
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
|
||||||
action: Kirigami.Action {
|
text: i18n("Remove")
|
||||||
text: i18n("Remove")
|
icon.name: "edit-delete-remove"
|
||||||
icon.name: "edit-delete-remove"
|
icon.color: "red"
|
||||||
icon.color: "red"
|
onTriggered: remove()
|
||||||
onTriggered: remove()
|
},
|
||||||
}
|
Kirigami.Action {
|
||||||
|
text: i18n("Copy")
|
||||||
|
icon.name: "edit-copy"
|
||||||
|
onTriggered: Clipboard.saveText(message)
|
||||||
|
},
|
||||||
|
Kirigami.Action {
|
||||||
|
text: i18n("View Source")
|
||||||
|
icon.name: "code-context"
|
||||||
|
onTriggered: viewSource()
|
||||||
}
|
}
|
||||||
Kirigami.BasicListItem {
|
]
|
||||||
action: Kirigami.Action {
|
|
||||||
text: i18n("Copy")
|
Component {
|
||||||
icon.name: "edit-copy"
|
id: regularMenu
|
||||||
onTriggered: Clipboard.saveText(message)
|
|
||||||
}
|
Kirigami.OverlaySheet {
|
||||||
}
|
id: root
|
||||||
Kirigami.BasicListItem {
|
|
||||||
action: Kirigami.Action {
|
parent: applicationWindow().overlay
|
||||||
text: i18n("View Source")
|
|
||||||
icon.name: "code-context"
|
leftPadding: 0
|
||||||
onTriggered: viewSource()
|
rightPadding: 0
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
RowLayout {
|
||||||
|
id: headerLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
Kirigami.Avatar {
|
||||||
|
id: avatar
|
||||||
|
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Kirigami.Heading {
|
||||||
|
level: 3
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: author.displayName
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
text: message
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 24
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
|
onLinkActivated: {
|
||||||
|
applicationWindow().handleLink(link, currentRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
spacing: 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||||
|
Repeater {
|
||||||
|
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
contentItem: QQC2.Label {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.family: "emoji"
|
||||||
|
text: modelData
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
currentRoom.toggleReaction(eventId, modelData)
|
||||||
|
loadRoot.item.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
model: loadRoot.actions
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
visible: modelData.visible
|
||||||
|
action: modelData
|
||||||
|
onClicked: {
|
||||||
|
modelData.triggered();
|
||||||
|
loadRoot.item.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Component {
|
||||||
|
id: mobileMenu
|
||||||
|
|
||||||
|
Kirigami.OverlayDrawer {
|
||||||
|
id: drawer
|
||||||
|
height: popupContent.implicitHeight
|
||||||
|
edge: Qt.BottomEdge
|
||||||
|
padding: 0
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
topPadding: 0
|
||||||
|
|
||||||
|
parent: applicationWindow().overlay
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: popupContent
|
||||||
|
width: parent.width
|
||||||
|
spacing: 0
|
||||||
|
RowLayout {
|
||||||
|
id: headerLayout
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: Kirigami.Units.largeSpacing
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
Kirigami.Avatar {
|
||||||
|
id: avatar
|
||||||
|
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : ""
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Kirigami.Heading {
|
||||||
|
level: 3
|
||||||
|
Layout.fillWidth: true
|
||||||
|
text: author.displayName
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
text: message
|
||||||
|
Layout.fillWidth: true
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
|
||||||
|
onLinkActivated: {
|
||||||
|
applicationWindow().handleLink(link, currentRoom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
spacing: 0
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2.5
|
||||||
|
Repeater {
|
||||||
|
model: ["👍", "👎️", "😄", "🎉", "🚀", "👀"]
|
||||||
|
delegate: QQC2.ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
contentItem: Kirigami.Heading {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
font.family: "emoji"
|
||||||
|
text: modelData
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
currentRoom.toggleReaction(eventId, modelData);
|
||||||
|
loadRoot.item.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
id: listViewAction
|
||||||
|
model: loadRoot.actions
|
||||||
|
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
icon: modelData.icon.name
|
||||||
|
iconColor: modelData.icon.color ?? undefined
|
||||||
|
enabled: modelData.enabled
|
||||||
|
visible: modelData.visible
|
||||||
|
text: modelData.text
|
||||||
|
onClicked: {
|
||||||
|
modelData.triggered()
|
||||||
|
loadRoot.item.close();
|
||||||
|
}
|
||||||
|
implicitHeight: visible ? Kirigami.Units.gridUnit * 3 : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
asynchronous: true
|
||||||
|
sourceComponent: Kirigami.Settings.isMobile ? mobileMenu : regularMenu
|
||||||
|
|
||||||
|
function open() {
|
||||||
|
active = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onStatusChanged: if (status == Loader.Ready) {
|
||||||
|
item.open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,58 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// hover actions on a delegate, activated in TimelineContainer.qml
|
||||||
|
Item {
|
||||||
|
id: hoverActions
|
||||||
|
property bool showEdit
|
||||||
|
property bool hovered: false
|
||||||
|
|
||||||
|
visible: (hovered || hoverHandler.hovered) && !Kirigami.Settings.isMobile
|
||||||
|
|
||||||
|
property var editClicked
|
||||||
|
property var replyClicked
|
||||||
|
property var reacted
|
||||||
|
|
||||||
|
property alias childWidth: hoverActionsRow.width
|
||||||
|
property alias childHeight: hoverActionsRow.height
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
id: hoverActionsRow
|
||||||
|
z: 4
|
||||||
|
spacing: 0
|
||||||
|
HoverHandler {
|
||||||
|
id: hoverHandler
|
||||||
|
margin: Kirigami.Units.smallSpacing
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.Button {
|
||||||
|
QQC2.ToolTip.text: i18n("React")
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
visible: actions.hovered
|
||||||
|
icon.name: "preferences-desktop-emoticons"
|
||||||
|
onClicked: emojiDialog.open();
|
||||||
|
EmojiDialog {
|
||||||
|
id: emojiDialog
|
||||||
|
onReact: hoverActions.reacted(emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
QQC2.ToolTip.text: i18n("Edit")
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
visible: actions.hovered && showEdit
|
||||||
|
icon.name: "document-edit"
|
||||||
|
onClicked: hoverActions.editClicked()
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
QQC2.ToolTip.text: i18n("Reply")
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
visible: actions.hovered
|
||||||
|
icon.name: "mail-replied-symbolic"
|
||||||
|
onClicked: hoverActions.replyClicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
id: messageListView
|
id: messageListView
|
||||||
|
|
||||||
@@ -137,7 +189,7 @@ Kirigami.ScrollablePage {
|
|||||||
readonly property bool isLoaded: page.width * page.height > 10
|
readonly property bool isLoaded: page.width * page.height > 10
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
clip: true
|
reuseItems: true
|
||||||
|
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
highlightMoveDuration: 500
|
highlightMoveDuration: 500
|
||||||
@@ -232,51 +284,62 @@ Kirigami.ScrollablePage {
|
|||||||
sourceModel: messageEventModel
|
sourceModel: messageEventModel
|
||||||
}
|
}
|
||||||
|
|
||||||
// populate: Transition {
|
populate: Transition {
|
||||||
// NumberAnimation {
|
NumberAnimation {
|
||||||
// property: "opacity"; from: 0; to: 1
|
property: "opacity"; from: 0; to: 1
|
||||||
// duration: 200
|
duration: Kirigami.Units.shortDuration
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// add: Transition {
|
add: Transition {
|
||||||
// NumberAnimation {
|
NumberAnimation {
|
||||||
// property: "opacity"; from: 0; to: 1
|
property: "opacity"; from: 0; to: 1
|
||||||
// duration: 200
|
duration: Kirigami.Units.shortDuration
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// move: Transition {
|
move: Transition {
|
||||||
// NumberAnimation {
|
NumberAnimation {
|
||||||
// property: "y"; duration: 200
|
property: "y"
|
||||||
// }
|
duration: Kirigami.Units.shortDuration
|
||||||
// NumberAnimation {
|
}
|
||||||
// property: "opacity"; to: 1
|
NumberAnimation {
|
||||||
// }
|
property: "opacity"; to: 1
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// displaced: Transition {
|
displaced: Transition {
|
||||||
// NumberAnimation {
|
NumberAnimation {
|
||||||
// property: "y"; duration: 200
|
property: "y"
|
||||||
// easing.type: Easing.OutQuad
|
duration: Kirigami.Units.shortDuration
|
||||||
// }
|
easing.type: Easing.OutQuad
|
||||||
// NumberAnimation {
|
}
|
||||||
// property: "opacity"; to: 1
|
NumberAnimation {
|
||||||
// }
|
property: "opacity"; to: 1
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
delegate: DelegateChooser {
|
delegate: DelegateChooser {
|
||||||
id: timelineDelegateChooser
|
id: timelineDelegateChooser
|
||||||
role: "eventType"
|
role: "eventType"
|
||||||
|
|
||||||
|
property bool delegateLoaded: true
|
||||||
|
ListView.onPooled: delegateLoaded = false
|
||||||
|
ListView.onReused: delegateLoaded = true
|
||||||
|
|
||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "state"
|
roleValue: "state"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
id: container
|
||||||
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
|
cardBackground: false
|
||||||
|
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
innerObject: StateDelegate {
|
innerObject: StateDelegate {
|
||||||
Layout.maximumWidth: parent.width
|
Layout.maximumWidth: container.width
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignLeft
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -284,30 +347,24 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "emote"
|
roleValue: "emote"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
innerObject: MessageDelegate {
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
Layout.fillWidth: true
|
isEmote: true
|
||||||
Layout.maximumWidth: messageListView.width
|
mouseArea: MouseArea {
|
||||||
|
acceptedButtons: Qt.RightButton
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: openMessageContext(author, display, eventId, toolTip);
|
||||||
|
}
|
||||||
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
|
innerObject: TextDelegate {
|
||||||
isEmote: true
|
isEmote: true
|
||||||
mouseArea: MouseArea {
|
Layout.fillWidth: true
|
||||||
acceptedButtons: Qt.RightButton
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
anchors.fill: parent
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
onClicked: openMessageContext(author, display, eventId, toolTip);
|
|
||||||
}
|
|
||||||
onReplyClicked: goToEvent(eventID)
|
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
innerObject: [
|
|
||||||
TextDelegate {
|
|
||||||
isEmote: true
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
|
||||||
},
|
|
||||||
ReactionDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.bottomMargin: Kirigami.Units.largeSpacing * 2
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,31 +372,27 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "message"
|
roleValue: "message"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
id: timeline
|
||||||
innerObject: MessageDelegate {
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
|
innerObject: TextDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.maximumWidth: messageListView.width
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
onReplyClicked: goToEvent(eventID)
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
TapHandler {
|
||||||
innerObject: [
|
acceptedButtons: Qt.RightButton
|
||||||
TextDelegate {
|
onTapped: openMessageContext(author, display, eventId, toolTip)
|
||||||
Layout.fillWidth: true
|
}
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
TapHandler {
|
||||||
TapHandler {
|
acceptedButtons: Qt.LeftButton
|
||||||
acceptedButtons: Qt.RightButton
|
onLongPressed: openMessageContext(author, display, eventId, toolTip)
|
||||||
onTapped: openMessageContext(author, display, eventId, toolTip)
|
}
|
||||||
}
|
|
||||||
TapHandler {
|
|
||||||
acceptedButtons: Qt.LeftButton
|
|
||||||
onLongPressed: openMessageContext(author, display, eventId, toolTip)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ReactionDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,16 +400,16 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "notice"
|
roleValue: "notice"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
|
||||||
innerObject: MessageDelegate {
|
hoverComponent: hoverActions
|
||||||
|
innerObject: TextDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
onReplyClicked: goToEvent(eventID)
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
innerObject: TextDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -364,26 +417,20 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "image"
|
roleValue: "image"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
innerObject: MessageDelegate {
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
|
||||||
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
|
innerObject: ImageDelegate {
|
||||||
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 10
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
onReplyClicked: goToEvent(eventID)
|
Layout.preferredHeight: info.h / info.w * width
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
|
||||||
innerObject: [
|
|
||||||
ImageDelegate {
|
|
||||||
Layout.maximumWidth: parent.width
|
|
||||||
Layout.minimumWidth: 320
|
|
||||||
Layout.preferredHeight: info.h / info.w * width
|
|
||||||
},
|
|
||||||
ReactionDelegate {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.maximumHeight: 320
|
|
||||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -391,27 +438,20 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "sticker"
|
roleValue: "sticker"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
innerObject: MessageDelegate {
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
Layout.fillWidth: true
|
onReplyClicked: goToEvent(eventID)
|
||||||
onReplyClicked: goToEvent(eventID)
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
|
|
||||||
innerObject: [
|
hoverComponent: hoverActions
|
||||||
ImageDelegate {
|
cardBackground: false
|
||||||
readonly: true
|
|
||||||
Layout.maximumWidth: parent.width / 2
|
innerObject: ImageDelegate {
|
||||||
Layout.minimumWidth: 320
|
readonly: true
|
||||||
Layout.preferredHeight: info.h / info.w * width
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 10
|
||||||
},
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 10
|
||||||
ReactionDelegate {
|
Layout.preferredHeight: info.h / info.w * width
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.topMargin: 0
|
|
||||||
Layout.maximumHeight: 320
|
|
||||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -419,25 +459,23 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "audio"
|
roleValue: "audio"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
innerObject: MessageDelegate {
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
|
||||||
|
innerObject: AudioDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
onReplyClicked: goToEvent(eventID)
|
TapHandler {
|
||||||
mouseArea: MouseArea {
|
acceptedButtons: Qt.RightButton
|
||||||
acceptedButtons: (Kirigami.Settings.isMobile ? Qt.LeftButton : 0) | Qt.RightButton
|
onTapped: openFileContext(author, display, eventId, toolTip, progressInfo, parent)
|
||||||
anchors.fill: parent
|
|
||||||
onClicked: {
|
|
||||||
if (mouse.button == Qt.RightButton) {
|
|
||||||
openMessageContext(author, display, eventId, toolTip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPressAndHold: openMessageContext(author, display, eventId, toolTip);
|
|
||||||
}
|
}
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
innerObject: AudioDelegate {
|
onLongPressed: openFileContext(author, display, eventId, toolTip, progressInfo, parent)
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -446,28 +484,29 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "video"
|
roleValue: "video"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
innerObject: MessageDelegate {
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
|
||||||
|
innerObject: VideoDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
onReplyClicked: goToEvent(eventID)
|
Layout.minimumWidth: Kirigami.Units.gridUnit * 10
|
||||||
mouseArea: MouseArea {
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
|
||||||
acceptedButtons: (Kirigami.Settings.isMobile ? Qt.LeftButton : 0) | Qt.RightButton
|
Layout.preferredHeight: content.info.h / content.info.w * width
|
||||||
anchors.fill: parent
|
Layout.maximumHeight: Kirigami.Units.gridUnit * 15
|
||||||
onClicked: {
|
Layout.minimumHeight: Kirigami.Units.gridUnit * 5
|
||||||
if (mouse.button == Qt.RightButton) {
|
|
||||||
openMessageContext(author, display, eventId, toolTip);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onPressAndHold: openMessageContext(author, display, eventId, toolTip);
|
|
||||||
}
|
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
|
||||||
|
|
||||||
innerObject: VideoDelegate {
|
TapHandler {
|
||||||
Layout.maximumWidth: parent.width
|
acceptedButtons: Qt.RightButton
|
||||||
Layout.minimumWidth: 320
|
onTapped: openFileContext(author, display, eventId, toolTip, progressInfo, parent)
|
||||||
Layout.maximumHeight: 320
|
}
|
||||||
Layout.preferredHeight: content.info.h / content.info.w * width
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openFileContext(author, display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -476,15 +515,23 @@ Kirigami.ScrollablePage {
|
|||||||
DelegateChoice {
|
DelegateChoice {
|
||||||
roleValue: "file"
|
roleValue: "file"
|
||||||
delegate: TimelineContainer {
|
delegate: TimelineContainer {
|
||||||
width: messageListView.width
|
width: messageListView.width - Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
innerObject: MessageDelegate {
|
hoverComponent: hoverActions
|
||||||
|
|
||||||
|
isLoaded: timelineDelegateChooser.delegateLoaded
|
||||||
|
onReplyClicked: goToEvent(eventID)
|
||||||
|
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
|
||||||
|
innerObject: FileDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
onReplyClicked: goToEvent(eventID)
|
TapHandler {
|
||||||
onReplyToMessageClicked: replyToMessage(replyUser, replyContent, eventId);
|
acceptedButtons: Qt.RightButton
|
||||||
|
onTapped: openFileContext(author, display, eventId, toolTip, progressInfo, parent)
|
||||||
innerObject: FileDelegate {
|
}
|
||||||
Layout.fillWidth: true
|
TapHandler {
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onLongPressed: openFileContext(author, display, eventId, toolTip, progressInfo, parent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -583,6 +630,18 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
MessageSourceSheet {}
|
MessageSourceSheet {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: openFolderDialog
|
||||||
|
|
||||||
|
OpenFolderDialog {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: fileDelegateContextMenu
|
||||||
|
|
||||||
|
FileDelegateContextMenu {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
footer: ChatTextInput {
|
footer: ChatTextInput {
|
||||||
@@ -626,8 +685,6 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
|
|
||||||
function goToEvent(eventID) {
|
function goToEvent(eventID) {
|
||||||
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
|
messageListView.positionViewAtIndex(eventToIndex(eventID), ListView.Contain)
|
||||||
}
|
}
|
||||||
@@ -661,7 +718,56 @@ Kirigami.ScrollablePage {
|
|||||||
return index;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
function openMessageContext(author, message, eventId, toolTip, model) {
|
/// Open message context dialog for file and videos
|
||||||
|
function openFileContext(author, message, eventId, toolTip, progressInfo, file) {
|
||||||
|
const contextMenu = fileDelegateContextMenu.createObject(page, {
|
||||||
|
'author': author,
|
||||||
|
'message': message,
|
||||||
|
'eventId': eventId,
|
||||||
|
});
|
||||||
|
contextMenu.downloadAndOpen.connect(function() {
|
||||||
|
if (file.downloaded) {
|
||||||
|
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||||
|
Qt.openUrlExternally(progressInfo.localDir);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
file.onDownloadedChanged.connect(function() {
|
||||||
|
if (!Qt.openUrlExternally(progressInfo.localPath)) {
|
||||||
|
Qt.openUrlExternally(progressInfo.localDir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(eventId))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
contextMenu.saveFileAs.connect(function() {
|
||||||
|
const folderDialog = openFolderDialog.createObject(ApplicationWindow.overlay)
|
||||||
|
|
||||||
|
folderDialog.chosen.connect(function(path) {
|
||||||
|
if (!path) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentRoom.downloadFile(eventId, path + "/" + currentRoom.fileNameToDownload(eventId))
|
||||||
|
})
|
||||||
|
|
||||||
|
folderDialog.open()
|
||||||
|
})
|
||||||
|
contextMenu.viewSource.connect(function() {
|
||||||
|
messageSourceSheet.createObject(page, {
|
||||||
|
'sourceText': toolTip,
|
||||||
|
}).open();
|
||||||
|
});
|
||||||
|
contextMenu.reply.connect(function(replyUser, replyContent) {
|
||||||
|
replyToMessage(replyUser, replyContent, eventId);
|
||||||
|
})
|
||||||
|
contextMenu.remove.connect(function() {
|
||||||
|
currentRoom.redactEvent(eventId);
|
||||||
|
})
|
||||||
|
contextMenu.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Open context menu for normal message
|
||||||
|
function openMessageContext(author, message, eventId, toolTip) {
|
||||||
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
const contextMenu = messageDelegateContextMenu.createObject(page, {
|
||||||
'author': author,
|
'author': author,
|
||||||
'message': message,
|
'message': message,
|
||||||
@@ -671,17 +777,14 @@ Kirigami.ScrollablePage {
|
|||||||
messageSourceSheet.createObject(page, {
|
messageSourceSheet.createObject(page, {
|
||||||
'sourceText': toolTip,
|
'sourceText': toolTip,
|
||||||
}).open();
|
}).open();
|
||||||
contextMenu.close();
|
|
||||||
});
|
});
|
||||||
contextMenu.reply.connect(function(replyUser, replyContent) {
|
contextMenu.reply.connect(function(replyUser, replyContent) {
|
||||||
replyToMessage(replyUser, replyContent, eventId);
|
replyToMessage(replyUser, replyContent, eventId);
|
||||||
contextMenu.close();
|
|
||||||
})
|
})
|
||||||
contextMenu.remove.connect(function() {
|
contextMenu.remove.connect(function() {
|
||||||
currentRoom.redactEvent(eventId);
|
currentRoom.redactEvent(eventId);
|
||||||
contextMenu.close();
|
|
||||||
})
|
})
|
||||||
contextMenu.open()
|
contextMenu.open();
|
||||||
}
|
}
|
||||||
|
|
||||||
function replyToMessage(replyUser, replyContent, eventId) {
|
function replyToMessage(replyUser, replyContent, eventId) {
|
||||||
|
|||||||
@@ -162,6 +162,8 @@ Kirigami.ApplicationWindow {
|
|||||||
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
|
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pageStack.columnView.columnWidth: Kirigami.Units.gridUnit * 17
|
||||||
|
|
||||||
globalDrawer: Kirigami.GlobalDrawer {
|
globalDrawer: Kirigami.GlobalDrawer {
|
||||||
property bool hasLayer
|
property bool hasLayer
|
||||||
contentItem.implicitWidth: columnWidth
|
contentItem.implicitWidth: columnWidth
|
||||||
|
|||||||
1
res.qrc
1
res.qrc
@@ -22,7 +22,6 @@
|
|||||||
<file>imports/NeoChat/Component/Emoji/EmojiPicker.qml</file>
|
<file>imports/NeoChat/Component/Emoji/EmojiPicker.qml</file>
|
||||||
<file>imports/NeoChat/Component/Emoji/qmldir</file>
|
<file>imports/NeoChat/Component/Emoji/qmldir</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/qmldir</file>
|
<file>imports/NeoChat/Component/Timeline/qmldir</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/MessageDelegate.qml</file>
|
|
||||||
<file>imports/NeoChat/Component/Timeline/ReplyComponent.qml</file>
|
<file>imports/NeoChat/Component/Timeline/ReplyComponent.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/StateDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/StateDelegate.qml</file>
|
||||||
<file>imports/NeoChat/Component/Timeline/TextDelegate.qml</file>
|
<file>imports/NeoChat/Component/Timeline/TextDelegate.qml</file>
|
||||||
|
|||||||
Reference in New Issue
Block a user