matrixion/qml/MessageView.qml

779 lines
30 KiB
QML
Raw Permalink Normal View History

2021-03-05 02:35:15 +03:00
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
2021-03-05 02:35:15 +03:00
// SPDX-License-Identifier: GPL-3.0-or-later
2022-04-16 03:13:01 +03:00
import "components"
import "delegates"
import "emoji"
import "ui"
import "dialogs"
import Qt.labs.platform 1.1 as Platform
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.2
import QtQuick.Window 2.13
import im.nheko
Item {
id: chatRoot
property int availableWidth: width
2022-04-16 03:13:01 +03:00
property int padding: Nheko.paddingMedium
ScrollBar {
id: scrollbar
anchors.bottom: parent.bottom
2022-04-16 03:13:01 +03:00
anchors.right: parent.right
anchors.top: parent.top
parent: chat.parent
}
2022-02-20 12:09:22 +03:00
ListView {
id: chat
2022-04-16 03:13:01 +03:00
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0)
2022-02-20 12:09:22 +03:00
2022-04-16 03:13:01 +03:00
ScrollBar.vertical: scrollbar
anchors.fill: parent
anchors.rightMargin: scrollbar.interactive ? scrollbar.width : 0
2022-02-20 12:09:22 +03:00
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
//onModelChanged: if (room) room.sendReset()
//reuseItems: true
boundsBehavior: Flickable.StopAtBounds
2022-04-16 03:13:01 +03:00
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
model: room
2022-02-20 12:09:22 +03:00
//pixelAligned: true
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
2022-04-16 03:13:01 +03:00
delegate: Item {
id: wrapper
required property string blurhash
required property string body
required property string callType
required property string day
required property string duration
required property int encryptionError
required property string eventId
required property string filename
required property string filesize
required property string formattedBody
required property int index
required property bool isEditable
required property bool isEdited
required property bool isEncrypted
required property bool isOnlyEmoji
required property bool isSender
required property bool isStateEvent
required property int originalWidth
required property string previousMessageDay
required property bool previousMessageIsStateEvent
required property string previousMessageUserId
required property double proportionalHeight
required property var reactions
required property int relatedEventCacheBuster
required property string replyTo
required property string roomName
required property string roomTopic
property bool scrolledToThis: eventId === chat.model.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
required property int status
required property string thumbnailUrl
required property var timestamp
required property int trustlevel
required property int type
required property string typeString
required property string url
required property string userId
required property string userName
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
height: section.active ? (section.item?.implicitHeight ?? 0) + timelinerow.height : timelinerow.height
width: chat.delegateMaxWidth
Loader {
id: section
property string day: wrapper.day
property bool isSender: wrapper.isSender
property bool isStateEvent: wrapper.isStateEvent
property int parentWidth: parent.width
property string previousMessageDay: wrapper.previousMessageDay
property bool previousMessageIsStateEvent: wrapper.previousMessageIsStateEvent
property string previousMessageUserId: wrapper.previousMessageUserId
property date timestamp: wrapper.timestamp
property string userId: wrapper.userId
property string userName: wrapper.userName
active: previousMessageUserId !== undefined && previousMessageUserId !== userId || previousMessageDay !== day || previousMessageIsStateEvent !== isStateEvent
//asynchronous: true
sourceComponent: sectionHeader
visible: status == Loader.Ready
z: 4
}
TimelineRow {
id: timelinerow
blurhash: wrapper.blurhash
body: wrapper.body
callType: wrapper.callType
duration: wrapper.duration
encryptionError: wrapper.encryptionError
eventId: chat.model, wrapper.eventId
filename: wrapper.filename
filesize: wrapper.filesize
formattedBody: wrapper.formattedBody
isEditable: wrapper.isEditable
isEdited: wrapper.isEdited
isEncrypted: wrapper.isEncrypted
isOnlyEmoji: wrapper.isOnlyEmoji
isSender: wrapper.isSender
isStateEvent: wrapper.isStateEvent
originalWidth: wrapper.originalWidth
proportionalHeight: wrapper.proportionalHeight
reactions: wrapper.reactions
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
replyTo: wrapper.replyTo
roomName: wrapper.roomName
roomTopic: wrapper.roomTopic
status: wrapper.status
thumbnailUrl: wrapper.thumbnailUrl
timestamp: wrapper.timestamp
trustlevel: wrapper.trustlevel
type: chat.model, wrapper.type
typeString: wrapper.typeString
url: wrapper.url
userId: wrapper.userId
userName: wrapper.userName
y: section.item?.implicitHeight ?? 0
background: Rectangle {
id: scrollHighlight
color: timelineRoot.palette.highlight
enabled: false
opacity: 0
visible: true
z: 1
states: State {
name: "revealed"
when: wrapper.scrolledToThis
}
transitions: Transition {
from: ""
to: "revealed"
SequentialAnimation {
PropertyAnimation {
duration: 500
easing.type: Easing.InOutQuad
from: 0
properties: "opacity"
target: scrollHighlight
to: 1
}
PropertyAnimation {
duration: 500
easing.type: Easing.InOutQuad
from: 1
properties: "opacity"
target: scrollHighlight
to: 0
}
ScriptAction {
script: chat.model.eventShown()
}
}
}
}
onHoveredChanged: {
if (!Settings.mobileMode && hovered) {
if (!messageActions.hovered) {
messageActions.attached = timelinerow;
messageActions.model = timelinerow;
}
}
}
}
Connections {
function onMovementEnded() {
if (y + height + 2 * chat.spacing > chat.contentY + chat.height && y < chat.contentY + chat.height)
chat.model.currentIndex = index;
}
target: chat
}
2022-02-20 12:09:22 +03:00
}
2022-04-16 03:13:01 +03:00
footer: Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge
// hacky, but works
height: (loadingSpinner.item?.implicitHeight ?? 0) + 2 * Nheko.paddingLarge
visible: chat.model && chat.model.paginationInProgress
2022-04-16 03:13:01 +03:00
Spinner {
id: loadingSpinner
anchors.centerIn: parent
anchors.margins: Nheko.paddingLarge
foreground: timelineRoot.palette.mid
running: chat.model && chat.model.paginationInProgress
z: 3
}
}
2022-04-16 03:13:01 +03:00
onCountChanged: {
// Mark timeline as read
if (atYEnd && room)
model.currentIndex = 0;
}
2021-12-13 02:43:05 +03:00
2022-03-08 01:16:18 +03:00
Control {
2022-02-20 12:09:22 +03:00
id: messageActions
2021-12-13 02:43:05 +03:00
2022-02-20 12:09:22 +03:00
property Item attached: null
// use comma to update on scroll
2022-03-08 01:16:18 +03:00
property var attachedPos: chat.contentY, attached ? chat.mapFromItem(attached, attached ? attached.width - width : 0, -height) : null
2022-04-16 03:13:01 +03:00
property alias model: row.model
2021-12-13 02:43:05 +03:00
hoverEnabled: true
2022-04-16 03:13:01 +03:00
padding: Nheko.paddingSmall
2022-03-08 01:16:18 +03:00
visible: Settings.buttonsInTimeline && !!attached && (attached.hovered || hovered)
2022-02-20 12:09:22 +03:00
x: attached ? attachedPos.x : 0
y: attached ? attachedPos.y + Nheko.paddingSmall : 0
2022-02-20 12:09:22 +03:00
z: 10
2022-03-08 01:16:18 +03:00
background: Rectangle {
2022-04-15 06:53:41 +03:00
border.color: timelineRoot.palette.placeholderText
2022-03-08 01:19:56 +03:00
border.width: 1
2022-04-16 03:13:01 +03:00
color: timelineRoot.palette.window
2022-03-08 01:19:56 +03:00
radius: padding
2022-02-20 12:09:22 +03:00
}
contentItem: RowLayout {
2022-02-20 12:09:22 +03:00
id: row
2022-02-20 12:09:22 +03:00
property var model
2022-02-20 12:09:22 +03:00
spacing: messageActions.padding
2022-02-20 12:09:22 +03:00
Repeater {
model: Settings.recentReactions
2022-02-20 12:09:22 +03:00
delegate: TextButton {
required property string modelData
Layout.preferredHeight: fontMetrics.height
2022-02-20 12:09:22 +03:00
font.family: Settings.emojiFont
text: modelData
2022-04-16 03:13:01 +03:00
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
onClicked: {
2022-02-20 12:09:22 +03:00
room.input.reaction(row.model.eventId, modelData);
TimelineManager.focusMessageInput();
}
}
2022-02-20 12:09:22 +03:00
}
ImageButton {
id: editButton
2022-04-16 03:13:01 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit")
ToolTip.visible: hovered
2022-04-15 06:53:41 +03:00
buttonTextColor: timelineRoot.palette.placeholderText
2022-02-20 12:09:22 +03:00
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg"
2022-04-16 03:13:01 +03:00
visible: !!row.model && row.model.isEditable
width: 16
2022-02-20 12:09:22 +03:00
onClicked: {
if (row.model.isEditable)
2022-04-16 03:13:01 +03:00
chat.model.editAction(row.model.eventId);
}
2022-02-20 12:09:22 +03:00
}
ImageButton {
id: reactButton
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("React")
2022-04-16 03:13:01 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/smile.svg"
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.Reaction) : false
width: 16
2022-04-16 03:13:01 +03:00
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, function (emoji) {
var event_id = row.model ? row.model.eventId : "";
room.input.reaction(event_id, emoji);
TimelineManager.focusMessageInput();
})
}
2022-02-20 12:09:22 +03:00
ImageButton {
id: replyButton
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply")
2022-04-16 03:13:01 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/reply.svg"
visible: chat.model ? chat.model.permissions.canSend(MtxEvent.TextMessage) : false
width: 16
2022-02-20 12:09:22 +03:00
onClicked: chat.model.replyAction(row.model.eventId)
}
ImageButton {
id: optionsButton
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options")
2022-04-16 03:13:01 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/options.svg"
width: 16
2022-02-20 12:09:22 +03:00
onClicked: messageContextMenu.show(row.model.eventId, row.model.type, row.model.isSender, row.model.isEncrypted, row.model.isEditable, "", row.model.body, optionsButton)
}
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: StandardKey.MoveToPreviousPage
2022-04-16 03:13:01 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
chat.contentY = chat.contentY - chat.height / 2;
chat.returnToBounds();
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: StandardKey.MoveToNextPage
2022-04-16 03:13:01 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
chat.contentY = chat.contentY + chat.height / 2;
chat.returnToBounds();
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: StandardKey.Cancel
2022-04-16 03:13:01 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
if (chat.model.reply)
2022-04-16 03:13:01 +03:00
chat.model.reply = undefined;
2022-02-20 12:09:22 +03:00
else
2022-04-16 03:13:01 +03:00
chat.model.edit = undefined;
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: "Alt+Up"
2022-04-16 03:13:01 +03:00
2022-02-20 12:09:22 +03:00
onActivated: chat.model.reply = chat.model.indexToId(chat.model.reply ? chat.model.idToIndex(chat.model.reply) + 1 : 0)
}
Shortcut {
sequence: "Alt+Down"
2022-04-16 03:13:01 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
var idx = chat.model.reply ? chat.model.idToIndex(chat.model.reply) - 1 : -1;
chat.model.reply = idx >= 0 ? chat.model.indexToId(idx) : null;
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: "Alt+F"
2022-04-16 03:13:01 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
if (chat.model.reply) {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(chat.model.reply);
forwardMess.open();
chat.model.reply = null;
timelineRoot.destroyOnClose(forwardMess);
}
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: "Ctrl+E"
2022-04-16 03:13:01 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
chat.model.edit = chat.model.reply;
}
2022-02-20 12:09:22 +03:00
}
Connections {
function onFocusChanged() {
readTimer.running = TimelineManager.isWindowFocused;
}
2022-02-20 12:09:22 +03:00
target: TimelineManager
}
Timer {
id: readTimer
2022-04-16 03:13:01 +03:00
interval: 1000
2022-02-20 12:09:22 +03:00
// force current read index to update
onTriggered: {
if (chat.model)
2022-04-16 03:13:01 +03:00
chat.model.setCurrentIndex(chat.model.currentIndex);
}
2022-02-20 12:09:22 +03:00
}
Component {
id: sectionHeader
Column {
2022-04-16 03:13:01 +03:00
bottomPadding: Settings.bubbles ? (isSender && previousMessageDay == day ? 0 : 2) : 3
height: ((previousMessageDay !== day) ? dateBubble.height : 0) + (isStateEvent ? 0 : userName.height + 8)
2022-02-20 12:09:22 +03:00
spacing: 8
2022-04-16 03:13:01 +03:00
topPadding: userName_.visible ? 4 : 0
2022-02-20 12:09:22 +03:00
visible: (previousMessageUserId !== userId || previousMessageDay !== day || isStateEvent !== previousMessageIsStateEvent)
width: parentWidth
Label {
id: dateBubble
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
2022-04-11 05:18:16 +03:00
color: timelineRoot.palette.text
2022-02-20 12:09:22 +03:00
height: Math.round(fontMetrics.height * 1.4)
horizontalAlignment: Text.AlignHCenter
2022-04-16 03:13:01 +03:00
text: room ? room.formatDateSeparator(timestamp) : ""
2022-02-20 12:09:22 +03:00
verticalAlignment: Text.AlignVCenter
2022-04-16 03:13:01 +03:00
visible: room && previousMessageDay !== day
width: contentWidth * 1.2
2022-02-20 12:09:22 +03:00
2022-04-16 03:13:01 +03:00
background: Rectangle {
color: timelineRoot.palette.window
radius: parent.height / 2
}
}
Row {
id: userInfo
2022-03-24 03:35:42 +03:00
2022-04-16 03:13:01 +03:00
property int remainingWidth: chat.delegateMaxWidth - spacing - messageUserAvatar.width
2022-03-24 03:35:42 +03:00
2022-04-16 03:13:01 +03:00
height: userName_.height
spacing: 8
visible: !isStateEvent && (!isSender || !Settings.bubbles)
2022-03-24 03:35:42 +03:00
2022-04-16 03:13:01 +03:00
Avatar {
id: messageUserAvatar
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userid
ToolTip.visible: messageUserAvatar.hovered
displayName: userName
height: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1)
url: !room ? "" : room.avatarUrl(userId).replace("mxc://", "image://MxcImage/")
userid: userId
width: Nheko.avatarSize * (Settings.smallAvatars ? 0.5 : 1)
2022-03-24 03:35:42 +03:00
2022-04-16 03:13:01 +03:00
onClicked: room.openUserProfile(userId)
}
Connections {
function onRoomAvatarUrlChanged() {
messageUserAvatar.url = chat.model.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
}
function onScrollToIndex(index) {
chat.positionViewAtIndex(index, ListView.Center);
2022-03-24 03:35:42 +03:00
}
2022-04-16 03:13:01 +03:00
target: chat.model
2022-03-24 03:35:42 +03:00
}
2022-04-16 03:13:01 +03:00
AbstractButton {
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId
ToolTip.visible: hovered
leftInset: 0
leftPadding: 0
rightInset: 0
rightPadding: 0
2022-03-24 03:35:42 +03:00
2022-04-16 03:13:01 +03:00
contentItem: ElidedLabel {
id: userName_
color: TimelineManager.userColor(userId, timelineRoot.palette.base)
elideWidth: Math.min(userInfo.remainingWidth - Math.min(statusMsg.implicitWidth, userInfo.remainingWidth / 3), userName_.fullTextWidth + Nheko.paddingMedium)
fullText: userName
textFormat: Text.RichText
}
2022-04-16 03:13:01 +03:00
onClicked: chat.model.openUserProfile(userId)
2022-04-16 03:13:01 +03:00
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
Label {
id: statusMsg
color: timelineRoot.palette.placeholderText
elide: Text.ElideRight
font.italic: true
text: Presence.userStatus(userId)
textFormat: Text.PlainText
width: userInfo.remainingWidth - userName_.width - parent.spacing
2022-02-20 12:09:22 +03:00
2022-04-16 03:13:01 +03:00
Connections {
function onPresenceChanged(id) {
if (id == userId)
statusMsg.text = Presence.userStatus(userId);
}
2022-02-20 12:09:22 +03:00
2022-04-16 03:13:01 +03:00
target: Presence
}
}
}
2021-07-22 03:37:36 +03:00
}
}
}
Platform.Menu {
id: messageContextMenu
property string eventId
property int eventType
property bool isEditable
2022-04-16 03:13:01 +03:00
property bool isEncrypted
property bool isSender
2022-04-16 03:13:01 +03:00
property string link
property string text
function show(eventId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
eventId = eventId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
isEditable = isEditable_;
isSender = isSender_;
if (text_)
2022-04-16 03:13:01 +03:00
text = text_;
else
2022-04-16 03:13:01 +03:00
text = "";
if (link_)
2022-04-16 03:13:01 +03:00
link = link_;
else
2022-04-16 03:13:01 +03:00
link = "";
if (showAt_)
2022-04-16 03:13:01 +03:00
open(showAt_);
else
2022-04-16 03:13:01 +03:00
open();
}
Component {
id: removeReason
InputDialog {
id: removeReasonDialog
property string eventId
prompt: qsTr("Enter reason for removal or hit enter for no reason:")
2022-04-16 03:13:01 +03:00
title: qsTr("Reason for removal")
onAccepted: function (text) {
room.redactEvent(eventId, text);
}
}
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Copy")
2022-04-16 03:13:01 +03:00
visible: messageContextMenu.text
onTriggered: Clipboard.text = messageContextMenu.text
}
Platform.MenuItem {
enabled: visible
text: qsTr("Copy &link location")
2022-04-16 03:13:01 +03:00
visible: messageContextMenu.link
onTriggered: Clipboard.text = messageContextMenu.link
}
Platform.MenuItem {
id: reactionOption
text: qsTr("Re&act")
2022-04-16 03:13:01 +03:00
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
2022-04-16 03:13:01 +03:00
onTriggered: emojiPopup.show(null, function (emoji) {
room.input.reaction(messageContextMenu.eventId, emoji);
})
}
Platform.MenuItem {
text: qsTr("Repl&y")
2022-04-16 03:13:01 +03:00
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
onTriggered: room.replyAction(messageContextMenu.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Edit")
2022-04-16 03:13:01 +03:00
visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
onTriggered: room.editAction(messageContextMenu.eventId)
}
2021-12-11 08:10:41 +03:00
Platform.MenuItem {
enabled: visible
text: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? qsTr("Un&pin") : qsTr("&Pin")
2022-04-16 03:13:01 +03:00
visible: (room ? room.permissions.canChange(MtxEvent.PinnedEvents) : false)
2021-12-11 08:10:41 +03:00
onTriggered: visible && room.pinnedMessages.includes(messageContextMenu.eventId) ? room.unpin(messageContextMenu.eventId) : room.pin(messageContextMenu.eventId)
}
Platform.MenuItem {
text: qsTr("Read receip&ts")
2022-04-16 03:13:01 +03:00
2021-07-24 21:42:40 +03:00
onTriggered: room.showReadReceipts(messageContextMenu.eventId)
}
Platform.MenuItem {
text: qsTr("&Forward")
2022-04-16 03:13:01 +03:00
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker || messageContextMenu.eventType == MtxEvent.TextMessage || messageContextMenu.eventType == MtxEvent.LocationMessage || messageContextMenu.eventType == MtxEvent.EmoteMessage || messageContextMenu.eventType == MtxEvent.NoticeMessage
onTriggered: {
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(messageContextMenu.eventId);
forwardMess.open();
timelineRoot.destroyOnClose(forwardMess);
}
}
Platform.MenuItem {
text: qsTr("&Mark as read")
}
Platform.MenuItem {
text: qsTr("View raw message")
2022-04-16 03:13:01 +03:00
onTriggered: room.viewRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("View decrypted raw message")
2022-04-16 03:13:01 +03:00
// TODO(Nico): Fix this still being iterated over, when using keyboard to select options
visible: messageContextMenu.isEncrypted
onTriggered: room.viewDecryptedRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
text: qsTr("Remo&ve message")
2022-04-16 03:13:01 +03:00
visible: (room ? room.permissions.canRedact() : false) || messageContextMenu.isSender
onTriggered: function () {
var dialog = removeReason.createObject(timelineRoot);
dialog.eventId = messageContextMenu.eventId;
dialog.show();
dialog.forceActiveFocus();
timelineRoot.destroyOnClose(dialog);
}
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Save as")
2022-04-16 03:13:01 +03:00
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
onTriggered: room.saveMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Open in external program")
2022-04-16 03:13:01 +03:00
visible: messageContextMenu.eventType == MtxEvent.ImageMessage || messageContextMenu.eventType == MtxEvent.VideoMessage || messageContextMenu.eventType == MtxEvent.AudioMessage || messageContextMenu.eventType == MtxEvent.FileMessage || messageContextMenu.eventType == MtxEvent.Sticker
onTriggered: room.openMedia(messageContextMenu.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("Copy link to eve&nt")
2022-04-16 03:13:01 +03:00
visible: messageContextMenu.eventId
onTriggered: room.copyLinkToEvent(messageContextMenu.eventId)
}
}
Component {
id: forwardCompleterComponent
ForwardCompleter {
}
}
2021-08-25 17:10:55 +03:00
Platform.Menu {
id: replyContextMenu
2022-03-24 03:35:42 +03:00
property string eventId
2022-04-16 03:13:01 +03:00
property string link
property string text
2021-08-25 17:10:55 +03:00
2022-03-24 03:35:42 +03:00
function show(text_, link_, eventId_) {
2021-08-25 17:10:55 +03:00
text = text_;
link = link_;
2022-03-24 03:35:42 +03:00
eventId = eventId_;
2021-08-25 17:10:55 +03:00
open();
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Copy")
2022-04-16 03:13:01 +03:00
visible: replyContextMenu.text
2021-08-25 17:10:55 +03:00
onTriggered: Clipboard.text = replyContextMenu.text
}
Platform.MenuItem {
enabled: visible
text: qsTr("Copy &link location")
2022-04-16 03:13:01 +03:00
visible: replyContextMenu.link
2021-08-25 17:10:55 +03:00
onTriggered: Clipboard.text = replyContextMenu.link
}
Platform.MenuItem {
enabled: visible
2021-09-14 00:17:03 +03:00
text: qsTr("&Go to quoted message")
2022-04-16 03:13:01 +03:00
visible: true
2022-03-24 03:35:42 +03:00
onTriggered: chat.model.showEvent(replyContextMenu.eventId)
2021-08-25 17:10:55 +03:00
}
}
2022-03-30 00:11:25 +03:00
RoundButton {
id: toEndButton
2022-04-16 03:13:01 +03:00
2022-03-30 00:11:25 +03:00
property int fullWidth: 40
2022-04-16 03:13:01 +03:00
2022-03-30 00:11:25 +03:00
flat: true
2022-04-16 03:13:01 +03:00
height: width
hoverEnabled: true
2022-04-16 03:13:01 +03:00
radius: width / 2
width: 0
background: Rectangle {
2022-04-15 06:53:41 +03:00
border.color: toEndButton.hovered ? timelineRoot.palette.highlight : timelineRoot.palette.placeholderText
border.width: 1
2022-04-16 03:13:01 +03:00
color: toEndButton.down ? timelineRoot.palette.highlight : timelineRoot.palette.button
opacity: enabled ? 1 : 0.3
radius: toEndButton.radius
}
states: [
State {
name: ""
2022-04-16 03:13:01 +03:00
PropertyChanges {
target: toEndButton
width: 0
}
},
State {
name: "shown"
when: !chat.atYEnd
2022-04-16 03:13:01 +03:00
PropertyChanges {
target: toEndButton
width: toEndButton.fullWidth
}
}
]
transitions: Transition {
from: ""
reversible: true
2022-04-16 03:13:01 +03:00
to: "shown"
2022-03-30 00:11:25 +03:00
SequentialAnimation {
2022-04-16 03:13:01 +03:00
PauseAnimation {
duration: 500
}
2022-03-30 00:11:25 +03:00
PropertyAnimation {
duration: 200
2022-04-16 03:13:01 +03:00
easing.type: Easing.InOutQuad
properties: "width"
target: toEndButton
2022-03-30 00:11:25 +03:00
}
}
}
2022-04-16 03:13:01 +03:00
onClicked: chat.positionViewAtBeginning()
anchors {
bottom: parent.bottom
bottomMargin: Nheko.paddingMedium + (fullWidth - width) / 2
right: scrollbar.left
rightMargin: Nheko.paddingMedium + (fullWidth - width) / 2
}
Image {
id: buttonImg
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
fillMode: Image.PreserveAspectFit
source: "image://colorimage/:/icons/icons/ui/download.svg?" + (toEndButton.down ? timelineRoot.palette.highlightedText : timelineRoot.palette.placeholderText)
}
2022-03-30 00:11:25 +03:00
}
}