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

import "./components"
import "./delegates"
import "./device-verification"
import "./emoji"
Joe Donofry's avatar
Joe Donofry committed
import "./ui"
import "./voip"
import Qt.labs.platform as Platform
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Particles
import QtQuick.Window
import im.nheko
Nicolas Werner's avatar
Nicolas Werner committed
    required property PrivacyScreen privacyScreen
    property var room: null
    property var roomPreview: null
    property bool shouldEffectsRun: false
Nicolas Werner's avatar
Nicolas Werner committed
    property bool showBackButton: false
Nicolas Werner's avatar
Nicolas Werner committed
    clip: true
    // focus message input on key press, but not on Ctrl-C and such.
Nicolas Werner's avatar
Nicolas Werner committed
    Keys.onPressed: event => {
        if (event.text && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && !topBar.searchHasFocus) {
            TimelineManager.focusMessageInput();
            room.input.setText(room.input.text + event.text);
        }
    }
Nicolas Werner's avatar
Nicolas Werner committed
    onRoomChanged: if (room != null)
        room.triggerSpecialEffects()

    StickerPicker {
        id: emojiPopup
Nicolas Werner's avatar
Nicolas Werner committed
        emoji: true
    }
    Shortcut {
        sequence: StandardKey.Close
Nicolas Werner's avatar
Nicolas Werner committed

        onActivated: Rooms.resetCurrentRoom()
    }
    Label {
        anchors.centerIn: parent
        font.pointSize: 24
Nicolas Werner's avatar
Nicolas Werner committed
        text: qsTr("No room open")
        visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
targetakhil's avatar
targetakhil committed
    }
