401 lines
13 KiB
QML
401 lines
13 KiB
QML
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
|
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
pragma ComponentBehavior: Bound
|
|
|
|
import QtQuick
|
|
import QtQuick.Controls as QQC2
|
|
import QtQuick.Window
|
|
import QtQuick.Layouts
|
|
|
|
import org.kde.kirigami as Kirigami
|
|
|
|
import org.kde.neochat
|
|
|
|
Kirigami.Page {
|
|
id: root
|
|
|
|
/**
|
|
* @brief The NeoChatRoom the delegate is being displayed in.
|
|
*/
|
|
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
|
|
|
|
/**
|
|
* @brief The TimelineModel to use.
|
|
*
|
|
* Required so that new events can be requested when the end of the current
|
|
* local timeline is reached.
|
|
*
|
|
* @note For loading a room in a different window, override this with a new
|
|
* TimelineModel set with the room to be shown.
|
|
*
|
|
* @sa TimelineModel
|
|
*/
|
|
property TimelineModel timelineModel: RoomManager.timelineModel
|
|
|
|
/**
|
|
* @brief The MessageFilterModel to use.
|
|
*
|
|
* This model has the filtered list of events that should be shown in the timeline.
|
|
*
|
|
* @note For loading a room in a different window, override this with a new
|
|
* MessageFilterModel with the new TimelineModel as the source model.
|
|
*
|
|
* @sa TimelineModel, MessageFilterModel
|
|
*/
|
|
property MessageFilterModel messageFilterModel: RoomManager.messageFilterModel
|
|
|
|
/**
|
|
* @brief The MediaMessageFilterModel to use.
|
|
*
|
|
* This model has the filtered list of media events that should be shown in
|
|
* the timeline.
|
|
*
|
|
* @note For loading a room in a different window, override this with a new
|
|
* MediaMessageFilterModel with the new MessageFilterModel as the source model.
|
|
*
|
|
* @sa TimelineModel, MessageFilterModel
|
|
*/
|
|
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
|
|
|
|
/**
|
|
* @brief The WidgetModel to use.
|
|
*
|
|
* This model has the list of widgets available in the current room.
|
|
*
|
|
* @note For loading a room in a different window, override this with a new
|
|
* WidgetModel.
|
|
*
|
|
* @sa WidgetModel
|
|
*/
|
|
property WidgetModel widgetModel: RoomManager.widgetModel
|
|
|
|
title: root.currentRoom ? root.currentRoom.displayName : ""
|
|
focus: true
|
|
padding: 0
|
|
|
|
actions: [
|
|
Kirigami.Action {
|
|
id: jitsiMeetingAction
|
|
|
|
readonly property bool hasExistingMeeting: root.widgetModel.jitsiIndex >= 0
|
|
readonly property bool canStartNewMeeting: root.currentRoom.canSendState("im.vector.modular.widgets")
|
|
|
|
tooltip: {
|
|
if (hasExistingMeeting) {
|
|
return i18nc("@action:button", "Join Jitsi meeting…");
|
|
}
|
|
|
|
return canStartNewMeeting ? i18nc("@action:button", "Start Jitsi meeting…") : i18nc("@action:button", "You do not have permissions to start Jitsi meetings")
|
|
}
|
|
icon {
|
|
name: "camera-video-symbolic"
|
|
color: hasExistingMeeting ? Kirigami.Theme.highlightColor : "transparent"
|
|
}
|
|
enabled: hasExistingMeeting || canStartNewMeeting
|
|
visible: root.currentRoom && !root.currentRoom.isSpace
|
|
onTriggered: {
|
|
const dialog = Qt.createComponent("org.kde.neochat", "MeetingDialog").createObject(QQC2.Overlay.overlay, { hasExistingMeeting });
|
|
dialog.onAccepted.connect(doAction);
|
|
dialog.open();
|
|
}
|
|
|
|
function doAction(): void {
|
|
let url;
|
|
if (!hasExistingMeeting) {
|
|
url = root.widgetModel.addJitsiConference();
|
|
} else {
|
|
let idx = root.widgetModel.index(root.widgetModel.jitsiIndex, 0);
|
|
url = root.widgetModel.data(idx, WidgetModel.UrlRole);
|
|
}
|
|
Qt.openUrlExternally(url);
|
|
}
|
|
},
|
|
Kirigami.Action {
|
|
visible: Kirigami.Settings.isMobile || !(root.Kirigami.PageStack.pageStack as Kirigami.PageRow)?.wideMode
|
|
icon.name: "view-right-new"
|
|
onTriggered: (root.QQC2.ApplicationWindow.window as Main).openRoomDrawer()
|
|
}
|
|
]
|
|
|
|
Kirigami.Action {
|
|
enabled: root.currentRoom && !root.currentRoom.isSpace
|
|
shortcut: "Ctrl+F"
|
|
onTriggered: {
|
|
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
|
|
room: root.currentRoom
|
|
}, {
|
|
title: i18nc("@action:title", "Search")
|
|
});
|
|
}
|
|
}
|
|
|
|
KeyNavigation.left: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).get(0)
|
|
|
|
onCurrentRoomChanged: {
|
|
if (!Kirigami.Settings.isMobile && chatBarLoader.item) {
|
|
(chatBarLoader.item as ChatBar).forceActiveFocus();
|
|
}
|
|
|
|
if (root.currentRoom.tagNames.includes("m.server_notice")) {
|
|
banner.text = i18nc("@info", "This room contains official messages from your homeserver.")
|
|
banner.show("message");
|
|
} else {
|
|
banner.hideIf("message");
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.currentRoom.connection
|
|
function onIsOnlineChanged() {
|
|
if (!root.currentRoom.connection.isOnline) {
|
|
banner.text = i18nc("@info:status", "NeoChat is offline. Please check your network connection.");
|
|
banner.type = Kirigami.MessageType.Error;
|
|
banner.show("offline");
|
|
} else {
|
|
banner.hideIf("offline");
|
|
}
|
|
}
|
|
}
|
|
|
|
header: ColumnLayout {
|
|
id: headerLayout
|
|
|
|
spacing: 0
|
|
|
|
readonly property bool shouldShowPins: root.currentRoom.pinnedMessage.length > 0 && !Kirigami.Settings.isMobile
|
|
|
|
QQC2.Control {
|
|
id: pinControl
|
|
|
|
visible: headerLayout.shouldShowPins
|
|
|
|
Layout.fillWidth: true
|
|
|
|
background: Rectangle {
|
|
color: Kirigami.Theme.backgroundColor
|
|
|
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
Kirigami.Theme.inherit: false
|
|
}
|
|
|
|
contentItem: RowLayout {
|
|
spacing: Kirigami.Units.smallSpacing
|
|
|
|
Kirigami.Icon {
|
|
source: "pin-symbolic"
|
|
|
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
|
|
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
|
|
}
|
|
|
|
QQC2.Label {
|
|
text: root.currentRoom.pinnedMessage
|
|
maximumLineCount: 1
|
|
elide: Text.ElideRight
|
|
|
|
onLinkActivated: link => UrlHelper.openUrl(link)
|
|
onHoveredLinkChanged: if (hoveredLink.length > 0 && hoveredLink !== "1") {
|
|
(QQC2.ApplicationWindow.window as Main).hoverLinkIndicator.text = hoveredLink;
|
|
} else {
|
|
(QQC2.ApplicationWindow.window as Main).hoverLinkIndicator.text = "";
|
|
}
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
}
|
|
|
|
TapHandler {
|
|
onTapped: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomPinnedMessagesPage'), {
|
|
room: root.currentRoom
|
|
}, {
|
|
title: i18nc("@title", "Pinned Messages")
|
|
});
|
|
}
|
|
}
|
|
|
|
Kirigami.Separator {
|
|
visible: headerLayout.shouldShowPins
|
|
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
Kirigami.InlineMessage {
|
|
id: banner
|
|
|
|
// Used to keep track of messages so we can hide the right one at the right time
|
|
property string messageId
|
|
|
|
Layout.fillWidth: true
|
|
|
|
showCloseButton: true
|
|
visible: false
|
|
position: Kirigami.InlineMessage.Position.Header
|
|
|
|
function show(msgid: string): void {
|
|
messageId = msgid;
|
|
visible = true;
|
|
}
|
|
|
|
function hideIf(msgid: string): void {
|
|
if (messageId == msgid) {
|
|
visible = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: timelineViewLoader
|
|
anchors.fill: parent
|
|
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
|
|
active: root.currentRoom && !root.currentRoom.isInvite && !root.currentRoom.isSpace
|
|
sourceComponent: TimelineView {
|
|
id: timelineView
|
|
messageFilterModel: root.messageFilterModel
|
|
compactLayout: NeoChatConfig.compactLayout
|
|
markReadCondition: NeoChatConfig.markReadCondition
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: invitationLoader
|
|
active: root.currentRoom && root.currentRoom.isInvite
|
|
anchors.fill: parent
|
|
sourceComponent: InvitationView {
|
|
currentRoom: root.currentRoom
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: spaceLoader
|
|
active: root.currentRoom && root.currentRoom.isSpace
|
|
anchors.fill: parent
|
|
sourceComponent: SpaceHomePage {
|
|
room: root.currentRoom
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
active: !RoomManager.currentRoom
|
|
anchors.centerIn: parent
|
|
sourceComponent: Kirigami.PlaceholderMessage {
|
|
icon.name: "org.kde.neochat"
|
|
text: i18nc("@title", "Welcome to NeoChat")
|
|
explanation: i18nc("@info:usagetip", "Select or join a room to get started")
|
|
}
|
|
}
|
|
|
|
footer: Loader {
|
|
id: chatBarLoader
|
|
height: active ? (item as ChatBar)?.implicitHeight : 0
|
|
active: timelineViewLoader.active && !root.currentRoom.readOnly
|
|
sourceComponent: ChatBar {
|
|
id: chatBar
|
|
width: parent.width
|
|
currentRoom: root.currentRoom
|
|
connection: root.currentRoom.connection as NeoChatConnection
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: RoomManager
|
|
function onCurrentRoomChanged() {
|
|
if (root.currentRoom && root.currentRoom.isInvite) {
|
|
Controller.clearInvitationNotification(root.currentRoom.id);
|
|
}
|
|
}
|
|
|
|
function onGoToEvent(eventId) {
|
|
(timelineViewLoader.item as TimelineView).goToEvent(eventId);
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.currentRoom.connection
|
|
function onJoinedRoom(room, invited) {
|
|
if (root.currentRoom.id === invited.id) {
|
|
RoomManager.resolveResource(room.id);
|
|
}
|
|
}
|
|
}
|
|
|
|
Keys.onPressed: event => {
|
|
if (event.key === Qt.Key_PageUp) {
|
|
event.accepted = true;
|
|
(timelineViewLoader.item as TimelineView).pageUp();
|
|
} else if (event.key === Qt.Key_PageDown) {
|
|
event.accepted = true;
|
|
(timelineViewLoader.item as TimelineView).pageDown();
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: RoomManager
|
|
|
|
function onShowMessage(messageType, message) {
|
|
banner.text = message;
|
|
banner.type = messageType;
|
|
banner.show("generic");
|
|
}
|
|
|
|
function onShowEventSource(eventId) {
|
|
(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
|
|
sourceText: root.currentRoom.getEventJsonSource(eventId)
|
|
}, {
|
|
title: i18nc("@title:dialog", "Message Source"),
|
|
width: Kirigami.Units.gridUnit * 25
|
|
});
|
|
}
|
|
|
|
function onShowDelegateMenu(parent: QtObject, eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, isThread: bool, selectedText: string, hoveredLink: string) {
|
|
(delegateContextMenu.createObject(parent, {
|
|
author: author,
|
|
eventId: eventId,
|
|
plainText: plainText,
|
|
mimeType: mimeType,
|
|
progressInfo: progressInfo,
|
|
messageComponentType: messageComponentType,
|
|
}) as DelegateContextMenu).popup();
|
|
}
|
|
|
|
function onShowMaximizedMedia(index) {
|
|
var popup = maximizeComponent.createObject(QQC2.Overlay.overlay, {
|
|
initialIndex: index
|
|
});
|
|
popup.closed.connect(() => {
|
|
timelineViewLoader.item.interactive = true;
|
|
popup.destroy();
|
|
});
|
|
popup.open();
|
|
}
|
|
|
|
function onShowMaximizedCode(author, time, codeText, language) {
|
|
(Qt.createComponent('org.kde.neochat', 'CodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
|
author: author,
|
|
time: time,
|
|
codeText: codeText,
|
|
language: language
|
|
}) as CodeMaximizeComponent).open();
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: delegateContextMenu
|
|
DelegateContextMenu {
|
|
room: root.currentRoom
|
|
connection: root.currentRoom.connection as NeoChatConnection
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: maximizeComponent
|
|
NeochatMaximizeComponent {
|
|
currentRoom: root.currentRoom
|
|
model: root.mediaMessageFilterModel
|
|
parent: root.QQC2.Overlay.overlay
|
|
}
|
|
}
|
|
}
|