Skip to content
Snippets Groups Projects
TimelineRow.qml 13.6 KiB
Newer Older
// SPDX-FileCopyrightText: Nheko Contributors
Nicolas Werner's avatar
Nicolas Werner committed
// SPDX-License-Identifier: GPL-3.0-or-later

import "./delegates"
import "./emoji"
Malte E's avatar
Malte E committed
import QtQuick 2.15
import QtQuick.Controls 2.3
import QtQuick.Layouts 1.2
import QtQuick.Window 2.13
Malte E's avatar
Malte E committed
AbstractButton {
    id: r

    required property string blurhash
    required property string body
Nicolas Werner's avatar
Nicolas Werner committed
    required property string callType
    required property int duration
    required property int encryptionError
    required property string eventId
    required property string filename
    required property string filesize
Nicolas Werner's avatar
Nicolas Werner committed
    required property string formattedBody
    required property int index
    required property bool isEditable
    required property bool isEdited
Nicolas Werner's avatar
Nicolas Werner committed
    required property bool isEncrypted
    required property bool isOnlyEmoji
    required property bool isSender
Nicolas Werner's avatar
Nicolas Werner committed
    required property int notificationlevel
    required property int originalWidth
    required property double proportionalHeight
    required property var reactions
    required property int relatedEventCacheBuster
    required property string replyTo
Nicolas Werner's avatar
Nicolas Werner committed
    required property string roomName
    required property string roomTopic
    required property int status
Nicolas Werner's avatar
Nicolas Werner committed
    required property string threadId
Nicolas Werner's avatar
Nicolas Werner committed
    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

Nicolas Werner's avatar
Nicolas Werner committed
    height: row.height + (reactionRow.height > 0 ? reactionRow.height - 2 : 0) + unreadRow.height
Malte E's avatar
Malte E committed
    hoverEnabled: true
Malte E's avatar
Malte E committed
    states: State {
        name: "dragging"
        when: draghandler.active
    }
    transitions: Transition {
        from: "dragging"
        to: ""
Nicolas Werner's avatar
Nicolas Werner committed

Malte E's avatar
Malte E committed
        PropertyAnimation {
Nicolas Werner's avatar
Nicolas Werner committed
            duration: 100
Malte E's avatar
Malte E committed
            easing.type: Easing.InOutQuad
Nicolas Werner's avatar
Nicolas Werner committed
            properties: "x"
            target: r
Malte E's avatar
Malte E committed
            to: 0
        }
    }

Nicolas Werner's avatar
Nicolas Werner committed
        let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX - row.x - msg.x, pressY - row.y - msg.y - contentItem.y);
Nicolas Werner's avatar
Nicolas Werner committed
            Nheko.openLink(link);
Nicolas Werner's avatar
Nicolas Werner committed
    onDoubleClicked: room.reply = eventId
    onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)

    Rectangle {
        anchors.fill: parent
        color: (Settings.messageHoverHighlight && hovered) ? palette.alternateBase : "transparent"

        // this looks better without margins
        TapHandler {
            acceptedButtons: Qt.RightButton
            acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
            gesturePolicy: TapHandler.ReleaseWithinBounds

            onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
        }
    }
    DragHandler {
        id: draghandler

        xAxis.maximum: 100
        xAxis.minimum: -100
        yAxis.enabled: false
Nicolas Werner's avatar
Nicolas Werner committed
        onActiveChanged: {
            if (!active && (x < -70 || x > 70))
                room.reply = eventId;
        }
    }
    AbstractButton {
Nicolas Werner's avatar
Nicolas Werner committed
        ToolTip.delay: Nheko.tooltipDelay
        ToolTip.text: qsTr("Part of a thread")
        ToolTip.visible: hovered
        anchors.left: parent.left
Nicolas Werner's avatar
Nicolas Werner committed
        anchors.leftMargin: Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8) // align bubble with section header
        height: parent.height
        visible: threadId
        width: 4
