Skip to content
Snippets Groups Projects
TimelineView.qml 13.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • // 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
    
            anchors.fill: parent
    
            roomid: room ? room.roomId : ""
    
        Timer {
            id: effectsTimer
    
    Nicolas Werner's avatar
    Nicolas Werner committed
    
    
            interval: timelineEffects.maxLifespan
    
            repeat: false
            running: false
    
    
    Nicolas Werner's avatar
    Nicolas Werner committed
            onTriggered: shouldEffectsRun = false
        }
    
    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:/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());
                }