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 "./delegates"
import "./device-verification"
import "./emoji"
2021-03-12 07:09:57 +03:00
import "./ui"
2021-07-10 18:21:15 +03:00
import "./voip"
2021-03-15 00:22:52 +03:00
import Qt . labs . platform 1.1 as Platform
2022-04-23 02:43:25 +03:00
import QtQuick 2.15
2021-06-13 02:48:11 +03:00
import QtQuick . Controls 2.5
2021-01-12 17:03:39 +03:00
import QtQuick . Layouts 1.3
2022-12-10 18:17:15 +03:00
import QtQuick . Particles 2.15
2021-08-04 03:27:50 +03:00
import QtQuick . Window 2.13
2019-11-30 03:43:39 +03:00
import im . nheko 1.0
2020-05-14 03:19:15 +03:00
import im . nheko . EmojiModel 1.0
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
2021-05-28 23:14:59 +03:00
property var room: null
2021-07-09 10:36:33 +03:00
property var roomPreview: null
2021-06-08 23:18:51 +03:00
property bool showBackButton: false
2023-01-14 04:23:07 +03:00
property bool shouldEffectsRun: false
2023-02-14 04:44:42 +03:00
required property PrivacyScreen privacyScreen
2022-03-27 22:23:40 +03:00
clip: true
2021-05-28 23:14:59 +03:00
2022-12-10 18:17:15 +03:00
onRoomChanged: if ( room != null ) room . triggerSpecialEffects ( )
2022-12-14 21:04:08 +03:00
// focus message input on key press, but not on Ctrl-C and such.
Keys.onPressed: if ( event . text && ! topBar . searchHasFocus ) TimelineManager . focusMessageInput ( ) ;
2022-12-13 20:09:34 +03:00
2022-04-03 03:28:44 +03:00
Shortcut {
sequence: StandardKey . Close
onActivated: Rooms . resetCurrentRoom ( )
}
2021-05-14 16:23:32 +03:00
Label {
2021-11-04 00:35:54 +03:00
visible: ! room && ! TimelineManager . isInitialSync && ( ! roomPreview || ! roomPreview . roomid )
2021-05-14 16:23:32 +03:00
anchors.centerIn: parent
text: qsTr ( "No room open" )
font.pointSize: 24
color: Nheko . colors . text
2021-04-11 17:31:49 +03:00
}
2021-03-12 07:09:57 +03:00
Spinner {
2021-03-15 02:22:33 +03:00
visible: TimelineManager . isInitialSync
2021-05-14 16:23:32 +03:00
anchors.centerIn: parent
2021-06-10 02:01:49 +03:00
foreground: Nheko . colors . mid
2021-05-14 16:23:32 +03:00
running: TimelineManager . isInitialSync
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
2021-05-14 16:23:32 +03:00
z: 3
2022-04-20 06:42:47 +03:00
opacity: hh . hovered ? 0.3 : 1
Behavior on opacity {
NumberAnimation { duration: 100 ; }
}
HoverHandler {
id: hh
}
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
2021-06-18 17:22:06 +03:00
visible: room != null && ! room . isSpace
enabled: visible
2020-10-08 22:11:21 +03:00
anchors.fill: parent
2021-05-14 16:23:32 +03:00
spacing: 0
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
height: 1
2020-10-08 22:11:21 +03:00
z: 3
2021-05-15 00:35:34 +03:00
color: Nheko . theme . separator
2020-10-08 22:11:21 +03:00
}
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.fillWidth: true
Layout.fillHeight: true
color: Nheko . colors . 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
}
2020-11-15 06:52:49 +03:00
2021-08-29 20:24:44 +03:00
MessageView {
implicitHeight: msgView . height - typingIndicator . height
2022-10-06 22:59:59 +03:00
searchString: topBar . searchString
2021-08-29 20:24:44 +03:00
Layout.fillWidth: true
}
2021-08-11 05:16:27 +03:00
2021-05-14 16:23:32 +03:00
Loader {
source: CallManager . isOnCall && CallManager . callType != CallType . VOICE ? "voip/VideoCall.qml" : ""
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
2020-12-17 19:25:32 +03:00
}
2021-05-14 16:23:32 +03:00
}
2020-10-08 22:11:21 +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
}
2020-10-26 15:50:44 +03:00
2021-05-14 16:23:32 +03:00
ActiveCallBar {
Layout.fillWidth: true
z: 3
}
Rectangle {
Layout.fillWidth: true
z: 3
height: 1
2021-05-15 00:35:34 +03:00
color: Nheko . theme . separator
2021-05-14 16:23:32 +03:00
}
2021-02-03 05:12:08 +03:00
2022-03-21 07:05:29 +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 {
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
bubbleColor: Nheko . theme . orange
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
}
2020-10-26 15:50:44 +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-01-26 07:46:55 +03:00
2021-06-18 17:22:06 +03:00
ColumnLayout {
2021-07-09 10:36:33 +03:00
id: preview
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 : "" )
property string avatarUrl: room ? room.roomAvatarUrl : ( roomPreview ? roomPreview.roomAvatarUrl : "" )
2022-12-27 03:40:03 +03:00
property string reason: roomPreview ? roomPreview.reason : ""
2021-07-09 10:36:33 +03:00
visible: room != null && room . isSpace || roomPreview != null
2021-06-18 17:22:06 +03:00
enabled: visible
anchors.fill: parent
anchors.margins: Nheko . paddingLarge
spacing: Nheko . paddingLarge
2021-07-09 10:36:33 +03:00
Item {
Layout.fillHeight: true
}
2021-06-18 17:22:06 +03:00
Avatar {
2021-07-09 10:36:33 +03:00
url: parent . avatarUrl . replace ( "mxc://" , "image://MxcImage/" )
2021-09-08 03:38:39 +03:00
roomid: parent . roomId
2021-07-09 10:36:33 +03:00
displayName: parent . roomName
2021-06-18 17:22:06 +03:00
height: 130
width: 130
Layout.alignment: Qt . AlignHCenter
enabled: false
}
2021-07-17 20:31:38 +03:00
2022-04-23 02:43:25 +03:00
RowLayout {
spacing: Nheko . paddingMedium
2021-06-18 17:22:06 +03:00
Layout.alignment: Qt . AlignHCenter
2022-04-23 02:43:25 +03:00
MatrixText {
2023-03-18 14:46:44 +03:00
text: ! roomPreview . isFetched ? qsTr ( "No preview available" ) : preview . roomName
2022-04-23 02:43:25 +03:00
font.pixelSize: 24
}
ImageButton {
image: ":/icons/icons/ui/settings.svg"
visible: ! ! room
hoverEnabled: true
ToolTip.visible: hovered
ToolTip.text: qsTr ( "Settings" )
onClicked: TimelineManager . openRoomSettings ( room . roomId )
}
2021-06-18 17:22:06 +03:00
}
2022-04-23 02:43:25 +03:00
RowLayout {
2021-07-09 10:36:33 +03:00
visible: ! ! room
2022-04-23 02:43:25 +03:00
spacing: Nheko . paddingMedium
2021-06-18 17:22:06 +03:00
Layout.alignment: Qt . AlignHCenter
2022-04-23 02:43:25 +03:00
MatrixText {
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 {
image: ":/icons/icons/ui/people.svg"
hoverEnabled: true
ToolTip.visible: hovered
2022-04-24 17:37:35 +03:00
ToolTip.text: qsTr ( "View members of %1" ) . arg ( room ? room.roomName : "" )
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
2021-06-18 17:22:06 +03:00
TextArea {
2023-03-18 17:06:19 +03:00
text: roomPreview . isFetched ? TimelineManager . escapeEmoji ( preview . roomTopic ) : qsTr ( "This room is possibly inaccessible. If this room is private, you should remove it from this community." )
2021-06-18 17:22:06 +03:00
wrapMode: TextEdit . WordWrap
textFormat: TextEdit . RichText
readOnly: true
background: null
selectByMouse: true
color: Nheko . colors . text
horizontalAlignment: TextEdit . AlignHCenter
onLinkActivated: Nheko . openLink ( link )
CursorShape {
anchors.fill: parent
cursorShape: parent . hoveredLink ? Qt.PointingHandCursor : Qt . ArrowCursor
}
}
}
2021-07-09 10:36:33 +03:00
FlatButton {
visible: roomPreview && ! roomPreview . isInvite
Layout.alignment: Qt . AlignHCenter
text: qsTr ( "join the conversation" )
onClicked: Rooms . joinPreview ( roomPreview . roomid )
}
FlatButton {
visible: roomPreview && roomPreview . isInvite
Layout.alignment: Qt . AlignHCenter
text: qsTr ( "accept invite" )
onClicked: Rooms . acceptInvite ( roomPreview . roomid )
}
FlatButton {
visible: roomPreview && roomPreview . isInvite
Layout.alignment: Qt . AlignHCenter
text: qsTr ( "decline invite" )
onClicked: Rooms . declineInvite ( roomPreview . roomid )
}
2023-03-02 18:30:59 +03:00
FlatButton {
visible: ! ! room
Layout.alignment: Qt . AlignHCenter
text: qsTr ( "leave" )
onClicked: TimelineManager . openLeaveRoomDialog ( room . roomId )
}
2022-12-27 03:40:03 +03:00
ScrollView {
id: reasonField
property bool showReason: false
Layout.alignment: Qt . AlignHCenter
Layout.fillWidth: true
Layout.leftMargin: Nheko . paddingLarge
Layout.rightMargin: Nheko . paddingLarge
visible: preview . reason !== "" && showReason
TextArea {
text: TimelineManager . escapeEmoji ( preview . reason )
wrapMode: TextEdit . WordWrap
textFormat: TextEdit . RichText
readOnly: true
background: null
selectByMouse: true
color: Nheko . colors . text
horizontalAlignment: TextEdit . AlignHCenter
}
}
Button {
id: showReasonButton
Layout.alignment: Qt . AlignHCenter
//Layout.fillWidth: true
Layout.leftMargin: Nheko . paddingLarge
Layout.rightMargin: Nheko . paddingLarge
visible: preview . reason !== ""
text: reasonField . showReason ? qsTr ( "Hide invite reason" ) : qsTr ( "Show invite reason" )
onClicked: {
reasonField . showReason = ! reasonField . showReason ;
}
}
2021-07-09 10:36:33 +03:00
Item {
visible: room != null
Layout.preferredHeight: Math . ceil ( fontMetrics . lineSpacing * 2 )
}
2021-06-18 17:22:06 +03:00
Item {
Layout.fillHeight: true
}
}
2021-06-18 17:40:40 +03:00
ImageButton {
id: backToRoomsButton
anchors.top: parent . top
anchors.left: parent . left
anchors.margins: Nheko . paddingMedium
width: Nheko . avatarSize
height: Nheko . avatarSize
2021-11-04 00:35:54 +03:00
visible: ( room == null || room . isSpace ) && showBackButton
2021-06-18 17:40:40 +03:00
enabled: visible
2021-11-14 04:23:10 +03:00
image: ":/icons/icons/ui/angle-arrow-left.svg"
2021-06-18 17:40:40 +03:00
ToolTip.visible: hovered
ToolTip.text: qsTr ( "Back to room list" )
onClicked: Rooms . resetCurrentRoom ( )
}
2023-01-14 04:23:07 +03:00
ParticleSystem { id: confettiParticleSystem
Component.onCompleted: pause ( ) ;
paused: ! shouldEffectsRun
}
2022-12-10 18:17:15 +03:00
Emitter {
id: confettiEmitter
width: parent . width * 3 / 4
enabled: false
anchors.horizontalCenter: parent . horizontalCenter
y: parent . height
emitRate: Math . min ( 400 * Math . sqrt ( parent . width * parent . height ) / 870 , 1000 )
lifeSpan: 15000
system: confettiParticleSystem
2023-01-14 04:23:07 +03:00
maximumEmitted: 500
2022-12-10 18:17:15 +03:00
velocityFromMovement: 8
size: 16
sizeVariation: 4
velocity: PointDirection {
x: 0
y: - Math . min ( 450 * parent . height / 700 , 1000 )
xVariation: Math . min ( 4 * parent . width / 7 , 450 )
yVariation: 250
}
2023-01-14 04:23:07 +03:00
}
2022-12-10 18:17:15 +03:00
2023-01-14 04:23:07 +03:00
ImageParticle {
system: confettiParticleSystem
source: "qrc:/confettiparticle.svg"
rotationVelocity: 0
rotationVelocityVariation: 360
colorVariation: 1
color: "white"
entryEffect: ImageParticle . None
xVector: PointDirection {
x: 1
y: 0
xVariation: 0.2
yVariation: 0.2
}
yVector: PointDirection {
x: 0
y: 0.5
xVariation: 0.2
yVariation: 0.2
2022-12-10 18:17:15 +03:00
}
}
Gravity {
system: confettiParticleSystem
anchors.fill: parent
magnitude: 350
angle: 90
}
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
onTriggered: shouldEffectsRun = false ;
interval: confettiEmitter . lifeSpan
repeat: false
running: false
}
2021-07-30 14:24:48 +03:00
Connections {
function onOpenReadReceiptsDialog ( rr ) {
var dialog = readReceiptsDialog . createObject ( timelineRoot , {
"readReceipts" : rr ,
"room" : room
} ) ;
dialog . show ( ) ;
2022-02-21 07:01:01 +03:00
timelineRoot . destroyOnClose ( dialog ) ;
2021-07-30 14:24:48 +03:00
}
2021-07-31 05:13:58 +03:00
function onShowRawMessageDialog ( rawMessage ) {
2023-02-21 16:32:35 +03:00
var component = Qt . createComponent ( "qrc:/qml/dialogs/RawMessageDialog.qml" )
if ( component . status == Component . Ready ) {
var dialog = component . createObject ( timelineRoot , {
"rawMessage" : rawMessage
} ) ;
dialog . show ( ) ;
timelineRoot . destroyOnClose ( dialog ) ;
} else {
console . error ( "Failed to create component: " + component . errorString ( ) ) ;
}
2021-07-31 05:13:58 +03:00
}
2022-12-10 18:17:15 +03:00
function onConfetti ( )
{
if ( ! Settings . fancyEffects )
return
2023-01-14 04:23:07 +03:00
shouldEffectsRun = true ;
2022-12-10 18:17:15 +03:00
confettiEmitter . pulse ( parent . height * 2 )
room . markSpecialEffectsDone ( )
}
2023-01-14 04:23:07 +03:00
function onConfettiDone ( )
{
if ( ! Settings . fancyEffects )
return
effectsTimer . start ( ) ;
}
2021-07-30 14:24:48 +03:00
target: room
}
2019-08-30 20:29:25 +03:00
}