Joe Donofry's avatar
Joe Donofry committed
    Spinner {
        foreground: palette.mid
Joe Donofry's avatar
Joe Donofry committed
        // height is somewhat arbitrary here... don't set width because width scales w/ height
        height: parent.height / 16
        opacity: hh.hovered ? 0.3 : 1
Nicolas Werner's avatar
Nicolas Werner committed
        running: TimelineManager.isInitialSync
        visible: TimelineManager.isInitialSync
        z: 3
Nicolas Werner's avatar
Nicolas Werner committed
        Behavior on opacity  {
            NumberAnimation {
                duration: 100
            }
Nicolas Werner's avatar
Nicolas Werner committed

    ColumnLayout {
        id: timelineLayout
        anchors.fill: parent
Nicolas Werner's avatar
Nicolas Werner committed
        enabled: visible
Nicolas Werner's avatar
Nicolas Werner committed
        visible: room != null && !room.isSpace
            showBackButton: timelineView.showBackButton
        Rectangle {
            Layout.fillWidth: true
Nicolas Werner's avatar
Nicolas Werner committed
            color: Nheko.theme.separator
Joe Donofry's avatar
Joe Donofry committed

Nicolas Werner's avatar
Nicolas Werner committed
            Layout.fillWidth: true
            color: palette.base
            ColumnLayout {
                anchors.fill: parent
                spacing: 0
                        function onRoomChanged() {
                            stackLayout.currentIndex = 0;
                        target: timelineView
Nicolas Werner's avatar
Nicolas Werner committed
                    MessageView {
Nicolas Werner's avatar
Nicolas Werner committed
                        Layout.fillWidth: true
Nicolas Werner's avatar
Nicolas Werner committed
                        implicitHeight: msgView.height - typingIndicator.height
                        searchString: topBar.searchString
                        source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : ""
Nicolas Werner's avatar
Nicolas Werner committed

                        onLoaded: TimelineManager.setVideoCallItem()
trilene's avatar
trilene committed
                    }
                TypingIndicator {
                    id: typingIndicator
Nicolas Werner's avatar
Nicolas Werner committed
                }
        }
        CallInviteBar {
            id: callInviteBar
            Layout.fillWidth: true
            z: 3
        }
        ActiveCallBar {
            Layout.fillWidth: true
            z: 3
        }
        Rectangle {
            Layout.fillWidth: true
            color: Nheko.theme.separator
Nicolas Werner's avatar
Nicolas Werner committed
            height: 1
            z: 3
        MessageInputWarning {
            text: qsTr("You are about to notify the whole room")
            visible: (room && room.permissions.canPingRoom() && room.input.containsAtRoom)
        }
        MessageInputWarning {
            text: qsTr("The command /%1 is not recognized and will be sent as part of your message").arg(room ? room.input.currentCommand : "")
            visible: room ? room.input.containsInvalidCommand && !room.input.containsIncompleteCommand : false
        }
        MessageInputWarning {
Nicolas Werner's avatar
Nicolas Werner committed
            bubbleColor: Nheko.theme.orange
            text: qsTr("/%1 looks like an incomplete command. To send it anyway, add a space to the end of your message.").arg(room ? room.input.currentCommand : "")
            visible: room ? room.input.containsIncompleteCommand : false
    ColumnLayout {
Nicolas Werner's avatar
Nicolas Werner committed
        property string avatarUrl: room ? room.roomAvatarUrl : (roomPreview ? roomPreview.roomAvatarUrl : "")
        property string reason: roomPreview ? roomPreview.reason : ""
        property string roomId: room ? room.roomId : (roomPreview ? roomPreview.roomid : "")
        property string roomName: room ? room.roomName : (roomPreview ? roomPreview.roomName : "")
        property string roomTopic: room ? room.roomTopic : (roomPreview ? roomPreview.roomTopic : "")

        anchors.fill: parent
        anchors.margins: Nheko.paddingLarge
Nicolas Werner's avatar
Nicolas Werner committed
        enabled: visible
        spacing: Nheko.paddingLarge
Nicolas Werner's avatar
Nicolas Werner committed
        visible: room != null && room.isSpace || roomPreview != null
        Item {
            Layout.fillHeight: true
        }
        Avatar {
Nicolas Werner's avatar
Nicolas Werner committed
            Layout.alignment: Qt.AlignHCenter
            displayName: parent.roomName
Nicolas Werner's avatar
Nicolas Werner committed
            enabled: false
            height: 130
Nicolas Werner's avatar
Nicolas Werner committed
            roomid: parent.roomId
            url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
            width: 130
        }
            Layout.alignment: Qt.AlignHCenter
Nicolas Werner's avatar
Nicolas Werner committed
            spacing: Nheko.paddingMedium
Nicolas Werner's avatar
Nicolas Werner committed
                text: !(roomPreview?.isFetched ?? false) ? qsTr("No preview available") : preview.roomName
Nicolas Werner's avatar
Nicolas Werner committed
                ToolTip.text: qsTr("Settings")
                ToolTip.visible: hovered
                hoverEnabled: true
                image: ":/icons/icons/ui/settings.svg"
                visible: !!room
Nicolas Werner's avatar
Nicolas Werner committed

                onClicked: TimelineManager.openRoomSettings(room.roomId)
            }
            Layout.alignment: Qt.AlignHCenter
Nicolas Werner's avatar
Nicolas Werner committed
            spacing: Nheko.paddingMedium
            visible: !!room
                text: qsTr("%n member(s)", "", room ? room.roomMemberCount : 0)
                ToolTip.text: qsTr("View members of %1").arg(room ? room.roomName : "")
Nicolas Werner's avatar
Nicolas Werner committed
                ToolTip.visible: hovered
                hoverEnabled: true
                image: ":/icons/icons/ui/people.svg"

                onClicked: TimelineManager.openRoomMembers(room)
            }
        }
        ScrollView {
            Layout.alignment: Qt.AlignHCenter
            Layout.fillWidth: true
            Layout.leftMargin: Nheko.paddingLarge
            Layout.rightMargin: Nheko.paddingLarge

            TextArea {
                background: null
                horizontalAlignment: TextEdit.AlignHCenter
Nicolas Werner's avatar
Nicolas Werner committed
                readOnly: true
                selectByMouse: true
                text: (roomPreview?.isFetched ?? false) ? TimelineManager.escapeEmoji(preview.roomTopic) : qsTr("This room is possibly inaccessible. If this room is private, you should remove it from this community.")
                textFormat: TextEdit.RichText
                wrapMode: TextEdit.WordWrap

                onLinkActivated: Nheko.openLink(link)

                NhekoCursorShape {
                    anchors.fill: parent
                    cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
                }
            }
        }
        FlatButton {
            Layout.alignment: Qt.AlignHCenter
            text: qsTr("join the conversation")
Nicolas Werner's avatar
Nicolas Werner committed
            visible: roomPreview && !roomPreview.isInvite

            onClicked: Rooms.joinPreview(roomPreview.roomid)
        }
        FlatButton {
            Layout.alignment: Qt.AlignHCenter
            text: qsTr("accept invite")
Nicolas Werner's avatar
Nicolas Werner committed
            visible: roomPreview && roomPreview.isInvite

            onClicked: Rooms.acceptInvite(roomPreview.roomid)
        }
        FlatButton {
            Layout.alignment: Qt.AlignHCenter
            text: qsTr("decline invite")
Nicolas Werner's avatar
Nicolas Werner committed
            visible: roomPreview && roomPreview.isInvite

            onClicked: Rooms.declineInvite(roomPreview.roomid)
        }
        FlatButton {
            Layout.alignment: Qt.AlignHCenter
            text: qsTr("leave")
Nicolas Werner's avatar
Nicolas Werner committed
            visible: !!room

            onClicked: TimelineManager.openLeaveRoomDialog(room.roomId)
        }
        RowLayout {
            Layout.alignment: Qt.AlignHCenter
            spacing: Nheko.paddingMedium
            visible: roomPreview && roomPreview.isInvite && reasonField.showReason

            MatrixText {
                text: qsTr("Invited by %1 (%2)").arg(TimelineManager.escapeEmoji(inviterAvatar.displayName)).arg(TimelineManager.escapeEmoji(TimelineManager.htmlEscape(inviterAvatar.userid)))
            }
            Avatar {
                id: inviterAvatar

                Layout.alignment: Qt.AlignHCenter
                displayName: roomPreview?.inviterDisplayName ?? ""
                enabled: true
                height: 48
                roomid: preview.roomId
                url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/")
                userid: roomPreview?.inviterUserId ?? ""
                width: 48

                onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId)
            }
        }
        ScrollView {
            id: reasonField
Nicolas Werner's avatar
Nicolas Werner committed

            property bool showReason: false

            Layout.alignment: Qt.AlignHCenter
            Layout.fillWidth: true
            Layout.leftMargin: Nheko.paddingLarge
            Layout.rightMargin: Nheko.paddingLarge
            visible: preview.reason !== "" && showReason

            TextArea {
                background: null
                horizontalAlignment: TextEdit.AlignHCenter
Nicolas Werner's avatar
Nicolas Werner committed
                readOnly: true
                selectByMouse: true
                text: TimelineManager.escapeEmoji(preview.reason)
                textFormat: TextEdit.RichText
                wrapMode: TextEdit.WordWrap
            }
        }
        Button {
            id: showReasonButton

            Layout.alignment: Qt.AlignHCenter
            //Layout.fillWidth: true
            Layout.leftMargin: Nheko.paddingLarge
            Layout.rightMargin: Nheko.paddingLarge
            text: reasonField.showReason ? qsTr("Hide invite reason") : qsTr("Show invite reason")
            visible: roomPreview && roomPreview.isInvite
Nicolas Werner's avatar
Nicolas Werner committed

            onClicked: {
                reasonField.showReason = !reasonField.showReason;
            }
        }
        Item {
            Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
Nicolas Werner's avatar
Nicolas Werner committed
            visible: room != null
        Item {
            Layout.fillHeight: true
        }
    }
Nicolas Werner's avatar
Nicolas Werner committed
        ToolTip.text: qsTr("Back to room list")
        ToolTip.visible: hovered
        anchors.left: parent.left
        anchors.margins: Nheko.paddingMedium
Nicolas Werner's avatar
Nicolas Werner committed
        anchors.top: parent.top
Nicolas Werner's avatar
Nicolas Werner committed
        height: Nheko.avatarSize
        image: ":/icons/icons/ui/angle-arrow-left.svg"
Nicolas Werner's avatar
Nicolas Werner committed
        visible: (room == null || room.isSpace) && showBackButton
        width: Nheko.avatarSize

    TimelineEffects {
        id: timelineEffects
Loren Burkholder's avatar
Loren Burkholder committed
        anchors.fill: parent
Nicolas Werner's avatar
Nicolas Werner committed
        shouldEffectsRun: timelineView.shouldEffectsRun
        anchors.fill: parent
        roomid: room ? room.roomId : ""
    Timer {
        id: effectsTimer
Nicolas Werner's avatar
Nicolas Werner committed

        interval: timelineEffects.maxLifespan
        repeat: false
        running: false

        onTriggered: {
            timelineEffects.removeParticles()
            shouldEffectsRun = false
        }
Nicolas Werner's avatar
Nicolas Werner committed
    }
Nicolas Werner's avatar
Nicolas Werner committed
        function onConfetti() {
            if (!Settings.fancyEffects)
                return;
            shouldEffectsRun = true;
            timelineEffects.pulseConfetti();
            room.markSpecialEffectsDone();
        }
        function onConfettiDone() {
            if (!Settings.fancyEffects)
                return;
            effectsTimer.restart();
        }
        function onOpenReadReceiptsDialog(rr) {
            var dialog = readReceiptsDialog.createObject(timelineRoot, {
Nicolas Werner's avatar
Nicolas Werner committed
                    "readReceipts": rr,
                    "room": room
                });
Nicolas Werner's avatar
Nicolas Werner committed
            timelineRoot.destroyOnClose(dialog);
Nicolas Werner's avatar
Nicolas Werner committed
        function onRainfall() {
            if (!Settings.fancyEffects)
                return;
            shouldEffectsRun = true;
            timelineEffects.pulseRainfall();
            room.markSpecialEffectsDone();
        }
        function onRainfallDone() {
            if (!Settings.fancyEffects)
                return;
            effectsTimer.restart();
        }
        function onShowRawMessageDialog(rawMessage) {
Nicolas Werner's avatar
Nicolas Werner committed
            var component = Qt.createComponent("qrc:/resources/qml/dialogs/RawMessageDialog.qml");
            if (component.status == Component.Ready) {
                var dialog = component.createObject(timelineRoot, {
Nicolas Werner's avatar
Nicolas Werner committed
                        "rawMessage": rawMessage
                    });
                dialog.show();
                timelineRoot.destroyOnClose(dialog);
            } else {
                console.error("Failed to create component: " + component.errorString());
            }