From b7d98fc6d962dfe9b9b919e124b27a399cbbcd13 Mon Sep 17 00:00:00 2001 From: Carl Schwan Date: Tue, 27 Apr 2021 21:07:10 +0000 Subject: [PATCH] Port RoomManager to C++ This also makes it possible to handle the Matrix URI --- .../Component/Timeline/TextDelegate.qml | 3 +- .../Timeline/MessageDelegateContextMenu.qml | 8 +- imports/NeoChat/Page/RoomListPage.qml | 9 +- imports/NeoChat/Page/RoomPage.qml | 28 ++- imports/NeoChat/Panel/RoomDrawer.qml | 2 +- org.kde.neochat.desktop | 3 +- qml/main.qml | 232 +++++++++++------- src/CMakeLists.txt | 1 + src/main.cpp | 21 +- src/neochatconfig.kcfg | 4 + src/roomlistmodel.cpp | 27 +- src/roommanager.cpp | 197 +++++++++++++++ src/roommanager.h | 107 ++++++++ 13 files changed, 517 insertions(+), 125 deletions(-) create mode 100644 src/roommanager.cpp create mode 100644 src/roommanager.h diff --git a/imports/NeoChat/Component/Timeline/TextDelegate.qml b/imports/NeoChat/Component/Timeline/TextDelegate.qml index 2f47608f9..4cc06e372 100644 --- a/imports/NeoChat/Component/Timeline/TextDelegate.qml +++ b/imports/NeoChat/Component/Timeline/TextDelegate.qml @@ -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 diff --git a/imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml b/imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml index 409a36f8b..60e0492fa 100644 --- a/imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml +++ b/imports/NeoChat/Menu/Timeline/MessageDelegateContextMenu.qml @@ -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); } } } diff --git a/imports/NeoChat/Page/RoomListPage.qml b/imports/NeoChat/Page/RoomListPage.qml index 743b4778b..9fff16cbf 100644 --- a/imports/NeoChat/Page/RoomListPage.qml +++ b/imports/NeoChat/Page/RoomListPage.qml @@ -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 } diff --git a/imports/NeoChat/Page/RoomPage.qml b/imports/NeoChat/Page/RoomPage.qml index 6e488e3f1..c9dc20568 100644 --- a/imports/NeoChat/Page/RoomPage.qml +++ b/imports/NeoChat/Page/RoomPage.qml @@ -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}
${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 diff --git a/imports/NeoChat/Panel/RoomDrawer.qml b/imports/NeoChat/Panel/RoomDrawer.qml index 5c47e930e..4e463d48d 100644 --- a/imports/NeoChat/Panel/RoomDrawer.qml +++ b/imports/NeoChat/Panel/RoomDrawer.qml @@ -16,7 +16,7 @@ import NeoChat.Dialog 1.0 Kirigami.OverlayDrawer { id: roomDrawer - property var room + readonly property var room: RoomManager.currentRoom enabled: true diff --git a/org.kde.neochat.desktop b/org.kde.neochat.desktop index 7b8f56818..b97af09cd 100644 --- a/org.kde.neochat.desktop +++ b/org.kde.neochat.desktop @@ -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 diff --git a/qml/main.qml b/qml/main.qml index 677f4a350..900cec78a 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -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(); + }); - function openRoomAndEvent(room, event) { - enterRoom(room) - roomItem.goToEvent(event) - } + item.switchRoomDown.connect(function() { + roomList.goToPreviousRoom(); + }); + item.forceActiveFocus(); + item.KeyNavigation.left = pageStack.get(0); + } - 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 - } - } + Connections { + target: RoomManager - 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, }); - } - currentRoom = room; - Config.openRoom = room.id; - Config.save(); + function onPushRoom(room, event) { + const roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml"); connectRoomToSignal(roomItem); - return roomItem; + if (event.length > 0) { + roomItem.goToEvent(event); + } } - function getBack() { - pageStack.replace("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': currentRoom, }); + 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 openWindow(room) { + function goToEvent(event) { + if (event.length > 0) { + roomItem.goToEvent(event); + } + roomItem.forceActiveFocus(); + } + + function onPushWelcomePage() { + // TODO + } + + 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"); - } - item.switchRoomUp.connect(function() { - roomList.goToNextRoom(); - }); + function onShowUserDetail(user) { + const roomItem = pageStack.get(pageStack.depth - 1); + roomItem.showUserDetail(user); + } - item.switchRoomDown.connect(function() { - roomList.goToPreviousRoom(); - }); + 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)); + } + } + + 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)) - } - } else if(content.match("^@")) { - let dialog = userDialog.createObject(root.overlay, {room: currentRoom, user: currentRoom.user(content)}) - dialog.open() - console.log(dialog.user) + 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(); + } + 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?", `${url}`) + 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(); + } } } } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 842dc4e81..088203e7e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,6 +13,7 @@ add_executable(neochat messageeventmodel.cpp messagefiltermodel.cpp roomlistmodel.cpp + roommanager.cpp neochatroom.cpp neochatuser.cpp userlistmodel.cpp diff --git a/src/main.cpp b/src/main.cpp index 83d006375..9777804d6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #ifdef HAVE_KDBUSADDONS @@ -41,8 +42,9 @@ #include "neochatuser.h" #include "notificationsmanager.h" #include "publicroomlistmodel.h" -#include "room.h" +#include #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("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(); diff --git a/src/neochatconfig.kcfg b/src/neochatconfig.kcfg index d0e83866c..07b32dd0d 100644 --- a/src/neochatconfig.kcfg +++ b/src/neochatconfig.kcfg @@ -18,6 +18,10 @@ true + + + true + false diff --git a/src/roomlistmodel.cpp b/src/roomlistmodel.cpp index bf9495f94..2a23588a6 100644 --- a/src/roomlistmodel.cpp +++ b/src/roomlistmodel.cpp @@ -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()); + 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); + 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(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(room), - room->displayName(), - sender->displayname(room), - notification["event"].toObject()["content"].toObject()["body"].toString(), - avatar_image, - notification["event"].toObject()["event_id"].toString()); } }); } diff --git a/src/roommanager.cpp b/src/roommanager.cpp new file mode 100644 index 000000000..2cccdf61c --- /dev/null +++ b/src/roommanager.cpp @@ -0,0 +1,197 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-FileCopyrightText: 2021 Alexey Rusakov +// 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 +#include +#include +#include + +RoomManager::RoomManager(QObject *parent) + : QObject(parent) + , m_currentRoom(nullptr) + , m_lastCurrentRoom(nullptr) +{} + +RoomManager::~RoomManager() +{} + +NeoChatRoom *RoomManager::currentRoom() const +{ + return m_currentRoom; +} + +void RoomManager::openResource(const QString &idOrUri, const QString &action) +{ + Uri uri { idOrUri }; + if (!uri.isValid()) { + Q_EMIT warning(i18n("Malformed or empty Matrix id"), + i18n("%1 is not a correct Matrix identifier", idOrUri)); + return; + } + auto account = Controller::instance().activeConnection(); + + if (uri.type() != Uri::NonMatrix) { + if (!account) { + return; + } + if (!action.isEmpty()) { + uri.setAction(action); + } + // TODO we should allow the user to select a connection. + } + + const auto result = visitResource(account, uri); + if (result == Quotient::CouldNotResolve) { + Q_EMIT warning(i18n("Room not found"), + i18n("There's no room %1 in the room list. Check the spelling and the account.", idOrUri)); + } else { // Invalid cases should have been eliminated earlier + Q_ASSERT(result == Quotient::UriResolved); + } +} + +bool RoomManager::hasOpenRoom() const +{ + return m_currentRoom != nullptr; +} + +void RoomManager::setUrlArgument(const QString &arg) +{ + m_arg = arg; +} + +void RoomManager::loadInitialRoom() +{ + Q_ASSERT(Controller::instance().activeConnection()); + + if (!m_arg.isEmpty()) { + openResource(m_arg); + } + + if (m_currentRoom) { + // we opened a room with the arg parsing already + return; + } + + if (!NeoChatConfig::self()->openRoom().isEmpty()) { + // Here we can cast because the controller has been configured to + // return NeoChatRoom instead of simple Quotient::Room + const auto room = qobject_cast( + Controller::instance().activeConnection()->room(NeoChatConfig::self()->openRoom())); + m_lastCurrentRoom = std::exchange(m_currentRoom, room); + Q_EMIT currentRoomChanged(); + Q_EMIT pushRoom(room, QString()); + } else { + Q_EMIT pushWelcomePage(); + } +} + +void RoomManager::enterRoom(NeoChatRoom *room) +{ + if (!m_currentRoom) { + m_lastCurrentRoom = std::exchange(m_currentRoom, room); + Q_EMIT currentRoomChanged(); + Q_EMIT pushRoom(room, QString()); + } + + m_lastCurrentRoom = std::exchange(m_currentRoom, room); + Q_EMIT currentRoomChanged(); + + NeoChatConfig::self()->setOpenRoom(room->id()); + NeoChatConfig::self()->save(); +} + +void RoomManager::getBack() +{ + Q_ASSERT(m_currentRoom); + + if (!m_lastCurrentRoom) { + Q_EMIT pushWelcomePage(); + return; + } + + Q_EMIT replaceRoom(m_lastCurrentRoom, QString()); +} + +void RoomManager::openWindow(NeoChatRoom *room) +{ + // forward the call to QML + Q_EMIT openRoomInNewWindow(room); +} + +UriResolveResult RoomManager::visitUser(User* user, const QString &action) +{ + if (action == "mention" || action.isEmpty()) { + // send it has QVariantMap because the properties in the +#ifdef QUOTIENT_07 + user->load(); +#endif + Q_EMIT showUserDetail(user); + } else if (action == "_interactive") { + user->requestDirectChat(); + } else if (action == "chat") { +#ifdef QUOTIENT_07 + user->load(); +#endif + Q_EMIT askDirectChatConfirmation(user); + } else { + return Quotient::IncorrectAction; + } + + return Quotient::UriResolved; +} + +void RoomManager::visitRoom(Room *room, const QString &eventId) +{ + auto neoChatRoom = qobject_cast(room); + Q_ASSERT(neoChatRoom != nullptr); + + if (m_currentRoom) { + if (m_currentRoom->id() == room->id()) { + Q_EMIT goToEvent(eventId); + } else { + m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom); + Q_EMIT currentRoomChanged(); + Q_EMIT replaceRoom(neoChatRoom, eventId); + } + } else { + m_lastCurrentRoom = std::exchange(m_currentRoom, neoChatRoom); + Q_EMIT currentRoomChanged(); + Q_EMIT pushRoom(neoChatRoom, eventId); + } +} + +void RoomManager::joinRoom(Quotient::Connection *account, + const QString &roomAliasOrId, + const QStringList &viaServers) +{ + // We already listen to roomJoined signal in the Controller + account->joinRoom(QUrl::toPercentEncoding(roomAliasOrId), viaServers); +} + +bool RoomManager::visitNonMatrix(const QUrl &url) +{ + // Return true if the user cancels, treating it as an alternative normal + // flow (rather than an abnormal flow when the navigation itself fails). + if (NeoChatConfig::self()->confirmLinksAction()) { + Q_EMIT openLink(url); + } else { + if (!QDesktopServices::openUrl(url)) { + Q_EMIT warning(i18n("No application for the link"), + i18n("Your operating system could not find an application for the link.")); + } + } + return true; +} + +void RoomManager::reset() +{ + m_arg = QString(); + m_currentRoom = nullptr; + m_lastCurrentRoom = nullptr; + Q_EMIT currentRoomChanged(); +} diff --git a/src/roommanager.h b/src/roommanager.h new file mode 100644 index 000000000..e2fb2a955 --- /dev/null +++ b/src/roommanager.h @@ -0,0 +1,107 @@ +// SPDX-FileCopyrightText: 2021 Carl Schwan +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#pragma once + +#include +#include + +class NeoChatRoom; + +namespace Quotient { +class Room; +class User; +} + +using namespace Quotient; + +class RoomManager : public QObject, public UriResolverBase +{ + Q_OBJECT + + /// This property holds the current open room in the NeoChat, if any. + /// \sa hasOpenRoom + Q_PROPERTY(NeoChatRoom *currentRoom READ currentRoom NOTIFY currentRoomChanged) + + /// This property holds whether a room is currently open in NeoChat. + /// \sa room + Q_PROPERTY(bool hasOpenRoom READ hasOpenRoom NOTIFY currentRoomChanged) + +public: + explicit RoomManager(QObject *parent = nullptr); + virtual ~RoomManager(); + + /// Load the last opened room or the welcome page. + Q_INVOKABLE void loadInitialRoom(); + + /// This method will tell the NeoChat to open the message list + /// with the given room. + Q_INVOKABLE void enterRoom(NeoChatRoom *room); + + /// Force refresh the view to show the last the opened room. + Q_INVOKABLE void getBack(); + + Q_INVOKABLE void openWindow(NeoChatRoom *room); + + /// Getter for the currentRoom property. + NeoChatRoom *currentRoom() const; + + /// Getter for the hasOpenRoom property. + bool hasOpenRoom() const; + + // Overrided methods from UriResolverBase + UriResolveResult visitUser(User *user, const QString &action) override; + void joinRoom(Quotient::Connection *account, const QString &roomAliasOrId, + const QStringList &viaServers) override; + Q_INVOKABLE void visitRoom(Room *room, const QString &eventId) override; + Q_INVOKABLE bool visitNonMatrix(const QUrl &url) override; + + Q_INVOKABLE void openResource(const QString &idOrUri, const QString &action = {}); + + /// Call this when the current used connection is dropped. + Q_INVOKABLE void reset(); + + void setUrlArgument(const QString &arg); + +Q_SIGNALS: + /// Signal triggered when the current open room change. + void currentRoomChanged(); + + /// Signal triggered when the pageStack should push a new page with the + /// message list for the given room. + void pushRoom(NeoChatRoom *room, const QString &event); + + /// Signal triggered when the room displayed by the message list should + /// be changed. + void replaceRoom(NeoChatRoom *room, const QString &event); + + /// Go to the specified event in the current room. + void goToEvent(const QString &event); + + /// Signal triggered when the pageStack should push a welcome page. + void pushWelcomePage(); + + /// Signal triggered when a room need to be opened in a new window. + void openRoomInNewWindow(NeoChatRoom *room); + + /// Ask current room to open the user's details for the give user. + /// This can assume the user is loaded. + void showUserDetail(const User *user); + + /// Ask current room to show confirmation dialog to open direct chat. + /// This can assume the user is loaded. + void askDirectChatConfirmation(const User *user); + + /// Displays warning to the user. + void warning(const QString &title, const QString &message); + + /// Ask user to open link and then open it. + void openLink(const QUrl &url); + +private: + NeoChatRoom *m_currentRoom; + NeoChatRoom *m_lastCurrentRoom; + QString m_arg; +}; + +Q_GLOBAL_STATIC(RoomManager, roomManager)