2023-02-22 01:48:49 +03:00
// 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
2021-07-09 10:36:33 +03:00
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
2019-09-03 00:28:05 +03:00
2021-05-14 16:23:32 +03:00
Item {
id: timelineView
2021-04-27 12:08:21 +03:00
2023-06-02 02:45:24 +03:00
required property PrivacyScreen privacyScreen
2021-05-28 23:14:59 +03:00
property var room: null
2021-07-09 10:36:33 +03:00
property var roomPreview: null
2023-01-14 04:23:07 +03:00
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 = > {
2023-05-23 23:56:48 +03:00
if ( event . text && event . key !== Qt . Key_Enter && event . key !== Qt . Key_Return && ! topBar . searchHasFocus ) {
2023-05-13 03:04:35 +03:00
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
2022-12-13 20:09:34 +03:00
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 ( )
}
2021-05-14 16:23:32 +03:00
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 {
2021-05-14 16:23:32 +03:00
anchors.centerIn: parent
2023-06-02 02:29:05 +03:00
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
}
2021-02-21 20:40:21 +03:00
}
2021-05-14 16:23:32 +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
2021-05-14 16:23:32 +03:00
spacing: 0
2023-06-02 02:45:24 +03:00
visible: room != null && ! room . isSpace
2021-01-12 22:22:52 +03:00
2021-05-14 16:23:32 +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
}
2021-05-14 16:23:32 +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
}
2021-05-14 16:23:32 +03:00
Rectangle {
id: msgView
2021-01-26 08:03:09 +03:00
2021-05-14 16:23:32 +03:00
Layout.fillHeight: true
2023-06-02 02:45:24 +03:00
Layout.fillWidth: true
2023-06-02 02:29:05 +03:00
color: palette . base
2020-10-08 22:11:21 +03:00
2021-05-14 16:23:32 +03:00
ColumnLayout {
anchors.fill: parent
spacing: 0
2020-11-15 06:52:49 +03:00
2021-05-14 16:23:32 +03:00
StackLayout {
id: stackLayout
2020-10-08 22:11:21 +03:00
2021-05-14 16:23:32 +03:00
currentIndex: 0
2020-11-15 06:52:49 +03:00
2021-05-14 16:23:32 +03:00
Connections {
2021-05-28 23:14:59 +03:00
function onRoomChanged ( ) {
2021-05-14 16:23:32 +03:00
stackLayout . currentIndex = 0 ;
2020-10-08 22:11:21 +03:00
}
2021-05-28 23:14:59 +03:00
target: timelineView
2021-05-14 16:23:32 +03:00
}
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
}
2021-05-14 16:23:32 +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
2021-05-14 16:23:32 +03:00
onLoaded: TimelineManager . setVideoCallItem ( )
2020-10-27 20:14:06 +03:00
}
2020-10-08 22:11:21 +03:00
}
2021-05-14 16:23:32 +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
}
2021-05-14 16:23:32 +03:00
}
CallInviteBar {
id: callInviteBar
2020-10-08 22:11:21 +03:00
2021-05-14 16:23:32 +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
2021-05-14 16:23:32 +03:00
}
2022-03-21 07:49:12 +03:00
UploadBox {
2022-03-21 00:49:33 +03:00
}
2023-02-24 05:57:53 +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 )
2023-02-24 05:57:53 +03:00
}
MessageInputWarning {
2023-02-28 02:06:24 +03:00
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
2021-09-02 04:15:07 +03:00
}
2021-05-14 16:23:32 +03:00
ReplyPopup {
2021-02-03 03:30:03 +03:00
}
2021-05-14 16:23:32 +03:00
MessageInput {
2020-10-08 22:11:21 +03:00
}
2021-02-03 05:12:08 +03:00
}
2021-06-18 17:22:06 +03:00
ColumnLayout {
2021-07-09 10:36:33 +03:00
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 : ""
2021-11-04 00:35:54 +03:00
property string roomId: room ? room.roomId : ( roomPreview ? roomPreview.roomid : "" )
2021-07-09 10:36:33 +03:00
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
2021-07-09 10:36:33 +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
2021-07-09 10:36:33 +03:00
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
}
2022-04-23 02:43:25 +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
2023-12-10 04:59:05 +03:00
Layout.fillWidth: true
2022-04-23 02:43:25 +03:00
MatrixText {
2023-12-10 04:59:05 +03:00
Layout.preferredWidth: implicitWidth
horizontalAlignment: TextEdit . AlignRight
2022-04-23 02:43:25 +03:00
font.pixelSize: 24
2023-12-10 04:59:05 +03:00
text: ( ! room && ! ( roomPreview ? . isFetched ? ? false ) ) ? qsTr ( "No preview available" ) : preview . roomName
2022-04-23 02:43:25 +03:00
}
ImageButton {
2023-06-02 02:45:24 +03:00
ToolTip.text: qsTr ( "Settings" )
ToolTip.visible: hovered
hoverEnabled: true
2022-04-23 02:43:25 +03:00
image: ":/icons/icons/ui/settings.svg"
visible: ! ! room
2023-06-02 02:45:24 +03:00
2022-04-23 02:43:25 +03:00
onClicked: TimelineManager . openRoomSettings ( room . roomId )
}
2021-06-18 17:22:06 +03:00
}
2022-04-23 02:43:25 +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
2023-12-10 04:59:05 +03:00
Layout.fillWidth: true
2022-04-23 02:43:25 +03:00
MatrixText {
2023-12-10 04:59:05 +03:00
Layout.preferredWidth: implicitWidth
2022-04-20 06:43:00 +03:00
text: qsTr ( "%n member(s)" , "" , room ? room.roomMemberCount : 0 )
2022-04-23 02:43:25 +03:00
}
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"
2022-04-23 02:43:25 +03:00
onClicked: TimelineManager . openRoomMembers ( room )
}
2021-06-18 17:22:06 +03:00
}
ScrollView {
Layout.alignment: Qt . AlignHCenter
2021-07-09 10:36:33 +03:00
Layout.fillWidth: true
Layout.leftMargin: Nheko . paddingLarge
Layout.rightMargin: Nheko . paddingLarge
2023-12-10 04:59:05 +03:00
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
2023-12-10 04:59:05 +03:00
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
}
}
}
2021-07-09 10:36:33 +03:00
FlatButton {
Layout.alignment: Qt . AlignHCenter
text: qsTr ( "join the conversation" )
2023-06-02 02:45:24 +03:00
visible: roomPreview && ! roomPreview . isInvite
2021-07-09 10:36:33 +03:00
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
2021-07-09 10:36:33 +03:00
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
2021-07-09 10:36:33 +03:00
onClicked: Rooms . declineInvite ( roomPreview . roomid )
}
2023-12-18 02:17:43 +03:00
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 )
}
2023-06-10 01:49:49 +03:00
RowLayout {
Layout.alignment: Qt . AlignHCenter
2023-12-10 04:59:05 +03:00
Layout.fillWidth: true
2023-06-10 01:49:49 +03:00
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
2023-06-10 01:49:49 +03:00
roomid: preview . roomId
url: ( roomPreview ? . inviterAvatarUrl ? ? "" ) . replace ( "mxc://" , "image://MxcImage/" )
userid: roomPreview ? . inviterUserId ? ? ""
2023-10-26 17:43:09 +03:00
implicitWidth: 48
2023-06-10 01:49:49 +03:00
onClicked: TimelineManager . openGlobalUserProfile ( roomPreview . inviterUserId )
}
}
2022-12-27 03:40:03 +03:00
ScrollView {
id: reasonField
2023-06-02 02:45:24 +03:00
2022-12-27 03:40:03 +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
2022-12-27 03:40:03 +03:00
}
}
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" )
2023-06-10 01:49:49 +03:00
visible: roomPreview && roomPreview . isInvite
2023-06-02 02:45:24 +03:00
2022-12-27 03:40:03 +03:00
onClicked: {
reasonField . showReason = ! reasonField . showReason ;
}
}
2021-07-09 10:36:33 +03:00
Item {
Layout.preferredHeight: Math . ceil ( fontMetrics . lineSpacing * 2 )
2023-06-02 02:45:24 +03:00
visible: room != null
2021-07-09 10:36:33 +03:00
}
2021-06-18 17:22:06 +03:00
Item {
Layout.fillHeight: true
}
}
2021-06-18 17:40:40 +03:00
ImageButton {
id: backToRoomsButton
2023-06-02 02:45:24 +03:00
ToolTip.text: qsTr ( "Back to room list" )
ToolTip.visible: hovered
2021-06-18 17:40:40 +03:00
anchors.left: parent . left
anchors.margins: Nheko . paddingMedium
2023-06-02 02:45:24 +03:00
anchors.top: parent . top
2021-06-18 17:40:40 +03:00
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
2021-06-18 17:40:40 +03:00
onClicked: Rooms . resetCurrentRoom ( )
}
2023-03-24 16:36:42 +03:00
TimelineEffects {
id: timelineEffects
2023-03-07 21:11:00 +03:00
2022-12-10 18:17:15 +03:00
anchors.fill: parent
2023-06-19 22:24:31 +03:00
shouldEffectsRun: timelineView . shouldEffectsRun
2023-03-07 21:11:00 +03:00
}
2021-05-14 16:23:32 +03:00
NhekoDropArea {
2021-02-03 05:12:08 +03:00
anchors.fill: parent
2021-07-17 23:56:56 +03:00
roomid: room ? room.roomId : ""
2021-05-28 23:14:59 +03:00
}
2023-01-14 04:23:07 +03:00
Timer {
id: effectsTimer
2023-06-02 02:45:24 +03:00
2023-03-24 16:36:42 +03:00
interval: timelineEffects . maxLifespan
2023-01-14 04:23:07 +03:00
repeat: false
running: false
2023-10-10 04:19:43 +03:00
onTriggered: {
timelineEffects . removeParticles ( )
shouldEffectsRun = false
}
2023-06-02 02:45:24 +03:00
}
2021-07-30 14:24:48 +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 ( ) ;
}
2021-07-30 14:24:48 +03:00
function onOpenReadReceiptsDialog ( rr ) {
var dialog = readReceiptsDialog . createObject ( timelineRoot , {
2023-06-02 02:45:24 +03:00
"readReceipts" : rr ,
"room" : room
} ) ;
2021-07-30 14:24:48 +03:00
dialog . show ( ) ;
2022-02-21 07:01:01 +03:00
timelineRoot . destroyOnClose ( dialog ) ;
2021-07-30 14:24:48 +03:00
}
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" ) ;
2023-02-21 16:32:35 +03:00
if ( component . status == Component . Ready ) {
var dialog = component . createObject ( timelineRoot , {
2023-06-02 02:45:24 +03:00
"rawMessage" : rawMessage
} ) ;
2023-02-21 16:32:35 +03:00
dialog . show ( ) ;
timelineRoot . destroyOnClose ( dialog ) ;
} else {
console . error ( "Failed to create component: " + component . errorString ( ) ) ;
}
2021-07-31 05:13:58 +03:00
}
2021-07-30 14:24:48 +03:00
target: room
}
2019-08-30 20:29:25 +03:00
}