Skip to content
Snippets Groups Projects
TimelineView.qml 13.7 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"
    
    Nicolas Werner's avatar
    Nicolas Werner committed
    import Qt.labs.platform 1.1 as Platform
    
    import QtQuick.Controls 2.5
    
    import QtQuick.Layouts 1.3
    
    Loren Burkholder's avatar
    Loren Burkholder committed
    import QtQuick.Particles 2.15
    
    import QtQuick.Window 2.13
    
    import im.nheko.EmojiModel 1.0
    
    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
    
                        Loader {
                            source: CallManager.isOnCall && CallManager.callType != CallType.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)
    
                    CursorShape {
                        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());
                }