matrixion/resources/qml/TimelineView.qml

451 lines
14 KiB
QML
Raw Normal View History

// SPDX-FileCopyrightText: Nheko Contributors
2021-03-14 04:45:20 +03:00
//
2021-03-05 02:35:15 +03:00
// SPDX-License-Identifier: GPL-3.0-or-later
import "./components"
2020-10-08 22:11:21 +03:00
import "./emoji"
2021-03-12 07:09:57 +03:00
import "./ui"
2021-07-10 18:21:15 +03:00
import "./voip"
2023-06-19 02:38:40 +03:00
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
import im.nheko
Item {
id: timelineView
2021-04-27 12:08:21 +03:00
2023-06-02 02:45:24 +03:00
required property PrivacyScreen privacyScreen
property var room: null
property var roomPreview: null
property bool shouldEffectsRun: false
2023-06-02 02:45:24 +03:00
property bool showBackButton: false
2023-05-25 20:20:25 +03:00
2023-06-02 02:45:24 +03:00
clip: true
2023-05-25 20:20:25 +03:00
2022-12-14 21:04:08 +03:00
// focus message input on key press, but not on Ctrl-C and such.
2023-06-02 02:45:24 +03:00
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);
}
}
2023-06-02 02:45:24 +03:00
onRoomChanged: if (room != null)
room.triggerSpecialEffects()
StickerPicker {
id: emojiPopup
2023-06-02 02:45:24 +03:00
emoji: true
}
2022-04-03 03:28:44 +03:00
Shortcut {
sequence: StandardKey.Close
2023-06-02 02:45:24 +03:00
2022-04-03 03:28:44 +03:00
onActivated: Rooms.resetCurrentRoom()
}
Label {
anchors.centerIn: parent
font.pointSize: 24
2023-06-02 02:45:24 +03:00
text: qsTr("No room open")
visible: !room && !TimelineManager.isInitialSync && (!roomPreview || !roomPreview.roomid)
2021-04-11 17:31:49 +03:00
}
2021-03-12 07:09:57 +03:00
Spinner {
anchors.centerIn: parent
foreground: palette.mid
2021-03-12 07:09:57 +03:00
// height is somewhat arbitrary here... don't set width because width scales w/ height
height: parent.height / 16
2022-04-20 06:42:47 +03:00
opacity: hh.hovered ? 0.3 : 1
2023-06-02 02:45:24 +03:00
running: TimelineManager.isInitialSync
visible: TimelineManager.isInitialSync
z: 3
2022-04-20 06:42:47 +03:00
2023-06-02 02:45:24 +03:00
Behavior on opacity {
NumberAnimation {
duration: 100
}
2022-04-20 06:42:47 +03:00
}
HoverHandler {
id: hh
2023-06-02 02:45:24 +03:00
2022-04-20 06:42:47 +03:00
}
}
ColumnLayout {
id: timelineLayout
2021-04-11 23:24:39 +03:00
2020-10-08 22:11:21 +03:00
anchors.fill: parent
2023-06-02 02:45:24 +03:00
enabled: visible
spacing: 0
2023-06-02 02:45:24 +03:00
visible: room != null && !room.isSpace
2021-01-12 22:22:52 +03:00
TopBar {
2022-10-06 22:59:59 +03:00
id: topBar
2021-06-08 23:18:51 +03:00
showBackButton: timelineView.showBackButton
2020-10-08 22:11:21 +03:00
}
Rectangle {
Layout.fillWidth: true
2023-06-02 02:45:24 +03:00
color: Nheko.theme.separator
2023-10-26 17:43:09 +03:00
implicitHeight: 1
2020-10-08 22:11:21 +03:00
z: 3
}
Rectangle {
id: msgView
2021-01-26 08:03:09 +03:00
Layout.fillHeight: true
2023-06-02 02:45:24 +03:00
Layout.fillWidth: true
color: palette.base
2020-10-08 22:11:21 +03:00
ColumnLayout {
anchors.fill: parent
spacing: 0
2020-11-15 06:52:49 +03:00
StackLayout {
id: stackLayout
2020-10-08 22:11:21 +03:00
currentIndex: 0
2020-11-15 06:52:49 +03:00
Connections {
function onRoomChanged() {
stackLayout.currentIndex = 0;
2020-10-08 22:11:21 +03:00
}
target: timelineView
}
2021-08-29 20:24:44 +03:00
MessageView {
2023-06-02 02:45:24 +03:00
Layout.fillWidth: true
2021-08-29 20:24:44 +03:00
implicitHeight: msgView.height - typingIndicator.height
2022-10-06 22:59:59 +03:00
searchString: topBar.searchString
2021-08-29 20:24:44 +03:00
}
Loader {
2023-06-19 02:38:40 +03:00
source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? "voip/VideoCall.qml" : ""
2023-06-02 02:45:24 +03:00
onLoaded: TimelineManager.setVideoCallItem()
2020-10-27 20:14:06 +03:00
}
2020-10-08 22:11:21 +03:00
}
TypingIndicator {
id: typingIndicator
2021-01-12 01:51:39 +03:00
2023-06-02 02:45:24 +03:00
}
2020-12-17 19:25:32 +03:00
}
}
CallInviteBar {
id: callInviteBar
2020-10-08 22:11:21 +03:00
Layout.fillWidth: true
z: 3
}
ActiveCallBar {
Layout.fillWidth: true
z: 3
}
Rectangle {
Layout.fillWidth: true
2021-05-15 00:35:34 +03:00
color: Nheko.theme.separator
2023-10-26 17:43:09 +03:00
implicitHeight: 1
2023-06-02 02:45:24 +03:00
z: 3
}
UploadBox {
2022-03-21 00:49:33 +03:00
}
MessageInputWarning {
text: qsTr("You are about to notify the whole room")
2023-02-28 02:33:27 +03:00
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 : "")
2023-03-08 03:10:42 +03:00
visible: room ? room.input.containsInvalidCommand && !room.input.containsIncompleteCommand : false
}
MessageInputWarning {
2023-06-02 02:45:24 +03:00
bubbleColor: Nheko.theme.orange
2023-03-08 03:10:42 +03:00
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
}
ReplyPopup {
2021-02-03 03:30:03 +03:00
}
MessageInput {
2020-10-08 22:11:21 +03:00
}
}
2021-06-18 17:22:06 +03:00
ColumnLayout {
id: preview
2023-06-02 02:45:24 +03:00
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 : "")
2021-06-18 17:22:06 +03:00
anchors.fill: parent
anchors.margins: Nheko.paddingLarge
2023-06-02 02:45:24 +03:00
enabled: visible
2021-06-18 17:22:06 +03:00
spacing: Nheko.paddingLarge
2023-06-02 02:45:24 +03:00
visible: room != null && room.isSpace || roomPreview != null
2021-06-18 17:22:06 +03:00
Item {
Layout.fillHeight: true
}
2021-06-18 17:22:06 +03:00
Avatar {
2023-06-02 02:45:24 +03:00
Layout.alignment: Qt.AlignHCenter
displayName: parent.roomName
2023-06-02 02:45:24 +03:00
enabled: false
2023-10-26 17:43:09 +03:00
implicitHeight: 130
2023-06-02 02:45:24 +03:00
roomid: parent.roomId
url: parent.avatarUrl.replace("mxc://", "image://MxcImage/")
2023-10-26 17:43:09 +03:00
implicitWidth: 130
2021-06-18 17:22:06 +03:00
}
RowLayout {
2021-06-18 17:22:06 +03:00
Layout.alignment: Qt.AlignHCenter
2023-06-02 02:45:24 +03:00
spacing: Nheko.paddingMedium
Layout.fillWidth: true
MatrixText {
Layout.preferredWidth: implicitWidth
horizontalAlignment: TextEdit.AlignRight
font.pixelSize: 24
text: (!room && !(roomPreview?.isFetched ?? false)) ? qsTr("No preview available") : preview.roomName
}
ImageButton {
2023-06-02 02:45:24 +03:00
ToolTip.text: qsTr("Settings")
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/settings.svg"
visible: !!room
2023-06-02 02:45:24 +03:00
onClicked: TimelineManager.openRoomSettings(room.roomId)
}
2021-06-18 17:22:06 +03:00
}
RowLayout {
2021-06-18 17:22:06 +03:00
Layout.alignment: Qt.AlignHCenter
2023-06-02 02:45:24 +03:00
spacing: Nheko.paddingMedium
visible: !!room
Layout.fillWidth: true
MatrixText {
Layout.preferredWidth: implicitWidth
2022-04-20 06:43:00 +03:00
text: qsTr("%n member(s)", "", room ? room.roomMemberCount : 0)
}
ImageButton {
2022-04-24 17:37:35 +03:00
ToolTip.text: qsTr("View members of %1").arg(room ? room.roomName : "")
2023-06-02 02:45:24 +03:00
ToolTip.visible: hovered
hoverEnabled: true
image: ":/icons/icons/ui/people.svg"
onClicked: TimelineManager.openRoomMembers(room)
}
2021-06-18 17:22:06 +03:00
}
ScrollView {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
Layout.leftMargin: Nheko.paddingLarge
Layout.rightMargin: Nheko.paddingLarge
Layout.maximumHeight: timelineView.height / 3
2021-06-18 17:22:06 +03:00
TextArea {
background: null
horizontalAlignment: TextEdit.AlignHCenter
2023-06-02 02:45:24 +03:00
readOnly: true
selectByMouse: true
text: (room || (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.")
2023-06-02 02:45:24 +03:00
textFormat: TextEdit.RichText
wrapMode: TextEdit.WordWrap
2021-06-18 17:22:06 +03:00
onLinkActivated: Nheko.openLink(link)
2023-06-19 02:38:40 +03:00
NhekoCursorShape {
2021-06-18 17:22:06 +03:00
anchors.fill: parent
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
}
FlatButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("join the conversation")
2023-06-02 02:45:24 +03:00
visible: roomPreview && !roomPreview.isInvite
onClicked: Rooms.joinPreview(roomPreview.roomid)
}
FlatButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("accept invite")
2023-06-02 02:45:24 +03:00
visible: roomPreview && roomPreview.isInvite
onClicked: Rooms.acceptInvite(roomPreview.roomid)
}
FlatButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("decline invite")
2023-06-02 02:45:24 +03:00
visible: roomPreview && roomPreview.isInvite
onClicked: Rooms.declineInvite(roomPreview.roomid)
}
FlatButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("decline invite and ignore user")
visible: roomPreview && roomPreview.isInvite
onClicked: {
var inviter = TimelineManager.getGlobalUserProfile(roomPreview.inviterUserId)
inviter.ignored = true
}
}
2023-03-02 18:30:59 +03:00
FlatButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("leave")
2023-06-02 02:45:24 +03:00
visible: !!room
2023-03-02 18:30:59 +03:00
onClicked: TimelineManager.openLeaveRoomDialog(room.roomId)
}
RowLayout {
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
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
2023-10-26 17:43:09 +03:00
implicitHeight: 48
roomid: preview.roomId
url: (roomPreview?.inviterAvatarUrl ?? "").replace("mxc://", "image://MxcImage/")
userid: roomPreview?.inviterUserId ?? ""
2023-10-26 17:43:09 +03:00
implicitWidth: 48
onClicked: TimelineManager.openGlobalUserProfile(roomPreview.inviterUserId)
}
}
ScrollView {
id: reasonField
2023-06-02 02:45:24 +03:00
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
2023-06-02 02:45:24 +03:00
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
2023-06-02 02:45:24 +03:00
onClicked: {
reasonField.showReason = !reasonField.showReason;
}
}
Item {
Layout.preferredHeight: Math.ceil(fontMetrics.lineSpacing * 2)
2023-06-02 02:45:24 +03:00
visible: room != null
}
2021-06-18 17:22:06 +03:00
Item {
Layout.fillHeight: true
}
}
ImageButton {
id: backToRoomsButton
2023-06-02 02:45:24 +03:00
ToolTip.text: qsTr("Back to room list")
ToolTip.visible: hovered
anchors.left: parent.left
anchors.margins: Nheko.paddingMedium
2023-06-02 02:45:24 +03:00
anchors.top: parent.top
enabled: visible
2023-06-02 02:45:24 +03:00
height: Nheko.avatarSize
2021-11-14 04:23:10 +03:00
image: ":/icons/icons/ui/angle-arrow-left.svg"
2023-06-02 02:45:24 +03:00
visible: (room == null || room.isSpace) && showBackButton
width: Nheko.avatarSize
onClicked: Rooms.resetCurrentRoom()
}
2023-03-24 16:36:42 +03:00
TimelineEffects {
id: timelineEffects
anchors.fill: parent
2023-06-19 22:24:31 +03:00
shouldEffectsRun: timelineView.shouldEffectsRun
}
NhekoDropArea {
anchors.fill: parent
2021-07-17 23:56:56 +03:00
roomid: room ? room.roomId : ""
}
Timer {
id: effectsTimer
2023-06-02 02:45:24 +03:00
2023-03-24 16:36:42 +03:00
interval: timelineEffects.maxLifespan
repeat: false
running: false
onTriggered: {
timelineEffects.removeParticles()
shouldEffectsRun = false
}
2023-06-02 02:45:24 +03:00
}
Connections {
2023-06-02 02:45:24 +03:00
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, {
2023-06-02 02:45:24 +03:00
"readReceipts": rr,
"room": room
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
}
2023-06-02 02:45:24 +03:00
function onRainfall() {
if (!Settings.fancyEffects)
return;
shouldEffectsRun = true;
timelineEffects.pulseRainfall();
room.markSpecialEffectsDone();
}
function onRainfallDone() {
if (!Settings.fancyEffects)
return;
effectsTimer.restart();
}
2021-07-31 05:13:58 +03:00
function onShowRawMessageDialog(rawMessage) {
2023-06-19 02:54:32 +03:00
var component = Qt.createComponent("qrc:/resources/qml/dialogs/RawMessageDialog.qml");
if (component.status == Component.Ready) {
var dialog = component.createObject(timelineRoot, {
2023-06-02 02:45:24 +03:00
"rawMessage": rawMessage
});
dialog.show();
timelineRoot.destroyOnClose(dialog);
} else {
console.error("Failed to create component: " + component.errorString());
}
2021-07-31 05:13:58 +03:00
}
target: room
}
2019-08-30 20:29:25 +03:00
}