Commit b7d98fc6 authored by Carl Schwan's avatar Carl Schwan 🚴
Browse files

Port RoomManager to C++

This also makes it possible to handle the Matrix URI
parent a2a69831
......@@ -5,6 +5,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.neochat 1.0
import org.kde.kirigami 2.15 as Kirigami
TextEdit {
......@@ -47,7 +48,7 @@ a{
wrapMode: Text.WordWrap
textFormat: Text.RichText
onLinkActivated: applicationWindow().handleLink(link, currentRoom)
onLinkActivated: RoomManager.openResource(link)
MouseArea {
anchors.fill: parent
......
......@@ -93,9 +93,7 @@ Loader {
Layout.maximumWidth: Kirigami.Units.gridUnit * 24
wrapMode: Text.WordWrap
onLinkActivated: {
applicationWindow().handleLink(link, currentRoom)
}
onLinkActivated: RoomManager.openResource(link);
}
}
}
......@@ -189,9 +187,7 @@ Loader {
Layout.fillWidth: true
wrapMode: Text.WordWrap
onLinkActivated: {
applicationWindow().handleLink(link, currentRoom)
}
onLinkActivated: RoomManager.openResource(link);
}
}
}
......
......@@ -116,16 +116,15 @@ Kirigami.ScrollablePage {
action: Kirigami.Action {
id: enterRoomAction
onTriggered: {
var roomItem = roomManager.enterRoom(currentRoom)
roomListItem.KeyNavigation.right = roomItem
roomItem.focus = true;
itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource(sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent)
RoomManager.enterRoom(currentRoom);
itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource(
sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent)
}
}
bold: unreadCount > 0
label: name ?? ""
subtitle: {
let txt = (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm," ")
let txt = (lastEvent.length === 0 ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm, " ")
if (txt.length) {
return txt
}
......
......@@ -21,7 +21,8 @@ import NeoChat.Menu.Timeline 1.0
Kirigami.ScrollablePage {
id: page
required property var currentRoom
/// It's not readonly because of the seperate window view.
property var currentRoom: RoomManager.currentRoom
title: currentRoom.displayName
......@@ -30,19 +31,17 @@ Kirigami.ScrollablePage {
onCurrentRoomChanged: ChatBoxHelper.clearEditReply()
ActionsHandler {
id: actionsHandler
room: page.currentRoom
connection: Controller.activeConnection
}
Connections {
target: Controller.activeConnection
function onJoinedRoom(room) {
if(room.id === invitation.id) {
roomManager.enterRoom(room);
RoomManager.enterRoom(room);
}
}
}
......@@ -81,7 +80,7 @@ Kirigami.ScrollablePage {
onClicked: {
page.currentRoom.forget()
roomManager.getBack();
RoomManager.getBack();
}
}
......@@ -667,6 +666,11 @@ Kirigami.ScrollablePage {
FullScreenImage {}
}
Component {
id: userDetailDialog
UserDetailDialog {}
}
header: TypingPane {
id: typingPane
......@@ -746,6 +750,20 @@ Kirigami.ScrollablePage {
}
}
function warning(title, message) {
page.header.contentItem.text = `${title}<br />${message}`;
page.header.contentItem.type = Kirigami.MessageType.Warning;
page.header.contentItem.visible = true;
}
function showUserDetail(user) {
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
room: currentRoom,
user: user,
}).open();
}
function goToLastMessage() {
currentRoom.markAllMessagesAsRead()
// scroll to the very end, i.e to messageListView.YEnd
......
......@@ -16,7 +16,7 @@ import NeoChat.Dialog 1.0
Kirigami.OverlayDrawer {
id: roomDrawer
property var room
readonly property var room: RoomManager.currentRoom
enabled: true
......
......@@ -70,7 +70,8 @@ Comment[sv]=Klient för protokollet Matrix
Comment[uk]=Клієнт протоколу Matrix
Comment[x-test]=xxClient for the Matrix protocolxx
Comment[zh_CN]=为 Matrix 协议打造的客户端
Exec=neochat
MimeType=x-scheme-handler/matrix;
Exec=neochat %u
Terminal=false
Icon=org.kde.neochat
Type=Application
......
......@@ -28,6 +28,8 @@ Kirigami.ApplicationWindow {
pageStack.initialPage: LoadingPage {}
property bool roomListLoaded: false
Connections {
target: root.quitAction
function onTriggered() {
......@@ -50,78 +52,85 @@ Kirigami.ApplicationWindow {
onXChanged: saveWindowGeometryTimer.restart()
onYChanged: saveWindowGeometryTimer.restart()
/**
* Manage opening and close rooms
* TODO this should probably be moved to C++
*/
QtObject {
id: roomManager
property var currentRoom: null
property alias pageStack: root.pageStack
property var roomList: null
property Item roomItem: null
readonly property bool hasOpenRoom: currentRoom !== null
signal leaveRoom(string room);
signal openRoom(string room);
function roomByAliasOrId(aliasOrId) {
return Controller.activeConnection.room(aliasOrId)
/// Setup keyboard navigation to the room page.
function connectRoomToSignal(item) {
if (!roomListLoaded) {
console.log("Should not happen: no room list page but room page");
}
const roomList = pageStack.get(0);
item.switchRoomUp.connect(function() {
roomList.goToNextRoom();
});
item.switchRoomDown.connect(function() {
roomList.goToPreviousRoom();
});
item.forceActiveFocus();
item.KeyNavigation.left = pageStack.get(0);
}
function openRoomAndEvent(room, event) {
enterRoom(room)
roomItem.goToEvent(event)
Connections {
target: RoomManager
function onPushRoom(room, event) {
const roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml");
connectRoomToSignal(roomItem);
if (event.length > 0) {
roomItem.goToEvent(event);
}
}
function loadInitialRoom() {
if (Config.openRoom) {
const room = Controller.activeConnection.room(Config.openRoom);
currentRoom = room;
roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': room, });
connectRoomToSignal(roomItem);
} else {
// TODO create welcome page
function onReplaceRoom(room, event) {
const roomItem = pageStack.get(pageStack.depth - 1);
pageStack.currentIndex = pageStack.depth - 1;
connectRoomToSignal(roomItem);
if (event.length > 0) {
roomItem.goToEvent(event);
}
}
function enterRoom(room) {
if (currentRoom != null) {
roomItem.currentRoom = room;
pageStack.currentIndex = pageStack.depth - 1;
} else {
roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': room, });
function goToEvent(event) {
if (event.length > 0) {
roomItem.goToEvent(event);
}
currentRoom = room;
Config.openRoom = room.id;
Config.save();
connectRoomToSignal(roomItem);
return roomItem;
roomItem.forceActiveFocus();
}
function getBack() {
pageStack.replace("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': currentRoom, });
function onPushWelcomePage() {
// TODO
}
function openWindow(room) {
function onOpenRoomInNewWindow(room) {
const secondayWindow = roomWindow.createObject(applicationWindow(), {currentRoom: room});
secondayWindow.width = root.width - roomList.width;
secondayWindow.show();
}
function connectRoomToSignal(item) {
if (!roomList) {
console.log("Should not happen: no room list page but room page");
function onShowUserDetail(user) {
const roomItem = pageStack.get(pageStack.depth - 1);
roomItem.showUserDetail(user);
}
function onAskDirectChatConfirmation(user) {
askDirectChatConfirmationComponent.createObject(QQC2.ApplicationWindow.overlay, {
user: user,
}).open();
}
function onWarning(title, message) {
if (RoomManager.currentRoom) {
const roomItem = pageStack.get(pageStack.depth - 1);
roomItem.warning(title, message);
} else {
showPassiveNotification(i18n("Warning: %1", message));
}
item.switchRoomUp.connect(function() {
roomList.goToNextRoom();
});
}
item.switchRoomDown.connect(function() {
roomList.goToPreviousRoom();
});
function onOpenLink(url) {
openLinkConfirmationComponent.createObject(QQC2.ApplicationWindow.overlay, {
url: url,
}).open();
}
}
......@@ -146,8 +155,7 @@ Kirigami.ApplicationWindow {
modal: !root.wideScreen || !enabled
onEnabledChanged: drawerOpen = enabled && !modal
onModalChanged: drawerOpen = !modal
enabled: roomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3
room: roomManager.currentRoom
enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
}
......@@ -238,7 +246,7 @@ Kirigami.ApplicationWindow {
Connections {
target: LoginHelper
function onInitialSyncFinished() {
roomManager.roomList = pageStack.replace(roomListComponent);
RoomManager.roomList = pageStack.replace(roomListComponent);
}
}
......@@ -246,32 +254,38 @@ Kirigami.ApplicationWindow {
target: Controller
function onInitiated() {
if (roomManager.hasOpenRoom) {
if (RoomManager.hasOpenRoom) {
return;
}
if (Controller.accountCount === 0) {
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
} else {
roomManager.roomList = pageStack.replace(roomListComponent, {'activeConnection': Controller.activeConnection});
roomManager.loadInitialRoom();
pageStack.replace(roomListComponent, {
activeConnection: Controller.activeConnection
});
roomListLoaded = true;
RoomManager.loadInitialRoom();
}
}
function onBusyChanged() {
if(!Controller.busy && roomManager.roomList === null) {
roomManager.roomList = pageStack.replace(roomListComponent);
if(!Controller.busy && roomListLoaded === false) {
pageStack.replace(roomListComponent);
roomListLoaded = true;
}
}
function onConnectionDropped() {
if (Controller.accountCount === 0) {
RoomManager.reset();
pageStack.clear();
roomListLoaded = false;
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml");
}
}
function onGlobalErrorOccured(error, detail) {
showPassiveNotification(error + ": " + detail)
showPassiveNotification(i18nc("%1: %2", error, detail));
}
function onShowWindow() {
......@@ -279,7 +293,7 @@ Kirigami.ApplicationWindow {
}
function onOpenRoom(room) {
roomManager.enterRoom(room)
RoomManager.enterRoom(room)
}
function onUserConsentRequired(url) {
......@@ -288,14 +302,14 @@ Kirigami.ApplicationWindow {
}
function onRoomJoined(roomName) {
roomManager.enterRoom(Controller.activeConnection.room(roomName))
RoomManager.enterRoom(Controller.activeConnection.room(roomName))
}
}
Connections {
target: Controller.activeConnection
onDirectChatAvailable: {
roomManager.enterRoom(Controller.activeConnection.room(directChat.id));
RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
}
}
......@@ -332,36 +346,68 @@ Kirigami.ApplicationWindow {
RoomWindow {}
}
function handleLink(link, currentRoom) {
if (link.startsWith("https://matrix.to/")) {
var content = link.replace("https://matrix.to/#/", "").replace(/\?.*/, "")
if(content.match("^[#!]")) {
if(content.includes("/")) {
var result = content.match("([!#].*:.*)/(\\$.*)")
if(!result) {
return
}
if(result[1] == currentRoom.id) {
roomManager.roomItem.goToEvent(result[2])
} else {
roomManager.openRoomAndEvent(roomManager.roomByAliasOrId(result[1]), result[2])
}
} else {
roomManager.enterRoom(roomManager.roomByAliasOrId(content))
Component {
id: userDialog
UserDetailDialog {}
}
Component {
id: askDirectChatConfirmationComponent
Kirigami.OverlaySheet {
id: askDirectChatConfirmation
required property var user;
parent: QQC2.ApplicationWindow.overlay
header: Kirigami.Heading {
text: i18n("Start a chat")
}
contentItem: QQC2.Label {
text: i18n("Do you want to start a chat with %1?", user.displayName)
wrapMode: Text.WordWrap
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel
onAccepted: {
user.requestDirectChat();
askDirectChatConfirmation.close();
}
} else if(content.match("^@")) {
let dialog = userDialog.createObject(root.overlay, {room: currentRoom, user: currentRoom.user(content)})
dialog.open()
console.log(dialog.user)
onRejected: askDirectChatConfirmation.close();
}
} else {
Qt.openUrlExternally(link)
}
}
Component {
id: userDialog
UserDetailDialog {
id: openLinkConfirmationComponent
Kirigami.OverlaySheet {
id: openLinkConfirmation
required property var url;
header: Kirigami.Heading {
text: i18n("Confirm opening a link")
}
parent: QQC2.ApplicationWindow.overlay
contentItem: ColumnLayout {
QQC2.Label {
text: i18n("Do you want to open the link to %1?", `<a href='${url}'>${url}</a>`)
wrapMode: Text.WordWrap
}
QQC2.CheckBox {
id: dontAskAgain
text: i18n("Don't ask again")
}
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel
onAccepted: {
Config.confirmLinksAction = dontAskAgain.checked;
Config.save();
Qt.openUrlExternally(url);
openLinkConfirmation.close();
}
onRejected: openLinkConfirmation.close();
}
}
}
}
......@@ -13,6 +13,7 @@ add_executable(neochat
messageeventmodel.cpp
messagefiltermodel.cpp
roomlistmodel.cpp
roommanager.cpp
neochatroom.cpp
neochatuser.cpp
userlistmodel.cpp
......
......@@ -10,6 +10,7 @@
#include <QQmlContext>
#include <QQuickStyle>
#include <QQuickWindow>
#include <QDebug>
#include <KAboutData>
#ifdef HAVE_KDBUSADDONS
......@@ -41,8 +42,9 @@
#include "neochatuser.h"
#include "notificationsmanager.h"
#include "publicroomlistmodel.h"
#include "room.h"
#include <room.h>
#include "roomlistmodel.h"
#include "roommanager.h"
#include "sortfilterroomlistmodel.h"
#include "userdirectorylistmodel.h"
#include "userlistmodel.h"
......@@ -99,6 +101,17 @@ int main(int argc, char *argv[])
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
service.connect(&service,
&KDBusService::activateRequested,
roomManager,
[](const QStringList &arguments, const QString &workingDirectory) {
Q_UNUSED(workingDirectory);
auto args = arguments;
args.removeFirst();
for (const auto &arg : args) {
roomManager->openResource(arg);
}
});
#endif
#ifdef NEOCHAT_FLATPAK
......@@ -117,6 +130,7 @@ int main(int argc, char *argv[])
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Controller", &Controller::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config);
qmlRegisterSingletonInstance<RoomManager>("org.kde.neochat", 1, 0, "RoomManager", roomManager);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "FileType", &fileTypeSingleton);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ChatBoxHelper", &chatBoxHelper);
......@@ -160,6 +174,7 @@ int main(int argc, char *argv[])
QCommandLineParser parser;
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports appstream: url scheme"));
about.setupCommandLine(&parser);
parser.process(app);
......@@ -175,6 +190,10 @@ int main(int argc, char *argv[])
return -1;
}
if (parser.positionalArguments().length() > 0) {
roomManager->setUrlArgument(parser.positionalArguments()[0]);
}
#ifdef HAVE_KDBUSADDONS
QObject::connect(&service, &KDBusService::activateRequested, &engine, [&engine](const QStringList & /*arguments*/, const QString & /*workingDirectory*/) {
const auto rootObjects = engine.rootObjects();
......
......@@ -18,6 +18,10 @@
<label>Show notifications</label>
<default>true</default>
</entry>
<entry name="ConfirmLinksAction" type="bool">
<label>Confirm link before opening them</label>
<default>true</default>
</entry>
<entry name="MergeRoomList" type="bool">
<label>Merge Room Lists</label>
<default>false</default>
......
......@@ -222,20 +222,23 @@ void RoomListModel::handleNotifications()
}
oldNotifications += notification["event"].toObject()["event_id"].toString();
auto room = m_connection->room(notification["room_id"].toString());
auto sender = room->user(notification["event"].toObject()["sender"].toString());
QImage avatar_image;
if (!sender->avatarUrl(room).isEmpty()) {
avatar_image = sender->avatar(128, room);
} else {
avatar_image = room->avatar(128);
if (room) {
// The room might have been deleted (for example rejected invitation).
auto sender = room->user(notification["event"].toObject()["sender"].toString());
QImage avatar_image;
if (!sender->avatarUrl(room).isEmpty()) {
avatar_image = sender->avatar(128, room);
} else {
avatar_image = room->avatar(128);
}
NotificationsManager::instance().postNotification(dynamic_cast<NeoChatRoom *>(room),
room->displayName(),
sender->displayname(room),
notification["event"].toObject()["content"].toObject()["body"].toString(),
avatar_image,
notification["event"].toObject()["event_id"].toString());
}
NotificationsManager::instance().postNotification(dynamic_cast<NeoChatRoom *>(room),
room->displayName(),
sender->displayname(room),
notification["event"].toObject()["content"].toObject()["body"].toString(),
avatar_image,
notification["event"].toObject()["event_id"].toString());
}
});
}
......
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-FileCopyrightText: 2021 Alexey Rusakov <TODO>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "roommanager.h"
#include "neochatroom.h"
#include "neochatconfig.h"
#include "controller.h"
#include <QDesktopServices>
#include <KLocalizedString>
#include <csapi/joining.h>
#include <utility>
RoomManager::RoomManager(QObject *parent)
: QObject(parent)
, m_currentRoom(nullptr)