Files
neochat/src/qml/RoomPage.qml
James Graham b598584aea Message Content Rework
For now everything should look identical. However this moves to using a model for the content of the message and is intended to lay the foundation for improved message content representation, e.g. splitting up a text message in multiple sections and using different delegates for things like code and quotes.
2024-02-18 09:53:08 +00:00

329 lines
10 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
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kirigami as Kirigami
import org.kde.kitemmodels
import org.kde.neochat
import org.kde.neochat.config
Kirigami.Page {
id: root
/// Not readonly because of the separate window view.
property NeoChatRoom currentRoom: RoomManager.currentRoom
required property NeoChatConnection connection
/**
* @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 ActionsHandler object to use.
*/
property ActionsHandler actionsHandler: ActionsHandler {
room: root.currentRoom
}
property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded)
/// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation.
property bool disableCancelShortcut: false
title: root.currentRoom.displayName
focus: true
padding: 0
actions: [
Kirigami.Action {
visible: Kirigami.Settings.isMobile || !applicationWindow().pageStack.wideMode
icon.name: "view-right-new"
onTriggered: applicationWindow().openRoomDrawer()
}
]
KeyNavigation.left: pageStack.get(0)
onCurrentRoomChanged: {
banner.visible = false;
if (!Kirigami.Settings.isMobile && chatBarLoader.item) {
chatBarLoader.item.forceActiveFocus();
}
}
Connections {
target: root.connection
function onIsOnlineChanged() {
if (!root.connection.isOnline) {
banner.text = i18n("NeoChat is offline. Please check your network connection.");
banner.visible = true;
banner.type = Kirigami.MessageType.Error;
} else {
banner.visible = false;
}
}
}
header: KirigamiComponents.Banner {
id: banner
showCloseButton: true
visible: false
}
Loader {
id: timelineViewLoader
anchors.fill: parent
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading
sourceComponent: TimelineView {
id: timelineView
currentRoom: root.currentRoom
timelineModel: root.timelineModel
messageFilterModel: root.messageFilterModel
actionsHandler: root.actionsHandler
onFocusChatBar: {
if (chatBarLoader.item) {
chatBarLoader.item.forceActiveFocus();
}
}
}
}
Loader {
id: invitationLoader
active: root.currentRoom && root.currentRoom.isInvite
anchors.centerIn: parent
sourceComponent: InvitationView {
currentRoom: root.currentRoom
anchors.centerIn: parent
}
}
Loader {
active: root.loading && !invitationLoader.active
anchors.centerIn: parent
sourceComponent: Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
}
}
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: Config.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
}
footer: Loader {
id: chatBarLoader
active: timelineViewLoader.active && !root.currentRoom.readOnly
sourceComponent: ChatBar {
id: chatBar
width: parent.width
currentRoom: root.currentRoom
connection: root.connection
actionsHandler: root.actionsHandler
onMessageSent: {
if (!timelineViewLoader.item.atYEnd) {
timelineViewLoader.item.goToLastMessage();
}
}
}
}
Connections {
target: RoomManager
function onCurrentRoomChanged() {
if (!RoomManager.currentRoom) {
if (pageStack.lastItem === root) {
pageStack.pop();
}
} else if (root.currentRoom.isInvite) {
root.currentRoom.clearInvitationNotification();
}
}
function onWarning(title, message) {
root.warning(title, message);
}
}
Shortcut {
sequence: StandardKey.Cancel
onActivated: {
if (!timelineViewLoader.item.atYEnd || root.currentRoom.hasUnreadMessages) {
timelineViewLoader.item.goToLastMessage();
root.currentRoom.markAllMessagesAsRead();
} else {
applicationWindow().pageStack.get(0).forceActiveFocus();
}
}
enabled: !root.disableCancelShortcut
}
Connections {
target: root.connection
function onJoinedRoom(room, invited) {
if (root.currentRoom.id === invited.id) {
RoomManager.resolveResource(room.id);
}
}
}
Keys.onPressed: event => {
if (!(event.modifiers & Qt.ControlModifier) && event.key < Qt.Key_Escape) {
event.accepted = true;
chatBarLoader.item.insertText(event.text);
chatBarLoader.item.forceActiveFocus();
return;
} else if (event.key === Qt.Key_PageUp) {
event.accepted = true;
timelineViewLoader.item.pageUp();
} else if (event.key === Qt.Key_PageDown) {
event.accepted = true;
timelineViewLoader.item.pageDown();
}
}
Connections {
target: root.currentRoom
function onShowMessage(messageType, message) {
banner.text = message;
banner.type = messageType === ActionsHandler.Error ? Kirigami.MessageType.Error : messageType === ActionsHandler.Positive ? Kirigami.MessageType.Positive : Kirigami.MessageType.Information;
banner.visible = true;
}
}
function warning(title, message) {
banner.text = `${title}<br />${message}`;
banner.type = Kirigami.MessageType.Warning;
banner.visible = true;
}
Connections {
target: RoomManager
function onShowUserDetail(user) {
root.showUserDetail(user);
}
function onShowEventSource(eventId) {
applicationWindow().pageStack.pushDialogLayer('qrc:/org/kde/neochat/qml/MessageSourceSheet.qml', {
sourceText: root.currentRoom.getEventJsonSource(eventId)
}, {
title: i18n("Message Source"),
width: Kirigami.Units.gridUnit * 25
});
}
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText) {
const contextMenu = messageDelegateContextMenu.createObject(root, {
selectedText: selectedText,
author: author,
eventId: eventId,
messageComponentType: messageComponentType,
plainText: plainText,
htmlText: htmlText
});
contextMenu.open();
}
function onShowFileMenu(eventId, author, messageComponentType, plainText, mimeType, progressInfo) {
const contextMenu = fileDelegateContextMenu.createObject(root, {
author: author,
eventId: eventId,
messageComponentType: messageComponentType,
plainText: plainText,
mimeType: mimeType,
progressInfo: progressInfo
});
contextMenu.open();
}
function onShowMaximizedMedia(index) {
var popup = maximizeComponent.createObject(QQC2.Overlay.overlay, {
initialIndex: index
});
popup.closed.connect(() => {
timelineViewLoader.item.interactive = true;
popup.destroy();
});
popup.open();
}
}
function showUserDetail(user) {
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
room: root.currentRoom,
user: root.currentRoom.getUser(user.id)
}).open();
}
Component {
id: userDetailDialog
UserDetailDialog {}
}
Component {
id: messageDelegateContextMenu
MessageDelegateContextMenu {
connection: root.connection
}
}
Component {
id: fileDelegateContextMenu
FileDelegateContextMenu {
connection: root.connection
}
}
Component {
id: maximizeComponent
NeochatMaximizeComponent {
currentRoom: root.currentRoom
model: root.mediaMessageFilterModel
}
}
}