matrixion/resources/qml/MessageView.qml

940 lines
36 KiB
QML
Raw Normal View History

// SPDX-FileCopyrightText: Nheko Contributors
//
2021-03-05 02:35:15 +03:00
// SPDX-License-Identifier: GPL-3.0-or-later
2021-12-13 02:43:05 +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 1.0
Item {
id: chatRoot
property int availableWidth: width
2023-06-02 02:45:24 +03:00
property int padding: Nheko.paddingMedium
2022-10-06 22:59:59 +03:00
property string searchString: ""
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
Connections {
function onHideMenu() {
2023-06-02 02:45:24 +03:00
messageContextMenu.close();
replyContextMenu.close();
}
2023-06-02 02:45:24 +03:00
target: MainWindow
}
ScrollBar {
id: scrollbar
2023-06-02 02:45:24 +03:00
anchors.bottom: parent.bottom
2023-06-02 02:45:24 +03:00
anchors.right: parent.right
anchors.top: parent.top
parent: chat.parent
}
2022-02-20 12:09:22 +03:00
ListView {
id: chat
2023-06-02 02:45:24 +03:00
property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < chatRoot.availableWidth) ? Settings.timelineMaxWidth : chatRoot.availableWidth) - chatRoot.padding * 2 - (scrollbar.interactive ? scrollbar.width : 0)
readonly property alias filteringInProgress: filteredTimeline.filteringInProgress
2023-06-02 02:45:24 +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
2023-06-02 02:45:24 +03:00
displayMarginBeginning: height / 2
displayMarginEnd: height / 2
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
2022-02-20 12:09:22 +03:00
//pixelAligned: true
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
2023-06-02 02:45:24 +03:00
delegate: Item {
id: wrapper
required property string blurhash
required property string body
required property string callType
required property var 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 notificationlevel
required property int originalWidth
property var previousMessageDay: (index + 1) >= chat.count ? 0 : chat.model.dataByIndex(index + 1, Room.Day)
property bool previousMessageIsStateEvent: (index + 1) >= chat.count ? true : chat.model.dataByIndex(index + 1, Room.IsStateEvent)
property string previousMessageUserId: (index + 1) >= chat.count ? "" : chat.model.dataByIndex(index + 1, Room.UserId)
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 === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)
required property int status
required property string threadId
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
required property int userPowerlevel
2023-06-02 02:45:24 +03:00
ListView.delayRemove: true
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
2023-06-02 03:50:54 +03:00
height: (section.item?.height ?? 0) + timelinerow.height
2023-06-02 02:45:24 +03:00
width: chat.delegateMaxWidth
Loader {
id: section
property var day: wrapper.day
property bool isSender: wrapper.isSender
property bool isStateEvent: wrapper.isStateEvent
property int parentWidth: parent.width
property var 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
property int userPowerlevel: wrapper.userPowerlevel
2023-06-02 02:45:24 +03:00
active: 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
index: wrapper.index
isEditable: wrapper.isEditable
isEdited: wrapper.isEdited
isEncrypted: wrapper.isEncrypted
isOnlyEmoji: wrapper.isOnlyEmoji
isSender: wrapper.isSender
isStateEvent: wrapper.isStateEvent
notificationlevel: wrapper.notificationlevel
originalWidth: wrapper.originalWidth
proportionalHeight: wrapper.proportionalHeight
reactions: wrapper.reactions
relatedEventCacheBuster: wrapper.relatedEventCacheBuster
replyTo: wrapper.replyTo
roomName: wrapper.roomName
roomTopic: wrapper.roomTopic
status: wrapper.status
threadId: wrapper.threadId
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
2023-06-06 01:29:46 +03:00
width: wrapper.width
2023-06-08 02:51:27 +03:00
y: section.visible && section.active ? section.y + section.height : 0
2023-06-02 02:45:24 +03:00
background: Rectangle {
id: scrollHighlight
color: 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: room.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
}
}
footer: Item {
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Nheko.paddingLarge
// hacky, but works
height: loadingSpinner.height + 2 * Nheko.paddingLarge
visible: (room && room.paginationInProgress) || chat.filteringInProgress
Spinner {
id: loadingSpinner
anchors.centerIn: parent
anchors.margins: Nheko.paddingLarge
foreground: palette.mid
running: (room && room.paginationInProgress) || chat.filteringInProgress
z: 3
}
}
Window.onActiveChanged: readTimer.running = Window.active
2022-02-20 12:09:22 +03:00
onCountChanged: {
// Mark timeline as read
2023-06-02 02:45:24 +03:00
if (atYEnd && room)
model.currentIndex = 0;
2022-02-20 12:09:22 +03:00
}
2023-06-02 02:45:24 +03:00
TimelineFilter {
id: filteredTimeline
2021-12-13 02:43:05 +03:00
2023-06-02 02:45:24 +03:00
filterByContent: chatRoot.searchString
filterByThread: room ? room.thread : ""
source: room
}
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
2023-06-02 02:45:24 +03:00
property alias model: row.model
2021-12-13 02:43:05 +03:00
hoverEnabled: true
2023-06-02 02:45:24 +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 {
border.color: palette.buttonText
2022-03-08 01:19:56 +03:00
border.width: 1
2023-06-02 02:45:24 +03:00
color: 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
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
delegate: AbstractButton {
id: button
property color buttonTextColor: palette.buttonText
2023-06-02 02:45:24 +03:00
property color highlightColor: palette.highlight
required property string modelData
property bool showImage: modelData.startsWith("mxc://")
//Layout.preferredHeight: fontMetrics.height
Layout.alignment: Qt.AlignBottom
focusPolicy: Qt.NoFocus
height: showImage ? 16 : buttonText.implicitHeight
implicitHeight: showImage ? 16 : buttonText.implicitHeight
2023-06-02 02:45:24 +03:00
implicitWidth: showImage ? 16 : buttonText.implicitWidth
width: showImage ? 16 : buttonText.implicitWidth
onClicked: {
room.input.reaction(row.model.eventId, modelData);
TimelineManager.focusMessageInput();
}
Label {
id: buttonText
anchors.centerIn: parent
color: button.hovered ? button.highlightColor : button.buttonTextColor
font.family: Settings.emojiFont
horizontalAlignment: Text.AlignHCenter
2023-06-02 02:45:24 +03:00
padding: 0
text: button.modelData
verticalAlignment: Text.AlignVCenter
visible: !button.showImage
}
Image {
id: buttonImg
// Workaround, can't get icon.source working for now...
anchors.fill: parent
2023-06-02 02:45:24 +03:00
fillMode: Image.PreserveAspectFit
source: button.showImage ? (button.modelData.replace("mxc://", "image://MxcImage/") + "?scale") : ""
sourceSize.height: button.height
sourceSize.width: button.width
}
2023-06-19 02:38:40 +03:00
NhekoCursorShape {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
Ripple {
color: Qt.rgba(buttonTextColor.r, buttonTextColor.g, buttonTextColor.b, 0.5)
}
}
2022-02-20 12:09:22 +03:00
}
ImageButton {
2023-06-02 02:45:24 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Edit")
ToolTip.visible: hovered
buttonTextColor: palette.buttonText
2022-02-20 12:09:22 +03:00
hoverEnabled: true
image: ":/icons/icons/ui/edit.svg"
2023-06-02 02:45:24 +03:00
visible: !!row.model && row.model.isEditable
width: 16
2022-02-20 12:09:22 +03:00
onClicked: {
2023-06-02 02:45:24 +03:00
if (row.model.isEditable)
room.edit = row.model.eventId;
}
2022-02-20 12:09:22 +03:00
}
ImageButton {
id: reactButton
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("React")
2023-06-02 02:45:24 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/smile-add.svg"
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
width: 16
2023-06-02 02:45:24 +03:00
onClicked: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(reactButton, room.roomId, function (plaintext, markdown) {
var event_id = row.model ? row.model.eventId : "";
room.input.reaction(event_id, plaintext);
TimelineManager.focusMessageInput();
})
}
2022-09-30 04:27:05 +03:00
ImageButton {
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: (row.model && row.model.threadId) ? qsTr("Reply in thread") : qsTr("New thread")
2023-06-02 02:45:24 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: (row.model && row.model.threadId) ? ":/icons/icons/ui/thread.svg" : ":/icons/icons/ui/new-thread.svg"
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
width: 16
onClicked: room.thread = (row.model.threadId || row.model.eventId)
2022-09-30 04:27:05 +03:00
}
2022-02-20 12:09:22 +03:00
ImageButton {
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Reply")
2023-06-02 02:45:24 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/reply.svg"
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
width: 16
onClicked: room.reply = row.model.eventId
2022-02-20 12:09:22 +03:00
}
ImageButton {
2023-06-02 02:45:24 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Go to message")
ToolTip.visible: hovered
buttonTextColor: palette.buttonText
hoverEnabled: true
image: ":/icons/icons/ui/go-to.svg"
2023-06-02 02:45:24 +03:00
visible: !!row.model && filteredTimeline.filterByContent
width: 16
onClicked: {
topBar.searchString = "";
room.showEvent(row.model.eventId);
}
}
2022-02-20 12:09:22 +03:00
ImageButton {
id: optionsButton
2022-02-20 12:09:22 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("Options")
2023-06-02 02:45:24 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/options.svg"
width: 16
2022-09-30 04:27:05 +03:00
onClicked: messageContextMenu.show(row.model.eventId, row.model.threadId, 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
2023-06-02 02:45:24 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
2023-03-02 21:35:14 +03:00
chat.contentY = chat.contentY - chat.height * 0.9;
2022-02-20 12:09:22 +03:00
chat.returnToBounds();
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: StandardKey.MoveToNextPage
2023-06-02 02:45:24 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
2023-03-02 21:35:14 +03:00
chat.contentY = chat.contentY + chat.height * 0.9;
2022-02-20 12:09:22 +03:00
chat.returnToBounds();
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: StandardKey.Cancel
2023-06-02 02:45:24 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
2023-06-02 02:45:24 +03:00
if (room.input.uploads.length > 0)
room.input.declineUploads();
2023-06-02 02:45:24 +03:00
else if (room.reply)
room.reply = undefined;
else if (room.edit)
room.edit = undefined;
2022-09-30 04:27:05 +03:00
else
2023-06-02 02:45:24 +03:00
room.thread = undefined;
TimelineManager.focusMessageInput();
}
2022-02-20 12:09:22 +03:00
}
// These shortcuts use the room timeline because switching to threads and out is annoying otherwise.
// Better solution welcome.
2022-02-20 12:09:22 +03:00
Shortcut {
sequence: "Alt+Up"
2023-06-02 02:45:24 +03:00
onActivated: room.reply = room.indexToId(room.reply ? room.idToIndex(room.reply) + 1 : 0)
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: "Alt+Down"
2023-06-02 02:45:24 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
var idx = room.reply ? room.idToIndex(room.reply) - 1 : -1;
room.reply = idx >= 0 ? room.indexToId(idx) : null;
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: "Alt+F"
2023-06-02 02:45:24 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
if (room.reply) {
2022-02-20 12:09:22 +03:00
var forwardMess = forwardCompleterComponent.createObject(timelineRoot);
forwardMess.setMessageEventId(room.reply);
2022-02-20 12:09:22 +03:00
forwardMess.open();
room.reply = null;
timelineRoot.destroyOnClose(forwardMess);
}
}
2022-02-20 12:09:22 +03:00
}
Shortcut {
sequence: "Ctrl+E"
2023-06-02 02:45:24 +03:00
2022-02-20 12:09:22 +03:00
onActivated: {
room.edit = room.reply;
}
2022-02-20 12:09:22 +03:00
}
Timer {
id: readTimer
2023-06-02 02:45:24 +03:00
interval: 1000
2022-02-20 12:09:22 +03:00
// force current read index to update
onTriggered: {
if (room)
2023-06-02 02:45:24 +03:00
room.setCurrentIndex(room.currentIndex);
}
2022-02-20 12:09:22 +03:00
}
Component {
id: sectionHeader
Column {
2023-06-02 02:45:24 +03:00
bottomPadding: Settings.bubbles ? (isSender && previousMessageDay == day ? 0 : 2) : 3
2022-02-20 12:09:22 +03:00
spacing: 8
2023-06-02 02:45:24 +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
color: palette.text
2022-02-20 12:09:22 +03:00
height: Math.round(fontMetrics.height * 1.4)
horizontalAlignment: Text.AlignHCenter
2023-06-02 02:45:24 +03:00
text: room ? room.formatDateSeparator(timestamp) : ""
2022-02-20 12:09:22 +03:00
verticalAlignment: Text.AlignVCenter
2023-06-02 02:45:24 +03:00
visible: room && previousMessageDay !== day
width: contentWidth * 1.2
2022-02-20 12:09:22 +03:00
background: Rectangle {
color: palette.window
2023-06-02 02:45:24 +03:00
radius: parent.height / 2
2022-02-20 12:09:22 +03:00
}
}
Row {
id: userInfo
2022-02-20 12:09:22 +03:00
property int remainingWidth: chat.delegateMaxWidth - spacing - messageUserAvatar.width
2022-03-24 03:35:42 +03:00
2023-06-02 02:45:24 +03:00
height: userName_.height
spacing: 8
visible: !isStateEvent && (!isSender || !Settings.bubbles)
2022-03-24 03:35:42 +03:00
2023-06-02 02:45:24 +03:00
Avatar {
id: messageUserAvatar
2022-03-24 03:35:42 +03:00
2023-06-02 02:45:24 +03:00
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)
onClicked: room.openUserProfile(userId)
}
Connections {
function onRoomAvatarUrlChanged() {
messageUserAvatar.url = room.avatarUrl(userId).replace("mxc://", "image://MxcImage/");
}
function onScrollToIndex(index) {
chat.positionViewAtIndex(index, ListView.Center);
2022-03-24 03:35:42 +03:00
}
2023-06-02 02:45:24 +03:00
target: room
2022-03-24 03:35:42 +03:00
}
2023-08-10 11:15:12 +03:00
2023-06-02 02:45:24 +03:00
AbstractButton {
id: userNameButton
2022-03-24 03:35:42 +03:00
2023-08-10 11:15:12 +03:00
PowerlevelIndicator {
id: powerlevelIndicator
anchors.left: parent.left
//anchors.horizontalCenter: parent.horizontalCenter
powerlevel: userPowerlevel
permissions: room ? room.permissions : null
visible: isAdmin || isModerator
}
2023-06-02 02:45:24 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: userId
ToolTip.visible: hovered
2023-08-10 11:15:12 +03:00
leftPadding: powerlevelIndicator.visible ? 16 : 0
2023-06-02 02:45:24 +03:00
leftInset: 0
rightInset: 0
rightPadding: 0
2022-03-08 01:16:18 +03:00
2023-06-03 01:30:44 +03:00
contentItem: Label {
2023-06-02 02:45:24 +03:00
id: userName_
2023-06-02 02:45:24 +03:00
color: TimelineManager.userColor(userId, palette.base)
2023-06-03 01:30:44 +03:00
text: TimelineManager.escapeEmoji(userNameTextMetrics.elidedText)
2023-06-02 02:45:24 +03:00
textFormat: Text.RichText
}
2023-06-02 02:45:24 +03:00
onClicked: room.openUserProfile(userId)
2023-06-03 01:30:44 +03:00
TextMetrics {
id: userNameTextMetrics
elide: Text.ElideRight
elideWidth: userInfo.remainingWidth - Math.min(statusMsg.implicitWidth, userInfo.remainingWidth / 3)
text: userName
}
2023-06-19 02:38:40 +03:00
NhekoCursorShape {
2023-06-02 02:45:24 +03:00
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
}
}
Label {
id: statusMsg
2023-06-02 02:45:24 +03:00
property string userStatus: Presence.userStatus(userId)
2022-02-20 12:09:22 +03:00
2023-06-02 02:45:24 +03:00
ToolTip.delay: Nheko.tooltipDelay
ToolTip.text: qsTr("%1's status message").arg(userName)
ToolTip.visible: statusMsgHoverHandler.hovered
anchors.baseline: userNameButton.baseline
color: palette.buttonText
elide: Text.ElideRight
font.italic: true
font.pointSize: Math.floor(fontMetrics.font.pointSize * 0.8)
text: userStatus.replace(/\n/g, " ")
textFormat: Text.PlainText
width: Math.min(implicitWidth, userInfo.remainingWidth - userName_.width - parent.spacing)
2022-02-20 12:09:22 +03:00
2023-06-02 02:45:24 +03:00
HoverHandler {
id: statusMsgHoverHandler
}
Connections {
function onPresenceChanged(id) {
if (id == userId)
statusMsg.userStatus = Presence.userStatus(userId);
}
2021-07-22 14:55:12 +03:00
2023-06-02 02:45:24 +03:00
target: Presence
}
}
}
}
}
}
Platform.Menu {
id: messageContextMenu
property string eventId
property int eventType
property bool isEditable
2023-06-02 02:45:24 +03:00
property bool isEncrypted
property bool isSender
2023-06-02 02:45:24 +03:00
property string link
property string text
property string threadId
2022-09-30 04:27:05 +03:00
function show(eventId_, threadId_, eventType_, isSender_, isEncrypted_, isEditable_, link_, text_, showAt_) {
eventId = eventId_;
2022-09-30 04:27:05 +03:00
threadId = threadId_;
eventType = eventType_;
isEncrypted = isEncrypted_;
isEditable = isEditable_;
isSender = isSender_;
if (text_)
2023-06-02 02:45:24 +03:00
text = text_;
else
2023-06-02 02:45:24 +03:00
text = "";
if (link_)
2023-06-02 02:45:24 +03:00
link = link_;
else
2023-06-02 02:45:24 +03:00
link = "";
if (showAt_)
2023-06-02 02:45:24 +03:00
open(showAt_);
else
2023-06-02 02:45:24 +03:00
open();
}
Component {
id: removeReason
2023-06-02 02:45:24 +03:00
InputDialog {
id: removeReasonDialog
property string eventId
prompt: qsTr("Enter reason for removal or hit enter for no reason:")
2023-06-02 02:45:24 +03:00
title: qsTr("Reason for removal")
onAccepted: function (text) {
room.redactEvent(eventId, text);
}
}
}
Platform.MenuItem {
2023-06-02 02:45:24 +03:00
enabled: visible
text: qsTr("Go to &message")
visible: filteredTimeline.filterByContent
onTriggered: function () {
topBar.searchString = "";
room.showEvent(messageContextMenu.eventId);
}
2023-06-02 02:45:24 +03:00
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Copy")
2023-06-02 02:45:24 +03:00
visible: messageContextMenu.text
onTriggered: Clipboard.text = messageContextMenu.text
}
Platform.MenuItem {
enabled: visible
text: qsTr("Copy &link location")
2023-06-02 02:45:24 +03:00
visible: messageContextMenu.link
onTriggered: Clipboard.text = messageContextMenu.link
}
Platform.MenuItem {
id: reactionOption
text: qsTr("Re&act")
2023-06-02 02:45:24 +03:00
visible: room ? room.permissions.canSend(MtxEvent.Reaction) : false
2023-06-02 02:45:24 +03:00
onTriggered: emojiPopup.visible ? emojiPopup.close() : emojiPopup.show(null, room.roomId, function (plaintext, markdown) {
room.input.reaction(messageContextMenu.eventId, plaintext);
TimelineManager.focusMessageInput();
})
}
Platform.MenuItem {
text: qsTr("Repl&y")
2023-06-02 02:45:24 +03:00
visible: room ? room.permissions.canSend(MtxEvent.TextMessage) : false
2022-09-30 04:27:05 +03:00
onTriggered: room.reply = (messageContextMenu.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Edit")
2023-06-02 02:45:24 +03:00
visible: messageContextMenu.isEditable && (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
2022-09-30 04:27:05 +03:00
onTriggered: room.edit = (messageContextMenu.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("&Thread")
2023-06-02 02:45:24 +03:00
visible: (room ? room.permissions.canSend(MtxEvent.TextMessage) : false)
2022-09-30 04:27:05 +03:00
onTriggered: room.thread = (messageContextMenu.threadId || 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")
2023-06-02 02:45:24 +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 {
2022-09-30 04:27:05 +03:00
text: qsTr("&Read receipts")
2023-06-02 02:45:24 +03:00
2021-07-24 21:42:40 +03:00
onTriggered: room.showReadReceipts(messageContextMenu.eventId)
}
Platform.MenuItem {
text: qsTr("&Forward")
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +03:00
onTriggered: room.viewRawMessage(messageContextMenu.eventId)
}
Platform.MenuItem {
enabled: visible
text: qsTr("View decrypted raw message")
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +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
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +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")
2023-06-02 02:45:24 +03:00
visible: true
onTriggered: room.showEvent(replyContextMenu.eventId)
2021-08-25 17:10:55 +03:00
}
}
2022-03-30 00:11:25 +03:00
RoundButton {
id: toEndButton
2023-06-02 02:45:24 +03:00
2022-03-30 00:11:25 +03:00
property int fullWidth: 40
2023-06-02 02:45:24 +03:00
2022-03-30 00:11:25 +03:00
flat: true
2023-06-02 02:45:24 +03:00
height: width
hoverEnabled: true
2023-06-02 02:45:24 +03:00
radius: width / 2
width: 0
background: Rectangle {
border.color: toEndButton.hovered ? palette.highlight : palette.buttonText
border.width: 1
2023-06-02 02:45:24 +03:00
color: toEndButton.down ? palette.highlight : palette.button
opacity: enabled ? 1 : 0.3
radius: toEndButton.radius
}
states: [
State {
name: ""
2023-06-02 02:45:24 +03:00
PropertyChanges {
target: toEndButton
width: 0
}
},
State {
name: "shown"
when: !chat.atYEnd
2023-06-02 02:45:24 +03:00
PropertyChanges {
target: toEndButton
width: toEndButton.fullWidth
}
}
]
transitions: Transition {
from: ""
reversible: true
2023-06-02 02:45:24 +03:00
to: "shown"
2022-03-30 00:11:25 +03:00
SequentialAnimation {
2023-06-02 02:45:24 +03:00
PauseAnimation {
duration: 500
}
2022-03-30 00:11:25 +03:00
PropertyAnimation {
duration: 200
2023-06-02 02:45:24 +03:00
easing.type: Easing.InOutQuad
properties: "width"
target: toEndButton
2022-03-30 00:11:25 +03:00
}
}
}
2023-06-02 02:45:24 +03:00
onClicked: function () {
chat.positionViewAtBeginning();
TimelineManager.focusMessageInput();
}
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 ? palette.highlightedText : palette.buttonText)
}
2022-03-30 00:11:25 +03:00
}
}