Skip to content
Snippets Groups Projects
TimelineRow.qml 13 KiB
Newer Older
  • Learn to ignore specific revisions
  • Nicolas Werner's avatar
    Nicolas Werner committed
    // SPDX-FileCopyrightText: 2021 Nheko Contributors
    
    // SPDX-FileCopyrightText: 2022 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 double proportionalHeight
        required property int type
        required property string typeString
        required property int originalWidth
        required property string blurhash
        required property string body
        required property string formattedBody
        required property string eventId
        required property string filename
        required property string filesize
        required property string url
        required property string thumbnailUrl
        required property bool isOnlyEmoji
        required property bool isSender
        required property bool isEncrypted
        required property bool isEditable
        required property bool isEdited
    
        required property string replyTo
    
    Nicolas Werner's avatar
    Nicolas Werner committed
        required property string threadId
    
        required property string userId
        required property string userName
    
        required property string roomTopic
        required property string roomName
        required property string callType
    
        required property var reactions
        required property int trustlevel
    
        required property int encryptionError
    
        required property int duration
    
        required property var timestamp
        required property int status
    
        required property int index
    
        required property int relatedEventCacheBuster
    
    Malte E's avatar
    Malte E committed
        hoverEnabled: true
    
        width: parent.width
    
        height: row.height+(reactionRow.height > 0 ? reactionRow.height-2 : 0 )+unreadRow.height
    
    Nicolas Werner's avatar
    Nicolas Werner committed
            color: (Settings.messageHoverHighlight && hovered) ? Nheko.colors.alternateBase : "transparent"
    
            // this looks better without margins
    
    Malte E's avatar
    Malte E committed
            TapHandler {
                acceptedButtons: Qt.RightButton
    
    Nicolas Werner's avatar
    Nicolas Werner committed
                onSingleTapped: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
    
    Malte E's avatar
    Malte E committed
                gesturePolicy: TapHandler.ReleaseWithinBounds
    
                acceptedDevices: PointerDevice.Mouse | PointerDevice.Stylus | PointerDevice.TouchPad
    
    Nicolas Werner's avatar
    Nicolas Werner committed
        onPressAndHold: messageContextMenu.show(eventId, threadId, type, isSender, isEncrypted, isEditable, contentItem.child.hoveredLink, contentItem.child.copyText)
    
        onDoubleClicked: room.reply = eventId
    
    Malte E's avatar
    Malte E committed
        DragHandler {
            id: draghandler
            yAxis.enabled: false
            xAxis.maximum: 100
            xAxis.minimum: -100
            onActiveChanged: {
                if(!active && (x < -70 || x > 70))
    
                    room.reply = eventId
    
    Malte E's avatar
    Malte E committed
            }
        }
        states: State {
            name: "dragging"
            when: draghandler.active
        }
        transitions: Transition {
            from: "dragging"
            to: ""
            PropertyAnimation {
                target: r
                properties: "x"
                easing.type: Easing.InOutQuad
                to: 0
                duration: 100
            }
        }
    
    
        onClicked: {
            let link = contentItem.child.linkAt != undefined && contentItem.child.linkAt(pressX-row.x-msg.x, pressY-row.y-msg.y-contentItem.y);
            if (link) {
                Nheko.openLink(link)
            }
        }
    
    
    Malte E's avatar
    Malte E committed
        Rectangle {
    
            id: row
    
            property bool bubbleOnRight : isSender && Settings.bubbles
    
            anchors.leftMargin: isStateEvent || Settings.smallAvatars? 0 : Nheko.avatarSize+8 // align bubble with section header
    
            anchors.left: isStateEvent? undefined : (bubbleOnRight? undefined : parent.left)
            anchors.right: isStateEvent? undefined: (bubbleOnRight? parent.right : undefined)
            anchors.horizontalCenter: isStateEvent? parent.horizontalCenter : undefined
    
            property int maxWidth: (parent.width-(Settings.smallAvatars || isStateEvent? 0 : Nheko.avatarSize+8))*(Settings.bubbles && !isStateEvent? 0.9 : 1)
    
            width: Settings.bubbles? Math.min(maxWidth,Math.max(reply.implicitWidth+8,contentItem.implicitWidth+metadata.width+20)) : maxWidth
    
    Malte E's avatar
    Malte E committed
            height: msg.height+msg.anchors.margins*2
    
    Malte E's avatar
    Malte E committed
            property color userColor: TimelineManager.userColor(userId, Nheko.colors.base)
            property color bgColor: Nheko.colors.base
            color: (Settings.bubbles && !isStateEvent) ? Qt.tint(bgColor, Qt.hsla(userColor.hslHue, 0.5, userColor.hslLightness, 0.2)) : "#00000000"
            radius: 4
    
    Malte E's avatar
    Malte E committed
    
    
    Malte E's avatar
    Malte E committed
            GridLayout {
                anchors {
                    left: parent.left
                    top: parent.top
                    right: parent.right
                    margins: (Settings.bubbles && ! isStateEvent)? 4 : 2
                    leftMargin: 4
                }
    
    Malte E's avatar
    Malte E committed
                columnSpacing: 2
    
                columns: Settings.bubbles? 1 : 2
                rows: Settings.bubbles? 3 : 2
    
    
                // fancy reply, if this is a reply
                Reply {
    
                    Layout.row: 0
                    Layout.column: 0
                    Layout.fillWidth: true
    
                    Layout.maximumWidth: Settings.bubbles? Number.MAX_VALUE : implicitWidth
    
    Malte E's avatar
    Malte E committed
                    Layout.bottomMargin: visible? 2 : 0
    
                    Layout.preferredHeight: height
    
                    function fromModel(role) {
    
                        return replyTo != "" ? room.dataById(replyTo, role, r.eventId) : null;
    
                    visible: replyTo
    
                    userColor: r.relatedEventCacheBuster, TimelineManager.userColor(userId, Nheko.colors.base)
                    blurhash: r.relatedEventCacheBuster, fromModel(Room.Blurhash) ?? ""
                    body: r.relatedEventCacheBuster, fromModel(Room.Body) ?? ""
                    formattedBody: r.relatedEventCacheBuster, fromModel(Room.FormattedBody) ?? ""
    
                    eventId: fromModel(Room.EventId) ?? ""
    
                    filename: r.relatedEventCacheBuster, fromModel(Room.Filename) ?? ""
                    filesize: r.relatedEventCacheBuster, fromModel(Room.Filesize) ?? ""
                    proportionalHeight: r.relatedEventCacheBuster, fromModel(Room.ProportionalHeight) ?? 1
                    type: r.relatedEventCacheBuster, fromModel(Room.Type) ?? MtxEvent.UnknownMessage
                    typeString: r.relatedEventCacheBuster, fromModel(Room.TypeString) ?? ""
                    url: r.relatedEventCacheBuster, fromModel(Room.Url) ?? ""
                    originalWidth: r.relatedEventCacheBuster, fromModel(Room.OriginalWidth) ?? 0
                    isOnlyEmoji: r.relatedEventCacheBuster, fromModel(Room.IsOnlyEmoji) ?? false
    
                    isStateEvent: r.relatedEventCacheBuster, fromModel(Room.IsStateEvent) ?? false
    
                    userId: r.relatedEventCacheBuster, fromModel(Room.UserId) ?? ""
                    userName: r.relatedEventCacheBuster, fromModel(Room.UserName) ?? ""
                    thumbnailUrl: r.relatedEventCacheBuster, fromModel(Room.ThumbnailUrl) ?? ""
    
                    duration: r.relatedEventCacheBuster, fromModel(Room.Duration) ?? ""
    
                    roomTopic: r.relatedEventCacheBuster, fromModel(Room.RoomTopic) ?? ""
                    roomName: r.relatedEventCacheBuster, fromModel(Room.RoomName) ?? ""
                    callType: r.relatedEventCacheBuster, fromModel(Room.CallType) ?? ""
    
                    encryptionError: r.relatedEventCacheBuster, fromModel(Room.EncryptionError) ?? ""
    
                    relatedEventCacheBuster: r.relatedEventCacheBuster, fromModel(Room.RelatedEventCacheBuster) ?? 0
    
                }
    
                // actual message content
                MessageDelegate {
    
                    Layout.row: 1
                    Layout.column: 0
                    Layout.fillWidth: true
    
                    Layout.preferredHeight: height
    
                    id: contentItem
    
    
                    blurhash: r.blurhash
                    body: r.body
                    formattedBody: r.formattedBody
                    eventId: r.eventId
                    filename: r.filename
                    filesize: r.filesize
                    proportionalHeight: r.proportionalHeight
                    type: r.type
                    typeString: r.typeString ?? ""
                    url: r.url
                    thumbnailUrl: r.thumbnailUrl
    
                    duration: r.duration
    
                    originalWidth: r.originalWidth
                    isOnlyEmoji: r.isOnlyEmoji
    
                    userId: r.userId
                    userName: r.userName
    
                    roomTopic: r.roomTopic
                    roomName: r.roomName
                    callType: r.callType
    
                    encryptionError: r.encryptionError
    
                    relatedEventCacheBuster: r.relatedEventCacheBuster
    
                    isReply: false
    
                    metadataWidth: metadata.width
    
                    Layout.column: Settings.bubbles? 0 : 1
                    Layout.row: Settings.bubbles? 2 : 0
                    Layout.rowSpan: Settings.bubbles? 1 : 2
                    Layout.bottomMargin: -2
    
    Malte E's avatar
    Malte E committed
                    Layout.topMargin: (contentItem.fitsMetadata && Settings.bubbles)? -height-Layout.bottomMargin : 0
    
                    Layout.alignment: Qt.AlignTop | Qt.AlignRight
    
                    Layout.preferredWidth: implicitWidth
    
    Malte E's avatar
    Malte E committed
    
    
                    property double scaling: Settings.bubbles? 0.75 : 1
    
                    property int iconSize: Math.floor(fontMetrics.ascent*scaling)
    
    
                    StatusIndicator {
                        Layout.alignment: Qt.AlignRight | Qt.AlignTop
    
                        height: parent.iconSize
                        width: parent.iconSize
    
                        status: r.status
                        eventId: r.eventId
    
                        anchors.verticalCenter: ts.verticalCenter
    
                        visible: isEdited || eventId == room.edit
    
                        Layout.alignment: Qt.AlignRight | Qt.AlignTop
    
                        height: parent.iconSize
                        width: parent.iconSize
                        sourceSize.width: parent.iconSize * Screen.devicePixelRatio
                        sourceSize.height: parent.iconSize * Screen.devicePixelRatio
    
                        source: "image://colorimage/:/icons/icons/ui/edit.svg?" + ((eventId == room.edit) ? Nheko.colors.highlight : Nheko.colors.buttonText)
    
                        ToolTip.visible: editHovered.hovered
                        ToolTip.delay: Nheko.tooltipDelay
                        ToolTip.text: qsTr("Edited")
    
                        anchors.verticalCenter: ts.verticalCenter
    
                    ImageButton {
                        visible: threadId
                        Layout.alignment: Qt.AlignRight | Qt.AlignTop
                        height: parent.iconSize
                        width: parent.iconSize
                        image: ":/icons/icons/ui/thread.svg"
                        buttonTextColor: TimelineManager.userColor(threadId, Nheko.colors.base)
                        ToolTip.visible: hovered
                        ToolTip.delay: Nheko.tooltipDelay
                        ToolTip.text: qsTr("Part of a thread")
                        anchors.verticalCenter: ts.verticalCenter
                        onClicked: room.thread = threadId
                    }
    
    
                    EncryptionIndicator {
                        visible: room.isEncrypted
                        encrypted: isEncrypted
                        trust: trustlevel
                        Layout.alignment: Qt.AlignRight | Qt.AlignTop
    
                        height: parent.iconSize
                        width: parent.iconSize
                        sourceSize.width: parent.iconSize * Screen.devicePixelRatio
                        sourceSize.height: parent.iconSize * Screen.devicePixelRatio
                        anchors.verticalCenter: ts.verticalCenter
    
                        Layout.alignment: Qt.AlignRight | Qt.AlignTop
    
                        Layout.preferredWidth: implicitWidth
    
                        text: timestamp.toLocaleTimeString(Locale.ShortFormat)
                        color: Nheko.inactiveColors.text
                        ToolTip.visible: ma.hovered
                        ToolTip.delay: Nheko.tooltipDelay
                        ToolTip.text: Qt.formatDateTime(timestamp, Qt.DefaultLocaleLongDate)
    
                        font.pointSize: fontMetrics.font.pointSize*parent.scaling
    
    Malte E's avatar
    Malte E committed
        }
    
    Malte E's avatar
    Malte E committed
        Reactions {
            anchors {
                top: row.bottom
    
                left: row.bubbleOnRight? undefined : row.left
                right: row.bubbleOnRight? row.right : undefined
    
    Malte E's avatar
    Malte E committed
            }
    
            width: row.maxWidth
            layoutDirection: row.bubbleOnRight? Qt.RightToLeft : Qt.LeftToRight
    
    Malte E's avatar
    Malte E committed
            id: reactionRow
    
    Malte E's avatar
    Malte E committed
            reactions: r.reactions
            eventId: r.eventId
    
    
        Rectangle {
            id: unreadRow
            anchors {
                top: reactionRow.bottom
                topMargin: 5
            }
            color: Nheko.colors.highlight
            width: row.maxWidth
    
            visible: (r.index > 0 && (room.fullyReadEventId == r.eventId))
    
            height: visible ? 3 : 0
    
        }