Nicolas Werner's avatar
Nicolas Werner committed

        onClicked: room.thread = threadId

        Rectangle {
            id: threadLine

            anchors.fill: parent
Nicolas Werner's avatar
Nicolas Werner committed
            color: TimelineManager.userColor(threadId, palette.base)
Malte E's avatar
Malte E committed
    Rectangle {
        id: row
        property color bgColor: palette.base
Nicolas Werner's avatar
Nicolas Werner committed
        property bool bubbleOnRight: isSender && Settings.bubbles
        property int maxWidth: (parent.width - (Settings.smallAvatars || isStateEvent ? 0 : Nheko.avatarSize + 8)) * (Settings.bubbles && !isStateEvent ? 0.9 : 1)
        property color userColor: TimelineManager.userColor(userId, palette.base)

        anchors.horizontalCenter: isStateEvent ? parent.horizontalCenter : undefined
        anchors.left: (isStateEvent || bubbleOnRight) ? undefined : parent.left
        anchors.leftMargin: (isStateEvent || Settings.smallAvatars ? 0 : (Nheko.avatarSize + 8)) + (threadId ? 6 : 0) // align bubble with section header
        anchors.right: (isStateEvent || !bubbleOnRight) ? undefined : parent.right
        border.color: Nheko.theme.red
        border.width: r.notificationlevel == MtxEvent.Highlight ? 1 : 0
Malte E's avatar
Malte E committed
        color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
Nicolas Werner's avatar
Nicolas Werner committed
        height: msg.height + msg.anchors.margins * 2
Malte E's avatar
Malte E committed
        radius: 4
Nicolas Werner's avatar
Nicolas Werner committed
        width: Settings.bubbles ? Math.min(maxWidth, Math.max(reply.implicitWidth + 8, contentItem.implicitWidth + metadata.width + 20)) : maxWidth
Malte E's avatar
Malte E committed

Malte E's avatar
Malte E committed
        GridLayout {
Nicolas Werner's avatar
Nicolas Werner committed
            id: msg

            columnSpacing: 2
            columns: Settings.bubbles ? 1 : 2
            rowSpacing: 0
            rows: Settings.bubbles ? 3 : 2
Malte E's avatar
Malte E committed
            anchors {
                left: parent.left
                leftMargin: 4
Nicolas Werner's avatar
Nicolas Werner committed
                margins: (Settings.bubbles && !isStateEvent) ? 4 : 2
                right: parent.right
                rightMargin: 4
Nicolas Werner's avatar
Nicolas Werner committed
                top: parent.top

            // fancy reply, if this is a reply
            Reply {
                function fromModel(role) {
                    return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
Nicolas Werner's avatar
Nicolas Werner committed

                Layout.bottomMargin: visible ? 2 : 0
                Layout.column: 0
                Layout.fillWidth: true
                Layout.maximumWidth: Settings.bubbles ? Number.MAX_VALUE : implicitWidth
                Layout.preferredHeight: height
                Layout.row: 0
                blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
                body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
                callType: r.relatedEventCacheBuster, fromModel(Room.Voip) ?? ""
Nicolas Werner's avatar
Nicolas Werner committed
                duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? 0
                encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? 0
                eventId: fromModel(Room.EventId) ?? ""
                filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
                filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
Nicolas Werner's avatar
Nicolas Werner committed
                formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
                isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
                isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
                originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
                proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
Nicolas Werner's avatar
Nicolas Werner committed
                relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
                roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
                roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
                thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
                type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
                typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
                url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
Nicolas Werner's avatar
Nicolas Werner committed
                userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, palette.base)
                userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
                userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
Nicolas Werner's avatar
Nicolas Werner committed
                visible: replyTo
            }

            // actual message content
            MessageDelegate {
Nicolas Werner's avatar
Nicolas Werner committed
                id: contentItem

                Layout.column: 0
                Layout.fillWidth: true
                Layout.preferredHeight: height
Nicolas Werner's avatar
Nicolas Werner committed
                Layout.row: 1
                blurhash: r.blurhash
                body: r.body
Nicolas Werner's avatar
Nicolas Werner committed
                callType: r.callType
                duration: r.duration
                encryptionError: r.encryptionError
                eventId: r.eventId
                filename: r.filename
                filesize: r.filesize
Nicolas Werner's avatar
Nicolas Werner committed
                formattedBody: r.formattedBody
                isOnlyEmoji: r.isOnlyEmoji
                isReply: false
                isStateEvent: r.isStateEvent
                metadataWidth: metadata.width
                originalWidth: r.originalWidth
                proportionalHeight: r.proportionalHeight
Nicolas Werner's avatar
Nicolas Werner committed
                relatedEventCacheBuster: r.relatedEventCacheBuster
                roomName: r.roomName
                roomTopic: r.roomTopic
                thumbnailUrl: r.thumbnailUrl
                type: r.type
                typeString: r.typeString ?? ""
                url: r.url
                userId: r.userId
                userName: r.userName
Nicolas Werner's avatar
Nicolas Werner committed

                property int iconSize: Math.floor(fontMetrics.ascent * scaling)
                property double scaling: Settings.bubbles ? 0.75 : 1

                Layout.alignment: Qt.AlignTop | Qt.AlignRight
Nicolas Werner's avatar
Nicolas Werner committed
                Layout.bottomMargin: -2
                Layout.column: Settings.bubbles ? 0 : 1
                Layout.preferredWidth: implicitWidth
Nicolas Werner's avatar
Nicolas Werner committed
                Layout.row: Settings.bubbles ? 2 : 0
                Layout.rowSpan: Settings.bubbles ? 1 : 2
                Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles) ? -height - Layout.bottomMargin : 0
Nicolas Werner's avatar
Nicolas Werner committed
                visible: !isStateEvent
                    Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Nicolas Werner's avatar
Nicolas Werner committed
                    eventId: r.eventId
                    height: parent.iconSize
Nicolas Werner's avatar
Nicolas Werner committed
                    width: parent.iconSize
                    Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
                    ToolTip.delay: Nheko.tooltipDelay
                    ToolTip.text: qsTr("Edited")
Nicolas Werner's avatar
Nicolas Werner committed
                    ToolTip.visible: editHovered.hovered
                    height: parent.iconSize
                    source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? palette.highlight : palette.buttonText)
                    sourceSize.height: parent.iconSize * Screen.devicePixelRatio
                    sourceSize.width: parent.iconSize * Screen.devicePixelRatio
                    visible: isEdited || eventId == room.edit
                    width: parent.iconSize
Nicolas Werner's avatar
Nicolas Werner committed
                    }
                ImageButton {
                    Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
                    ToolTip.delay: Nheko.tooltipDelay
                    ToolTip.text: qsTr("Part of a thread")
Nicolas Werner's avatar
Nicolas Werner committed
                    ToolTip.visible: hovered
                    buttonTextColor: TimelineManager.userColor(threadId, palette.base)
                    height: parent.iconSize
                    image: ":/icons/icons/ui/thread.svg"
                    visible: threadId
                    width: parent.iconSize

                    onClicked: room.thread = threadId
                }
                    Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Nicolas Werner's avatar
Nicolas Werner committed
                    encrypted: isEncrypted
                    height: parent.iconSize
                    sourceSize.height: parent.iconSize * Screen.devicePixelRatio
Nicolas Werner's avatar
Nicolas Werner committed
                    sourceSize.width: parent.iconSize * Screen.devicePixelRatio
                    trust: trustlevel
                    visible: room.isEncrypted
                    width: parent.iconSize
Nicolas Werner's avatar
Nicolas Werner committed

                    Layout.alignment: Qt.AlignRight | Qt.AlignTop
                    Layout.preferredWidth: implicitWidth
                    ToolTip.delay: Nheko.tooltipDelay
                    ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
Nicolas Werner's avatar
Nicolas Werner committed
                    ToolTip.visible: ma.hovered
                    color: palette.inactive.text
                    font.pointSize: fontMetrics.font.pointSize * parent.scaling
                    text: timestamp.toLocaleTimeString(Locale.ShortFormat)

Nicolas Werner's avatar
Nicolas Werner committed
                    }
Malte E's avatar
Malte E committed
    }
    Reactions {
Nicolas Werner's avatar
Nicolas Werner committed
        id: reactionRow

        eventId: r.eventId
        layoutDirection: row.bubbleOnRight ? Qt.RightToLeft : Qt.LeftToRight
        reactions: r.reactions
        width: row.maxWidth

Malte E's avatar
Malte E committed
        anchors {
Nicolas Werner's avatar
Nicolas Werner committed
            left: row.bubbleOnRight ? undefined : row.left
            right: row.bubbleOnRight ? row.right : undefined
Malte E's avatar
Malte E committed
            top: row.bottom
Malte E's avatar
Malte E committed
        }
    Rectangle {
        id: unreadRow
Nicolas Werner's avatar
Nicolas Werner committed

        color: palette.highlight
        height: visible ? 3 : 0
        visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))

Nicolas Werner's avatar
Nicolas Werner committed
            left: parent.left
            right: parent.right
Nicolas Werner's avatar
Nicolas Werner committed
            top: reactionRow.bottom
            topMargin: 5