Port RoomManager to C++

This also makes it possible to handle the Matrix URI
This commit is contained in:
Carl Schwan
2021-04-27 21:07:10 +00:00
parent a2a6983123
commit b7d98fc6d9
13 changed files with 517 additions and 125 deletions

View File

@@ -5,6 +5,7 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2 import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import org.kde.neochat 1.0
import org.kde.kirigami 2.15 as Kirigami import org.kde.kirigami 2.15 as Kirigami
TextEdit { TextEdit {
@@ -47,7 +48,7 @@ a{
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
textFormat: Text.RichText textFormat: Text.RichText
onLinkActivated: applicationWindow().handleLink(link, currentRoom) onLinkActivated: RoomManager.openResource(link)
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent

View File

@@ -93,9 +93,7 @@ Loader {
Layout.maximumWidth: Kirigami.Units.gridUnit * 24 Layout.maximumWidth: Kirigami.Units.gridUnit * 24
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
onLinkActivated: { onLinkActivated: RoomManager.openResource(link);
applicationWindow().handleLink(link, currentRoom)
}
} }
} }
} }
@@ -189,9 +187,7 @@ Loader {
Layout.fillWidth: true Layout.fillWidth: true
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
onLinkActivated: { onLinkActivated: RoomManager.openResource(link);
applicationWindow().handleLink(link, currentRoom)
}
} }
} }
} }

View File

@@ -116,16 +116,15 @@ Kirigami.ScrollablePage {
action: Kirigami.Action { action: Kirigami.Action {
id: enterRoomAction id: enterRoomAction
onTriggered: { onTriggered: {
var roomItem = roomManager.enterRoom(currentRoom) RoomManager.enterRoom(currentRoom);
roomListItem.KeyNavigation.right = roomItem itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource(
roomItem.focus = true; sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent)
itemSelection.setCurrentIndex(sortFilterRoomListModel.mapToSource(sortFilterRoomListModel.index(index, 0)), ItemSelectionModel.SelectCurrent)
} }
} }
bold: unreadCount > 0 bold: unreadCount > 0
label: name ?? "" label: name ?? ""
subtitle: { 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) { if (txt.length) {
return txt return txt
} }

View File

@@ -21,7 +21,8 @@ import NeoChat.Menu.Timeline 1.0
Kirigami.ScrollablePage { Kirigami.ScrollablePage {
id: page id: page
required property var currentRoom /// It's not readonly because of the seperate window view.
property var currentRoom: RoomManager.currentRoom
title: currentRoom.displayName title: currentRoom.displayName
@@ -30,19 +31,17 @@ Kirigami.ScrollablePage {
onCurrentRoomChanged: ChatBoxHelper.clearEditReply() onCurrentRoomChanged: ChatBoxHelper.clearEditReply()
ActionsHandler { ActionsHandler {
id: actionsHandler id: actionsHandler
room: page.currentRoom room: page.currentRoom
connection: Controller.activeConnection connection: Controller.activeConnection
} }
Connections { Connections {
target: Controller.activeConnection target: Controller.activeConnection
function onJoinedRoom(room) { function onJoinedRoom(room) {
if(room.id === invitation.id) { if(room.id === invitation.id) {
roomManager.enterRoom(room); RoomManager.enterRoom(room);
} }
} }
} }
@@ -81,7 +80,7 @@ Kirigami.ScrollablePage {
onClicked: { onClicked: {
page.currentRoom.forget() page.currentRoom.forget()
roomManager.getBack(); RoomManager.getBack();
} }
} }
@@ -667,6 +666,11 @@ Kirigami.ScrollablePage {
FullScreenImage {} FullScreenImage {}
} }
Component {
id: userDetailDialog
UserDetailDialog {}
}
header: TypingPane { header: TypingPane {
id: 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() { function goToLastMessage() {
currentRoom.markAllMessagesAsRead() currentRoom.markAllMessagesAsRead()
// scroll to the very end, i.e to messageListView.YEnd // scroll to the very end, i.e to messageListView.YEnd

View File

@@ -16,7 +16,7 @@ import NeoChat.Dialog 1.0
Kirigami.OverlayDrawer { Kirigami.OverlayDrawer {
id: roomDrawer id: roomDrawer
property var room readonly property var room: RoomManager.currentRoom
enabled: true enabled: true

View File

@@ -70,7 +70,8 @@ Comment[sv]=Klient för protokollet Matrix
Comment[uk]=Клієнт протоколу Matrix Comment[uk]=Клієнт протоколу Matrix
Comment[x-test]=xxClient for the Matrix protocolxx Comment[x-test]=xxClient for the Matrix protocolxx
Comment[zh_CN]=为 Matrix 协议打造的客户端 Comment[zh_CN]=为 Matrix 协议打造的客户端
Exec=neochat MimeType=x-scheme-handler/matrix;
Exec=neochat %u
Terminal=false Terminal=false
Icon=org.kde.neochat Icon=org.kde.neochat
Type=Application Type=Application

View File

@@ -28,6 +28,8 @@ Kirigami.ApplicationWindow {
pageStack.initialPage: LoadingPage {} pageStack.initialPage: LoadingPage {}
property bool roomListLoaded: false
Connections { Connections {
target: root.quitAction target: root.quitAction
function onTriggered() { function onTriggered() {
@@ -50,78 +52,85 @@ Kirigami.ApplicationWindow {
onXChanged: saveWindowGeometryTimer.restart() onXChanged: saveWindowGeometryTimer.restart()
onYChanged: 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 /// Setup keyboard navigation to the room page.
property alias pageStack: root.pageStack function connectRoomToSignal(item) {
property var roomList: null if (!roomListLoaded) {
property Item roomItem: null console.log("Should not happen: no room list page but room page");
readonly property bool hasOpenRoom: currentRoom !== null
signal leaveRoom(string room);
signal openRoom(string room);
function roomByAliasOrId(aliasOrId) {
return Controller.activeConnection.room(aliasOrId)
} }
const roomList = pageStack.get(0);
item.switchRoomUp.connect(function() {
roomList.goToNextRoom();
});
function openRoomAndEvent(room, event) { item.switchRoomDown.connect(function() {
enterRoom(room) roomList.goToPreviousRoom();
roomItem.goToEvent(event) });
} item.forceActiveFocus();
item.KeyNavigation.left = pageStack.get(0);
}
function loadInitialRoom() { Connections {
if (Config.openRoom) { target: RoomManager
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 enterRoom(room) { function onPushRoom(room, event) {
if (currentRoom != null) { const roomItem = pageStack.push("qrc:/imports/NeoChat/Page/RoomPage.qml");
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();
connectRoomToSignal(roomItem); connectRoomToSignal(roomItem);
return roomItem; if (event.length > 0) {
roomItem.goToEvent(event);
}
} }
function getBack() { function onReplaceRoom(room, event) {
pageStack.replace("qrc:/imports/NeoChat/Page/RoomPage.qml", { 'currentRoom': currentRoom, }); 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}); const secondayWindow = roomWindow.createObject(applicationWindow(), {currentRoom: room});
secondayWindow.width = root.width - roomList.width; secondayWindow.width = root.width - roomList.width;
secondayWindow.show(); secondayWindow.show();
} }
function connectRoomToSignal(item) { function onShowUserDetail(user) {
if (!roomList) { const roomItem = pageStack.get(pageStack.depth - 1);
console.log("Should not happen: no room list page but room page"); roomItem.showUserDetail(user);
} }
item.switchRoomUp.connect(function() {
roomList.goToNextRoom();
});
item.switchRoomDown.connect(function() { function onAskDirectChatConfirmation(user) {
roomList.goToPreviousRoom(); 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 modal: !root.wideScreen || !enabled
onEnabledChanged: drawerOpen = enabled && !modal onEnabledChanged: drawerOpen = enabled && !modal
onModalChanged: drawerOpen = !modal onModalChanged: drawerOpen = !modal
enabled: roomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3 enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3
room: roomManager.currentRoom
handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3 handleVisible: enabled && pageStack.layers.depth < 2 && pageStack.depth < 3
} }
@@ -238,7 +246,7 @@ Kirigami.ApplicationWindow {
Connections { Connections {
target: LoginHelper target: LoginHelper
function onInitialSyncFinished() { function onInitialSyncFinished() {
roomManager.roomList = pageStack.replace(roomListComponent); RoomManager.roomList = pageStack.replace(roomListComponent);
} }
} }
@@ -246,32 +254,38 @@ Kirigami.ApplicationWindow {
target: Controller target: Controller
function onInitiated() { function onInitiated() {
if (roomManager.hasOpenRoom) { if (RoomManager.hasOpenRoom) {
return; return;
} }
if (Controller.accountCount === 0) { if (Controller.accountCount === 0) {
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {}); pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
} else { } else {
roomManager.roomList = pageStack.replace(roomListComponent, {'activeConnection': Controller.activeConnection}); pageStack.replace(roomListComponent, {
roomManager.loadInitialRoom(); activeConnection: Controller.activeConnection
});
roomListLoaded = true;
RoomManager.loadInitialRoom();
} }
} }
function onBusyChanged() { function onBusyChanged() {
if(!Controller.busy && roomManager.roomList === null) { if(!Controller.busy && roomListLoaded === false) {
roomManager.roomList = pageStack.replace(roomListComponent); pageStack.replace(roomListComponent);
roomListLoaded = true;
} }
} }
function onConnectionDropped() { function onConnectionDropped() {
if (Controller.accountCount === 0) { if (Controller.accountCount === 0) {
RoomManager.reset();
pageStack.clear(); pageStack.clear();
roomListLoaded = false;
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml"); pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml");
} }
} }
function onGlobalErrorOccured(error, detail) { function onGlobalErrorOccured(error, detail) {
showPassiveNotification(error + ": " + detail) showPassiveNotification(i18nc("%1: %2", error, detail));
} }
function onShowWindow() { function onShowWindow() {
@@ -279,7 +293,7 @@ Kirigami.ApplicationWindow {
} }
function onOpenRoom(room) { function onOpenRoom(room) {
roomManager.enterRoom(room) RoomManager.enterRoom(room)
} }
function onUserConsentRequired(url) { function onUserConsentRequired(url) {
@@ -288,14 +302,14 @@ Kirigami.ApplicationWindow {
} }
function onRoomJoined(roomName) { function onRoomJoined(roomName) {
roomManager.enterRoom(Controller.activeConnection.room(roomName)) RoomManager.enterRoom(Controller.activeConnection.room(roomName))
} }
} }
Connections { Connections {
target: Controller.activeConnection target: Controller.activeConnection
onDirectChatAvailable: { onDirectChatAvailable: {
roomManager.enterRoom(Controller.activeConnection.room(directChat.id)); RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
} }
} }
@@ -332,36 +346,68 @@ Kirigami.ApplicationWindow {
RoomWindow {} RoomWindow {}
} }
function handleLink(link, currentRoom) { Component {
if (link.startsWith("https://matrix.to/")) { id: userDialog
var content = link.replace("https://matrix.to/#/", "").replace(/\?.*/, "") UserDetailDialog {}
if(content.match("^[#!]")) { }
if(content.includes("/")) {
var result = content.match("([!#].*:.*)/(\\$.*)") Component {
if(!result) { id: askDirectChatConfirmationComponent
return
} Kirigami.OverlaySheet {
if(result[1] == currentRoom.id) { id: askDirectChatConfirmation
roomManager.roomItem.goToEvent(result[2]) required property var user;
} else {
roomManager.openRoomAndEvent(roomManager.roomByAliasOrId(result[1]), result[2]) parent: QQC2.ApplicationWindow.overlay
} header: Kirigami.Heading {
} else { text: i18n("Start a chat")
roomManager.enterRoom(roomManager.roomByAliasOrId(content)) }
} contentItem: QQC2.Label {
} else if(content.match("^@")) { text: i18n("Do you want to start a chat with %1?", user.displayName)
let dialog = userDialog.createObject(root.overlay, {room: currentRoom, user: currentRoom.user(content)}) wrapMode: Text.WordWrap
dialog.open() }
console.log(dialog.user) footer: QQC2.DialogButtonBox {
standardButtons: QQC2.DialogButtonBox.Ok | QQC2.DialogButtonBox.Cancel
onAccepted: {
user.requestDirectChat();
askDirectChatConfirmation.close();
}
onRejected: askDirectChatConfirmation.close();
} }
} else {
Qt.openUrlExternally(link)
} }
} }
Component { Component {
id: userDialog id: openLinkConfirmationComponent
UserDetailDialog {
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();
}
} }
} }
} }

View File

@@ -13,6 +13,7 @@ add_executable(neochat
messageeventmodel.cpp messageeventmodel.cpp
messagefiltermodel.cpp messagefiltermodel.cpp
roomlistmodel.cpp roomlistmodel.cpp
roommanager.cpp
neochatroom.cpp neochatroom.cpp
neochatuser.cpp neochatuser.cpp
userlistmodel.cpp userlistmodel.cpp

View File

@@ -10,6 +10,7 @@
#include <QQmlContext> #include <QQmlContext>
#include <QQuickStyle> #include <QQuickStyle>
#include <QQuickWindow> #include <QQuickWindow>
#include <QDebug>
#include <KAboutData> #include <KAboutData>
#ifdef HAVE_KDBUSADDONS #ifdef HAVE_KDBUSADDONS
@@ -41,8 +42,9 @@
#include "neochatuser.h" #include "neochatuser.h"
#include "notificationsmanager.h" #include "notificationsmanager.h"
#include "publicroomlistmodel.h" #include "publicroomlistmodel.h"
#include "room.h" #include <room.h>
#include "roomlistmodel.h" #include "roomlistmodel.h"
#include "roommanager.h"
#include "sortfilterroomlistmodel.h" #include "sortfilterroomlistmodel.h"
#include "userdirectorylistmodel.h" #include "userdirectorylistmodel.h"
#include "userlistmodel.h" #include "userlistmodel.h"
@@ -99,6 +101,17 @@ int main(int argc, char *argv[])
#ifdef HAVE_KDBUSADDONS #ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique); 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 #endif
#ifdef NEOCHAT_FLATPAK #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, "Controller", &Controller::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard); qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Clipboard", &clipboard);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Config", config); 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, "FileType", &fileTypeSingleton);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login); qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ChatBoxHelper", &chatBoxHelper); qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "ChatBoxHelper", &chatBoxHelper);
@@ -160,6 +174,7 @@ int main(int argc, char *argv[])
QCommandLineParser parser; QCommandLineParser parser;
parser.setApplicationDescription(i18n("Client for the matrix communication protocol")); parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports appstream: url scheme"));
about.setupCommandLine(&parser); about.setupCommandLine(&parser);
parser.process(app); parser.process(app);
@@ -175,6 +190,10 @@ int main(int argc, char *argv[])
return -1; return -1;
} }
if (parser.positionalArguments().length() > 0) {
roomManager->setUrlArgument(parser.positionalArguments()[0]);
}
#ifdef HAVE_KDBUSADDONS #ifdef HAVE_KDBUSADDONS
QObject::connect(&service, &KDBusService::activateRequested, &engine, [&engine](const QStringList & /*arguments*/, const QString & /*workingDirectory*/) { QObject::connect(&service, &KDBusService::activateRequested, &engine, [&engine](const QStringList & /*arguments*/, const QString & /*workingDirectory*/) {
const auto rootObjects = engine.rootObjects(); const auto rootObjects = engine.rootObjects();

View File

@@ -18,6 +18,10 @@
<label>Show notifications</label> <label>Show notifications</label>
<default>true</default> <default>true</default>
</entry> </entry>
<entry name="ConfirmLinksAction" type="bool">
<label>Confirm link before opening them</label>
<default>true</default>
</entry>
<entry name="MergeRoomList" type="bool"> <entry name="MergeRoomList" type="bool">
<label>Merge Room Lists</label> <label>Merge Room Lists</label>
<default>false</default> <default>false</default>

View File

@@ -222,20 +222,23 @@ void RoomListModel::handleNotifications()
} }
oldNotifications += notification["event"].toObject()["event_id"].toString(); oldNotifications += notification["event"].toObject()["event_id"].toString();
auto room = m_connection->room(notification["room_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; QImage avatar_image;
if (!sender->avatarUrl(room).isEmpty()) { if (!sender->avatarUrl(room).isEmpty()) {
avatar_image = sender->avatar(128, room); avatar_image = sender->avatar(128, room);
} else { } else {
avatar_image = room->avatar(128); 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());
} }
}); });
} }

197
src/roommanager.cpp Normal file
View File

@@ -0,0 +1,197 @@
// 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)
, 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<NeoChatRoom *>(
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<NeoChatRoom *>(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();
}

107
src/roommanager.h Normal file
View File

@@ -0,0 +1,107 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <uriresolver.h>
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)