Compare commits
30 Commits
work/pushn
...
work/fix_c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0083199493 | ||
|
|
d4cb27eca4 | ||
|
|
541350e678 | ||
|
|
843deefaf8 | ||
|
|
070d579bc2 | ||
|
|
add283c9fb | ||
|
|
fe4230b5fd | ||
|
|
e8f40d98de | ||
|
|
eba62103a4 | ||
|
|
925393deab | ||
|
|
abe881caf7 | ||
|
|
237a3c9dfb | ||
|
|
9715440854 | ||
|
|
ecdad9f965 | ||
|
|
08711fc927 | ||
|
|
e44cd405b7 | ||
|
|
8945e004e2 | ||
|
|
c04d8d6f59 | ||
|
|
58a73c0208 | ||
|
|
852110debd | ||
|
|
6b71d3c78d | ||
|
|
f3a0adee39 | ||
|
|
6e7b6c9ce0 | ||
|
|
f67cd7deb5 | ||
|
|
931b4b1f9a | ||
|
|
167ed4eca3 | ||
|
|
7d5b2c1b6a | ||
|
|
be7b1e49b4 | ||
|
|
957419070a | ||
|
|
f22107c8ab |
@@ -63,9 +63,9 @@ set_package_properties(KF6 PROPERTIES
|
|||||||
PURPOSE "Basic application components"
|
PURPOSE "Basic application components"
|
||||||
)
|
)
|
||||||
set_package_properties(KF6Kirigami2 PROPERTIES
|
set_package_properties(KF6Kirigami2 PROPERTIES
|
||||||
TYPE REQUIRED
|
TYPE REQUIRED
|
||||||
PURPOSE "Kirigami application UI framework"
|
PURPOSE "Kirigami application UI framework"
|
||||||
)
|
)
|
||||||
find_package(KF6KirigamiAddons 0.7.2 REQUIRED)
|
find_package(KF6KirigamiAddons 0.7.2 REQUIRED)
|
||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
@@ -81,6 +81,12 @@ else()
|
|||||||
TYPE RUNTIME
|
TYPE RUNTIME
|
||||||
)
|
)
|
||||||
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
|
||||||
|
|
||||||
|
find_package(ICU 61.0 COMPONENTS uc)
|
||||||
|
set_package_properties(ICU PROPERTIES
|
||||||
|
TYPE REQUIRED
|
||||||
|
PURPOSE "Unicode library"
|
||||||
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||||
|
|||||||
@@ -4,25 +4,26 @@
|
|||||||
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
|
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
|
||||||
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||||
|
|
||||||
import unittest
|
|
||||||
from appium import webdriver
|
|
||||||
from appium.webdriver.common.appiumby import AppiumBy
|
|
||||||
from selenium.webdriver.support.ui import WebDriverWait
|
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
from appium import webdriver
|
||||||
|
from appium.options.common.base import AppiumOptions
|
||||||
|
from appium.webdriver.common.appiumby import AppiumBy
|
||||||
|
|
||||||
|
|
||||||
class LoginTest(unittest.TestCase):
|
class LoginTest(unittest.TestCase):
|
||||||
|
|
||||||
|
mockServerProcess: subprocess.Popen
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(self):
|
def setUpClass(cls):
|
||||||
desired_caps = {}
|
options = AppiumOptions()
|
||||||
desired_caps["app"] = "neochat --ignore-ssl-errors"
|
options.set_capability("app", "neochat --ignore-ssl-errors")
|
||||||
desired_caps["timeouts"] = {'implicit': 10000}
|
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
|
||||||
self.driver = webdriver.Remote(
|
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
|
||||||
command_executor='http://127.0.0.1:4723',
|
|
||||||
desired_capabilities=desired_caps)
|
|
||||||
self.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
pass
|
pass
|
||||||
@@ -45,5 +46,6 @@ class LoginTest(unittest.TestCase):
|
|||||||
self.driver.find_element(by=AppiumBy.NAME, value="Login").click()
|
self.driver.find_element(by=AppiumBy.NAME, value="Login").click()
|
||||||
self.driver.find_element(by=AppiumBy.NAME, value="Join some rooms to get started").click()
|
self.driver.find_element(by=AppiumBy.NAME, value="Join some rooms to get started").click()
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
#
|
#
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR})
|
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${KDE_INSTALL_MANDIR})
|
||||||
|
|||||||
@@ -53,6 +53,7 @@
|
|||||||
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
|
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
|
||||||
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
|
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
|
||||||
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
|
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
|
||||||
|
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
|
||||||
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
|
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
|
||||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||||
<summary xml:lang="ia">Starta Conversation conntu amicos sur matrix</summary>
|
<summary xml:lang="ia">Starta Conversation conntu amicos sur matrix</summary>
|
||||||
|
|||||||
@@ -1 +1,8 @@
|
|||||||
<svg width="22" height="22" fill="none" version="1.1" id="svg13" xmlns="http://www.w3.org/2000/svg"><style type="text/css" id="current-color-scheme">.ColorScheme-Text{color:#232629}</style><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="M2 4h18v11H6.681L3 18.067V15H2zm1 10h1v1.933L6.319 14H19V5H3z" id="path3"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect5" d="M4 7h9v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect7" d="M4 9h7v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect9" d="M4 11h5v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="m16 15.293-1.147-1.146-.707.707 2.853 2.853V14.5h-1z" id="path11"/></svg>
|
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<style type="text/css" id="current-color-scheme">.ColorScheme-Text{color:#232629}</style>
|
||||||
|
<path class="ColorScheme-Text" fill-rule="evenodd" clip-rule="evenodd" d="M3 3H19V14H8.68787L4 18.1019V14H3V3ZM4 13H5V15.8981L8.31213 13H18V4H4V13Z" fill="currentColor"/>
|
||||||
|
<path class="ColorScheme-Text" fill-rule="evenodd" clip-rule="evenodd" d="M17 15.2929L14.8536 13.1465L14.1465 13.8536L18 17.7071V13.5H17V15.2929Z" fill="currentColor"/>
|
||||||
|
<path class="ColorScheme-Text" d="M5 6H15V7H5V6Z" fill="currentColor"/>
|
||||||
|
<path class="ColorScheme-Text" d="M5 8H13V9H5V8Z" fill="currentColor"/>
|
||||||
|
<path class="ColorScheme-Text" d="M5 10H11V11H5V10Z" fill="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 928 B After Width: | Height: | Size: 752 B |
5425
po/ar/neochat.po
5425
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
5160
po/az/neochat.po
5160
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
4498
po/ca/neochat.po
4498
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
4990
po/cs/neochat.po
4990
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
5097
po/da/neochat.po
5097
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
4802
po/de/neochat.po
4802
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
5206
po/el/neochat.po
5206
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
5649
po/en_GB/neochat.po
5649
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
4409
po/eo/neochat.po
4409
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
4492
po/es/neochat.po
4492
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
5370
po/eu/neochat.po
5370
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
4991
po/fi/neochat.po
4991
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
5000
po/fr/neochat.po
5000
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
4750
po/hu/neochat.po
4750
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
4981
po/ia/neochat.po
4981
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
4673
po/id/neochat.po
4673
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
5330
po/ie/neochat.po
5330
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
4501
po/it/neochat.po
4501
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
4878
po/ja/neochat.po
4878
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
4427
po/ka/neochat.po
4427
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
4887
po/ko/neochat.po
4887
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
4922
po/lt/neochat.po
4922
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
4482
po/nl/neochat.po
4482
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
4497
po/nn/neochat.po
4497
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
5501
po/pa/neochat.po
5501
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
4456
po/pl/neochat.po
4456
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
4681
po/pt/neochat.po
4681
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
5196
po/pt_BR/neochat.po
5196
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
5172
po/ru/neochat.po
5172
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
5314
po/sk/neochat.po
5314
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
4618
po/sl/neochat.po
4618
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
4679
po/sv/neochat.po
4679
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
4457
po/ta/neochat.po
4457
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
5750
po/tok/neochat.po
5750
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
4463
po/tr/neochat.po
4463
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
5021
po/uk/neochat.po
5021
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
5269
po/zh_CN/neochat.po
5269
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
4878
po/zh_TW/neochat.po
4878
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,12 @@ add_library(neochat STATIC
|
|||||||
models/userfiltermodel.h
|
models/userfiltermodel.h
|
||||||
models/publicroomlistmodel.cpp
|
models/publicroomlistmodel.cpp
|
||||||
models/publicroomlistmodel.h
|
models/publicroomlistmodel.h
|
||||||
|
models/spacechildrenmodel.cpp
|
||||||
|
models/spacechildrenmodel.h
|
||||||
|
models/spacechildsortfiltermodel.cpp
|
||||||
|
models/spacechildsortfiltermodel.h
|
||||||
|
models/spacetreeitem.cpp
|
||||||
|
models/spacetreeitem.h
|
||||||
models/userdirectorylistmodel.cpp
|
models/userdirectorylistmodel.cpp
|
||||||
models/userdirectorylistmodel.h
|
models/userdirectorylistmodel.h
|
||||||
models/pushrulemodel.cpp
|
models/pushrulemodel.cpp
|
||||||
@@ -127,6 +133,8 @@ add_library(neochat STATIC
|
|||||||
mediasizehelper.h
|
mediasizehelper.h
|
||||||
eventhandler.cpp
|
eventhandler.cpp
|
||||||
enums/delegatetype.h
|
enums/delegatetype.h
|
||||||
|
roomlastmessageprovider.cpp
|
||||||
|
roomlastmessageprovider.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||||
@@ -207,7 +215,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/Sso.qml
|
qml/Sso.qml
|
||||||
qml/UserDetailDialog.qml
|
qml/UserDetailDialog.qml
|
||||||
qml/CreateRoomDialog.qml
|
qml/CreateRoomDialog.qml
|
||||||
qml/CreateSpaceDialog.qml
|
|
||||||
qml/EmojiDialog.qml
|
qml/EmojiDialog.qml
|
||||||
qml/OpenFileDialog.qml
|
qml/OpenFileDialog.qml
|
||||||
qml/KeyVerificationDialog.qml
|
qml/KeyVerificationDialog.qml
|
||||||
@@ -271,6 +278,9 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
|||||||
qml/RoomMedia.qml
|
qml/RoomMedia.qml
|
||||||
qml/ChooseRoomDialog.qml
|
qml/ChooseRoomDialog.qml
|
||||||
qml/ShareAction.qml
|
qml/ShareAction.qml
|
||||||
|
qml/SpaceHomePage.qml
|
||||||
|
qml/SpaceHierarchyDelegate.qml
|
||||||
|
qml/RemoveChildDialog.qml
|
||||||
RESOURCES
|
RESOURCES
|
||||||
qml/confetti.png
|
qml/confetti.png
|
||||||
qml/glowdot.png
|
qml/glowdot.png
|
||||||
@@ -319,9 +329,10 @@ if(NOT ANDROID)
|
|||||||
else()
|
else()
|
||||||
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
|
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(neochat PUBLIC KF6::ConfigWidgets KF6::WindowSystem)
|
target_link_libraries(neochat PUBLIC KF6::ConfigWidgets KF6::WindowSystem ICU::uc)
|
||||||
target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME)
|
target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME)
|
||||||
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
|
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
|
||||||
|
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||||
|
|||||||
@@ -93,8 +93,9 @@ public:
|
|||||||
Q_ENUM(PasswordStatus)
|
Q_ENUM(PasswordStatus)
|
||||||
|
|
||||||
static Controller &instance();
|
static Controller &instance();
|
||||||
static Controller *create(QQmlEngine *, QJSEngine *)
|
static Controller *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
|
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||||
return &instance();
|
return &instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -231,8 +231,11 @@ bool EventHandler::isHidden()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_event->isStateEvent() && eventCast<const StateEvent>(m_event)->repeatsState()) {
|
if (m_event->isStateEvent()) {
|
||||||
return true;
|
auto *stateEvent = eventCast<const StateEvent>(m_event);
|
||||||
|
if (stateEvent && stateEvent->repeatsState()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// isReplacement?
|
// isReplacement?
|
||||||
@@ -986,3 +989,5 @@ QString EventHandler::getReadMarkersString() const
|
|||||||
readMarkersString.chop(2);
|
readMarkersString.chop(2);
|
||||||
return readMarkersString;
|
return readMarkersString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "moc_eventhandler.cpp"
|
||||||
|
|||||||
46
src/main.cpp
46
src/main.cpp
@@ -34,69 +34,25 @@
|
|||||||
|
|
||||||
#include "neochat-version.h"
|
#include "neochat-version.h"
|
||||||
|
|
||||||
#include <Quotient/accountregistry.h>
|
|
||||||
#include <Quotient/keyverificationsession.h>
|
|
||||||
#include <Quotient/networkaccessmanager.h>
|
#include <Quotient/networkaccessmanager.h>
|
||||||
#include <Quotient/room.h>
|
|
||||||
#include <Quotient/user.h>
|
|
||||||
#include <Quotient/util.h>
|
#include <Quotient/util.h>
|
||||||
|
|
||||||
#include "actionshandler.h"
|
|
||||||
#include "blurhashimageprovider.h"
|
#include "blurhashimageprovider.h"
|
||||||
#include "chatdocumenthandler.h"
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "delegatesizehelper.h"
|
|
||||||
#include "enums/delegatetype.h"
|
|
||||||
#include "linkpreviewer.h"
|
|
||||||
#include "locationhelper.h"
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
#include "login.h"
|
|
||||||
#include "matriximageprovider.h"
|
#include "matriximageprovider.h"
|
||||||
#include "mediasizehelper.h"
|
|
||||||
#include "models/accountemoticonmodel.h"
|
|
||||||
#include "models/customemojimodel.h"
|
|
||||||
#include "models/devicesmodel.h"
|
|
||||||
#include "models/devicesproxymodel.h"
|
|
||||||
#include "models/emojimodel.h"
|
|
||||||
#include "models/emoticonfiltermodel.h"
|
|
||||||
#include "models/imagepacksmodel.h"
|
|
||||||
#include "models/livelocationsmodel.h"
|
|
||||||
#include "models/locationsmodel.h"
|
|
||||||
#include "models/mediamessagefiltermodel.h"
|
|
||||||
#include "models/messageeventmodel.h"
|
|
||||||
#include "models/messagefiltermodel.h"
|
|
||||||
#include "models/publicroomlistmodel.h"
|
|
||||||
#include "models/pushrulemodel.h"
|
|
||||||
#include "models/reactionmodel.h"
|
|
||||||
#include "models/roomlistmodel.h"
|
|
||||||
#include "models/searchmodel.h"
|
|
||||||
#include "models/serverlistmodel.h"
|
|
||||||
#include "models/sortfilterroomlistmodel.h"
|
|
||||||
#include "models/sortfilterspacelistmodel.h"
|
|
||||||
#include "models/statefiltermodel.h"
|
|
||||||
#include "models/stickermodel.h"
|
|
||||||
#include "models/userdirectorylistmodel.h"
|
|
||||||
#include "models/userfiltermodel.h"
|
|
||||||
#include "models/userlistmodel.h"
|
|
||||||
#include "models/webshortcutmodel.h"
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "pollhandler.h"
|
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "spacehierarchycache.h"
|
|
||||||
#include "urlhelper.h"
|
|
||||||
#include "windowcontroller.h"
|
#include "windowcontroller.h"
|
||||||
|
|
||||||
#ifdef HAVE_COLORSCHEME
|
#ifdef HAVE_COLORSCHEME
|
||||||
#include "colorschemer.h"
|
#include "colorschemer.h"
|
||||||
#endif
|
#endif
|
||||||
#include "models/completionmodel.h"
|
|
||||||
#include "models/statemodel.h"
|
|
||||||
|
|
||||||
#ifdef HAVE_RUNNER
|
#ifdef HAVE_RUNNER
|
||||||
#include "runner.h"
|
#include "runner.h"
|
||||||
#include <QDBusConnection>
|
#include <QDBusConnection>
|
||||||
#endif
|
#endif
|
||||||
#include "registration.h"
|
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
#include <Windows.h>
|
#include <Windows.h>
|
||||||
@@ -223,7 +179,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
|
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
|
||||||
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
|
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
|
||||||
|
|
||||||
// qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
|
qmlRegisterUncreatableType<KeyVerificationSession>("com.github.quotient_im.libquotient", 1, 0, "KeyVerificationSession", {});
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ public:
|
|||||||
static CustomEmojiModel _instance;
|
static CustomEmojiModel _instance;
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
static CustomEmojiModel *create(QQmlEngine *, QJSEngine *)
|
static CustomEmojiModel *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
|
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||||
return &instance();
|
return &instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,9 @@ public:
|
|||||||
static EmojiModel _instance;
|
static EmojiModel _instance;
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
static EmojiModel *create(QQmlEngine *, QJSEngine *)
|
static EmojiModel *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
|
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||||
return &instance();
|
return &instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||||
roles[ReactionRole] = "reaction";
|
roles[ReactionRole] = "reaction";
|
||||||
roles[ShowReactionsRole] = "showReactions";
|
roles[ShowReactionsRole] = "showReactions";
|
||||||
roles[AuthorIdRole] = "authorId";
|
|
||||||
roles[VerifiedRole] = "verified";
|
roles[VerifiedRole] = "verified";
|
||||||
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
||||||
roles[IsRedactedRole] = "isRedacted";
|
roles[IsRedactedRole] = "isRedacted";
|
||||||
@@ -659,10 +658,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return m_reactionModels.contains(evt.id());
|
return m_reactionModels.contains(evt.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == AuthorIdRole) {
|
|
||||||
return evt.senderId();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == VerifiedRole) {
|
if (role == VerifiedRole) {
|
||||||
if (evt.originalEvent()) {
|
if (evt.originalEvent()) {
|
||||||
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
|
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
|
||||||
|
|||||||
@@ -73,8 +73,6 @@ public:
|
|||||||
ReactionRole, /**< List model for this event. */
|
ReactionRole, /**< List model for this event. */
|
||||||
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
|
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
|
||||||
|
|
||||||
AuthorIdRole, /**< Matrix ID of the message author. */
|
|
||||||
|
|
||||||
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
|
||||||
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||||
IsRedactedRole, /**< Whether an event has been deleted. */
|
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||||
|
|||||||
@@ -4,6 +4,12 @@
|
|||||||
#include "reactionmodel.h"
|
#include "reactionmodel.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#ifdef HAVE_ICU
|
||||||
|
#include <QTextBoundaryFinder>
|
||||||
|
#include <QTextCharFormat>
|
||||||
|
#include <unicode/uchar.h>
|
||||||
|
#include <unicode/urename.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
@@ -29,11 +35,38 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
const auto &reaction = m_reactions.at(index.row());
|
const auto &reaction = m_reactions.at(index.row());
|
||||||
|
|
||||||
if (role == TextRole) {
|
const auto isEmoji = [](const QString &text) {
|
||||||
|
#ifdef HAVE_ICU
|
||||||
|
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||||
|
int from = 0;
|
||||||
|
while (finder.toNextBoundary() != -1) {
|
||||||
|
auto to = finder.position();
|
||||||
|
if (text[from].isSpace()) {
|
||||||
|
from = to;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||||
|
if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
from = to;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto reactionText = isEmoji(reaction.reaction)
|
||||||
|
? QStringLiteral("<span style=\"font-family: 'emoji';\">") + reaction.reaction + QStringLiteral("</span>")
|
||||||
|
: reaction.reaction;
|
||||||
|
|
||||||
|
if (role == TextContentRole) {
|
||||||
if (reaction.authors.count() > 1) {
|
if (reaction.authors.count() > 1) {
|
||||||
return QStringLiteral("%1 %2").arg(reaction.reaction, QString::number(reaction.authors.count()));
|
return QStringLiteral("%1 %2").arg(reactionText, QString::number(reaction.authors.count()));
|
||||||
} else {
|
} else {
|
||||||
return reaction.reaction;
|
return reactionText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +97,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
|||||||
"%2 reacted with %3",
|
"%2 reacted with %3",
|
||||||
reaction.authors.count(),
|
reaction.authors.count(),
|
||||||
text,
|
text,
|
||||||
reaction.reaction);
|
reactionText);
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +134,7 @@ void ReactionModel::setReactions(QList<Reaction> reactions)
|
|||||||
QHash<int, QByteArray> ReactionModel::roleNames() const
|
QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
{TextRole, "text"},
|
{TextContentRole, "textContent"},
|
||||||
{ReactionRole, "reaction"},
|
{ReactionRole, "reaction"},
|
||||||
{ToolTipRole, "toolTip"},
|
{ToolTipRole, "toolTip"},
|
||||||
{AuthorsRole, "authors"},
|
{AuthorsRole, "authors"},
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ public:
|
|||||||
* @brief Defines the model roles.
|
* @brief Defines the model roles.
|
||||||
*/
|
*/
|
||||||
enum Roles {
|
enum Roles {
|
||||||
TextRole = Qt::DisplayRole, /**< The text to show in the reaction. */
|
TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */
|
||||||
ReactionRole, /**< The reaction emoji. */
|
ReactionRole, /**< The reaction emoji. */
|
||||||
ToolTipRole, /**< The tool tip to show for the reaction. */
|
ToolTipRole, /**< The tool tip to show for the reaction. */
|
||||||
AuthorsRole, /**< The list of authors who sent the given reaction. */
|
AuthorsRole, /**< The list of authors who sent the given reaction. */
|
||||||
|
|||||||
334
src/models/spacechildrenmodel.cpp
Normal file
334
src/models/spacechildrenmodel.cpp
Normal file
@@ -0,0 +1,334 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "spacechildrenmodel.h"
|
||||||
|
|
||||||
|
#include <Quotient/connection.h>
|
||||||
|
#include <Quotient/jobs/basejob.h>
|
||||||
|
#include <Quotient/room.h>
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
|
||||||
|
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
||||||
|
: QAbstractItemModel(parent)
|
||||||
|
{
|
||||||
|
m_rootItem = new SpaceTreeItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceChildrenModel::~SpaceChildrenModel()
|
||||||
|
{
|
||||||
|
delete m_rootItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
NeoChatRoom *SpaceChildrenModel::space() const
|
||||||
|
{
|
||||||
|
return m_space;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceChildrenModel::setSpace(NeoChatRoom *space)
|
||||||
|
{
|
||||||
|
if (space == m_space) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// disconnect the new room signal from the old connection in case it is different.
|
||||||
|
if (m_space != nullptr) {
|
||||||
|
disconnect(m_space->connection(), &Quotient::Connection::loadedRoomState, this, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_space = space;
|
||||||
|
Q_EMIT spaceChanged();
|
||||||
|
|
||||||
|
for (auto job : m_currentJobs) {
|
||||||
|
if (job) {
|
||||||
|
job->abandon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_currentJobs.clear();
|
||||||
|
|
||||||
|
auto connection = m_space->connection();
|
||||||
|
connect(connection, &Quotient::Connection::loadedRoomState, this, [this](Quotient::Room *room) {
|
||||||
|
if (m_pendingChildren.contains(room->name())) {
|
||||||
|
m_pendingChildren.removeAll(room->name());
|
||||||
|
refreshModel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_space, &Quotient::Room::changed, this, [this]() {
|
||||||
|
refreshModel();
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceChildrenModel::loading() const
|
||||||
|
{
|
||||||
|
return m_loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceChildrenModel::refreshModel()
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_replacedRooms.clear();
|
||||||
|
delete m_rootItem;
|
||||||
|
m_loading = true;
|
||||||
|
Q_EMIT loadingChanged();
|
||||||
|
m_rootItem = new SpaceTreeItem(nullptr, m_space->id(), m_space->displayName(), m_space->canonicalAlias());
|
||||||
|
endResetModel();
|
||||||
|
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(m_space->id(), Quotient::none, Quotient::none, 1);
|
||||||
|
m_currentJobs.append(job);
|
||||||
|
connect(job, &Quotient::BaseJob::success, this, [this, job]() {
|
||||||
|
insertChildren(job->rooms());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent)
|
||||||
|
{
|
||||||
|
SpaceTreeItem *parentItem = getItem(parent);
|
||||||
|
|
||||||
|
if (children[0].roomId == m_space->id() || children[0].roomId == parentItem->id()) {
|
||||||
|
children.erase(children.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the first set of children added to the root item then we need to
|
||||||
|
// set it so that we are no longer loading.
|
||||||
|
if (rowCount(QModelIndex()) == 0 && !children.empty()) {
|
||||||
|
m_loading = false;
|
||||||
|
Q_EMIT loadingChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
beginInsertRows(parent, parentItem->childCount(), parentItem->childCount() + children.size() - 1);
|
||||||
|
for (unsigned long i = 0; i < children.size(); ++i) {
|
||||||
|
if (children[i].roomId == m_space->id() || children[i].roomId == parentItem->id()) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
int insertRow = parentItem->childCount();
|
||||||
|
if (const auto room = m_space->connection()->room(children[i].roomId)) {
|
||||||
|
const auto predecessorId = room->predecessorId();
|
||||||
|
if (!predecessorId.isEmpty()) {
|
||||||
|
m_replacedRooms += predecessorId;
|
||||||
|
}
|
||||||
|
const auto successorId = room->successorId();
|
||||||
|
if (!successorId.isEmpty()) {
|
||||||
|
m_replacedRooms += successorId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parentItem->insertChild(insertRow,
|
||||||
|
new SpaceTreeItem(parentItem,
|
||||||
|
children[i].roomId,
|
||||||
|
children[i].name,
|
||||||
|
children[i].canonicalAlias,
|
||||||
|
children[i].topic,
|
||||||
|
children[i].numJoinedMembers,
|
||||||
|
children[i].avatarUrl,
|
||||||
|
children[i].guestCanJoin,
|
||||||
|
children[i].worldReadable,
|
||||||
|
children[i].roomType == QLatin1String("m.space")));
|
||||||
|
if (children[i].childrenState.size() > 0) {
|
||||||
|
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, Quotient::none, Quotient::none, 1);
|
||||||
|
m_currentJobs.append(job);
|
||||||
|
connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() {
|
||||||
|
insertChildren(job->rooms(), index(insertRow, 0, parent));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem *SpaceChildrenModel::getItem(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (index.isValid()) {
|
||||||
|
SpaceTreeItem *item = static_cast<SpaceTreeItem *>(index.internalPointer());
|
||||||
|
if (item) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return m_rootItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant SpaceChildrenModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem *child = getItem(index);
|
||||||
|
if (role == DisplayNameRole) {
|
||||||
|
auto displayName = child->name();
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName = child->canonicalAlias();
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return child->id();
|
||||||
|
}
|
||||||
|
if (role == AvatarUrlRole) {
|
||||||
|
return child->avatarUrl();
|
||||||
|
}
|
||||||
|
if (role == TopicRole) {
|
||||||
|
return child->topic();
|
||||||
|
}
|
||||||
|
if (role == RoomIDRole) {
|
||||||
|
return child->id();
|
||||||
|
}
|
||||||
|
if (role == AliasRole) {
|
||||||
|
return child->canonicalAlias();
|
||||||
|
}
|
||||||
|
if (role == MemberCountRole) {
|
||||||
|
return child->memberCount();
|
||||||
|
}
|
||||||
|
if (role == AllowGuestsRole) {
|
||||||
|
return child->allowGuests();
|
||||||
|
}
|
||||||
|
if (role == WorldReadableRole) {
|
||||||
|
return child->worldReadable();
|
||||||
|
}
|
||||||
|
if (role == IsJoinedRole) {
|
||||||
|
return child->isJoined();
|
||||||
|
}
|
||||||
|
if (role == IsSpaceRole) {
|
||||||
|
return child->isSpace();
|
||||||
|
}
|
||||||
|
if (role == CanAddChildrenRole) {
|
||||||
|
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
|
||||||
|
return room->canSendState(QLatin1String("m.space.child"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (role == ParentDisplayNameRole) {
|
||||||
|
const auto parent = child->parentItem();
|
||||||
|
auto displayName = parent->name();
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName = parent->canonicalAlias();
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parent->id();
|
||||||
|
}
|
||||||
|
if (role == CanSetParentRole) {
|
||||||
|
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
|
||||||
|
return room->canSendState(QLatin1String("m.space.parent"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (role == IsDeclaredParentRole) {
|
||||||
|
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(child->id()))) {
|
||||||
|
return room->currentState().contains(QLatin1String("m.space.parent"), child->parentItem()->id());
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (role == CanRemove) {
|
||||||
|
const auto parent = child->parentItem();
|
||||||
|
if (const auto room = static_cast<NeoChatRoom *>(m_space->connection()->room(parent->id()))) {
|
||||||
|
return room->canSendState(QLatin1String("m.space.child"));
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (role == ParentRoomRole) {
|
||||||
|
if (const auto parentRoom = static_cast<NeoChatRoom *>(m_space->connection()->room(child->parentItem()->id()))) {
|
||||||
|
return QVariant::fromValue(parentRoom);
|
||||||
|
}
|
||||||
|
return QVariant::fromValue(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex SpaceChildrenModel::index(int row, int column, const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
if (!hasIndex(row, column, parent)) {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem *parentItem = getItem(parent);
|
||||||
|
if (!parentItem) {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem *childItem = parentItem->child(row);
|
||||||
|
if (childItem) {
|
||||||
|
return createIndex(row, column, childItem);
|
||||||
|
}
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
QModelIndex SpaceChildrenModel::parent(const QModelIndex &index) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem *childItem = static_cast<SpaceTreeItem *>(index.internalPointer());
|
||||||
|
SpaceTreeItem *parentItem = childItem->parentItem();
|
||||||
|
|
||||||
|
if (parentItem == m_rootItem) {
|
||||||
|
return QModelIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
return createIndex(parentItem->row(), 0, parentItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpaceChildrenModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
SpaceTreeItem *parentItem;
|
||||||
|
if (parent.column() > 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!parent.isValid()) {
|
||||||
|
parentItem = m_rootItem;
|
||||||
|
} else {
|
||||||
|
parentItem = static_cast<SpaceTreeItem *>(parent.internalPointer());
|
||||||
|
}
|
||||||
|
|
||||||
|
return parentItem->childCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpaceChildrenModel::columnCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> SpaceChildrenModel::roleNames() const
|
||||||
|
{
|
||||||
|
QHash<int, QByteArray> roles;
|
||||||
|
|
||||||
|
roles[DisplayNameRole] = "displayName";
|
||||||
|
roles[AvatarUrlRole] = "avatarUrl";
|
||||||
|
roles[TopicRole] = "topic";
|
||||||
|
roles[RoomIDRole] = "roomId";
|
||||||
|
roles[MemberCountRole] = "memberCount";
|
||||||
|
roles[AllowGuestsRole] = "allowGuests";
|
||||||
|
roles[WorldReadableRole] = "worldReadable";
|
||||||
|
roles[IsJoinedRole] = "isJoined";
|
||||||
|
roles[AliasRole] = "alias";
|
||||||
|
roles[IsSpaceRole] = "isSpace";
|
||||||
|
roles[CanAddChildrenRole] = "canAddChildren";
|
||||||
|
roles[ParentDisplayNameRole] = "parentDisplayName";
|
||||||
|
roles[CanSetParentRole] = "canSetParent";
|
||||||
|
roles[IsDeclaredParentRole] = "isDeclaredParent";
|
||||||
|
roles[CanRemove] = "canRemove";
|
||||||
|
roles[ParentRoomRole] = "parentRoom";
|
||||||
|
|
||||||
|
return roles;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceChildrenModel::isRoomReplaced(const QString &roomId) const
|
||||||
|
{
|
||||||
|
return m_replacedRooms.contains(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceChildrenModel::addPendingChild(const QString &childName)
|
||||||
|
{
|
||||||
|
m_pendingChildren += childName;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_spacechildrenmodel.cpp"
|
||||||
144
src/models/spacechildrenmodel.h
Normal file
144
src/models/spacechildrenmodel.h
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractItemModel>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <Quotient/csapi/space_hierarchy.h>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#include "neochatroom.h"
|
||||||
|
#include "spacetreeitem.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SpaceChildrenModel
|
||||||
|
*
|
||||||
|
* Create a model that contains a list of the child rooms for any given space id.
|
||||||
|
*/
|
||||||
|
class SpaceChildrenModel : public QAbstractItemModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The current space that the hierarchy is being generated for.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(NeoChatRoom *space READ space WRITE setSpace NOTIFY spaceChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the model is loading the initial set of children.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Roles {
|
||||||
|
DisplayNameRole = Qt::DisplayRole,
|
||||||
|
AvatarUrlRole,
|
||||||
|
TopicRole,
|
||||||
|
RoomIDRole,
|
||||||
|
AliasRole,
|
||||||
|
MemberCountRole,
|
||||||
|
AllowGuestsRole,
|
||||||
|
WorldReadableRole,
|
||||||
|
IsJoinedRole,
|
||||||
|
IsSpaceRole,
|
||||||
|
CanAddChildrenRole,
|
||||||
|
ParentDisplayNameRole,
|
||||||
|
CanSetParentRole,
|
||||||
|
IsDeclaredParentRole,
|
||||||
|
CanRemove,
|
||||||
|
ParentRoomRole,
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit SpaceChildrenModel(QObject *parent = nullptr);
|
||||||
|
~SpaceChildrenModel();
|
||||||
|
|
||||||
|
NeoChatRoom *space() const;
|
||||||
|
void setSpace(NeoChatRoom *space);
|
||||||
|
|
||||||
|
bool loading() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
|
QVariant data(const QModelIndex &index, int role = DisplayNameRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the index of the item in the model specified by the given row, column and parent index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::index
|
||||||
|
*/
|
||||||
|
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the parent of the model item with the given index.
|
||||||
|
*
|
||||||
|
* If the item has no parent, an invalid QModelIndex is returned.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::parent
|
||||||
|
*/
|
||||||
|
QModelIndex parent(const QModelIndex &index) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of columns in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::columnCount
|
||||||
|
*/
|
||||||
|
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
|
QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the room has been replaced.
|
||||||
|
*
|
||||||
|
* @note This information is only available if the local user is either a member
|
||||||
|
* of the replaced room or is a member of the successor room as currently
|
||||||
|
* there is no other way to obtain the required information.
|
||||||
|
*/
|
||||||
|
bool isRoomReplaced(const QString &roomId) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add the name of new child room that is expected to be added soon.
|
||||||
|
*
|
||||||
|
* A pending child is one where Quotient::Connection::createRoom has been called
|
||||||
|
* but the room hasn't synced with the server yet. This list is used to check
|
||||||
|
* whether a new room loading should trigger a refresh of the model, as we only
|
||||||
|
* want to trigger a refresh if the loading room is part of this space.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void addPendingChild(const QString &childName);
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void spaceChanged();
|
||||||
|
void loadingChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
NeoChatRoom *m_space = nullptr;
|
||||||
|
SpaceTreeItem *m_rootItem;
|
||||||
|
|
||||||
|
bool m_loading = false;
|
||||||
|
QList<QPointer<Quotient::GetSpaceHierarchyJob>> m_currentJobs;
|
||||||
|
QList<QString> m_pendingChildren;
|
||||||
|
|
||||||
|
QList<QString> m_replacedRooms;
|
||||||
|
|
||||||
|
SpaceTreeItem *getItem(const QModelIndex &index) const;
|
||||||
|
|
||||||
|
void refreshModel();
|
||||||
|
void insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent = QModelIndex());
|
||||||
|
};
|
||||||
46
src/models/spacechildsortfiltermodel.cpp
Normal file
46
src/models/spacechildsortfiltermodel.cpp
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "spacechildsortfiltermodel.h"
|
||||||
|
|
||||||
|
#include "spacechildrenmodel.h"
|
||||||
|
|
||||||
|
SpaceChildSortFilterModel::SpaceChildSortFilterModel(QObject *parent)
|
||||||
|
: QSortFilterProxyModel(parent)
|
||||||
|
{
|
||||||
|
setRecursiveFilteringEnabled(true);
|
||||||
|
sort(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpaceChildSortFilterModel::setFilterText(const QString &filterText)
|
||||||
|
{
|
||||||
|
m_filterText = filterText;
|
||||||
|
Q_EMIT filterTextChanged();
|
||||||
|
invalidateFilter();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SpaceChildSortFilterModel::filterText() const
|
||||||
|
{
|
||||||
|
return m_filterText;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceChildSortFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||||
|
{
|
||||||
|
if (!source_left.data(SpaceChildrenModel::IsSpaceRole).toBool() && source_right.data(SpaceChildrenModel::IsSpaceRole).toBool()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceChildSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||||
|
{
|
||||||
|
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||||
|
if (auto sourceModel = static_cast<SpaceChildrenModel *>(this->sourceModel())) {
|
||||||
|
bool isReplaced = sourceModel->isRoomReplaced(index.data(SpaceChildrenModel::RoomIDRole).toString());
|
||||||
|
bool acceptRoom = index.data(SpaceChildrenModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive);
|
||||||
|
return !isReplaced && acceptRoom;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_spacechildsortfiltermodel.cpp"
|
||||||
54
src/models/spacechildsortfiltermodel.h
Normal file
54
src/models/spacechildsortfiltermodel.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QQmlEngine>
|
||||||
|
#include <QSortFilterProxyModel>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SpaceChildSortFilterModel
|
||||||
|
*
|
||||||
|
* This class creates a custom QSortFilterProxyModel for filtering and sorting spaces
|
||||||
|
* in a SpaceChildrenModel.
|
||||||
|
*
|
||||||
|
* @sa SpaceChildrenModel
|
||||||
|
*/
|
||||||
|
class SpaceChildSortFilterModel : public QSortFilterProxyModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The text to use to filter room names.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||||
|
|
||||||
|
public:
|
||||||
|
SpaceChildSortFilterModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
|
void setFilterText(const QString &filterText);
|
||||||
|
[[nodiscard]] QString filterText() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/**
|
||||||
|
* @brief Returns true if the value of source_left is less than source_right.
|
||||||
|
*
|
||||||
|
* @sa QSortFilterProxyModel::lessThan
|
||||||
|
*/
|
||||||
|
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Custom filter function checking if an event type has been filtered out.
|
||||||
|
*
|
||||||
|
* The filter rejects a row if the room is known been replaced or if a search
|
||||||
|
* string is set it will only return rooms that match.
|
||||||
|
*/
|
||||||
|
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void filterTextChanged();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_filterText;
|
||||||
|
};
|
||||||
140
src/models/spacetreeitem.cpp
Normal file
140
src/models/spacetreeitem.cpp
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "spacetreeitem.h"
|
||||||
|
|
||||||
|
#include "controller.h"
|
||||||
|
|
||||||
|
SpaceTreeItem::SpaceTreeItem(SpaceTreeItem *parent,
|
||||||
|
const QString &id,
|
||||||
|
const QString &name,
|
||||||
|
const QString &canonicalAlias,
|
||||||
|
const QString &topic,
|
||||||
|
int memberCount,
|
||||||
|
const QUrl &avatarUrl,
|
||||||
|
bool allowGuests,
|
||||||
|
bool worldReadable,
|
||||||
|
bool isSpace)
|
||||||
|
: m_parentItem(parent)
|
||||||
|
, m_id(id)
|
||||||
|
, m_name(name)
|
||||||
|
, m_canonicalAlias(canonicalAlias)
|
||||||
|
, m_topic(topic)
|
||||||
|
, m_memberCount(memberCount)
|
||||||
|
, m_avatarUrl(avatarUrl)
|
||||||
|
, m_allowGuests(allowGuests)
|
||||||
|
, m_worldReadable(worldReadable)
|
||||||
|
, m_isSpace(isSpace)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem::~SpaceTreeItem()
|
||||||
|
{
|
||||||
|
qDeleteAll(m_children);
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem *SpaceTreeItem::child(int number)
|
||||||
|
{
|
||||||
|
if (number < 0 || number >= m_children.size()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return m_children[number];
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpaceTreeItem::childCount() const
|
||||||
|
{
|
||||||
|
return m_children.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceTreeItem::insertChild(int row, SpaceTreeItem *newChild)
|
||||||
|
{
|
||||||
|
if (row < 0 || row > m_children.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_children.insert(row, newChild);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceTreeItem::removeChild(int row)
|
||||||
|
{
|
||||||
|
if (row < 0 || row >= m_children.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
delete m_children.takeAt(row);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpaceTreeItem::row() const
|
||||||
|
{
|
||||||
|
if (m_parentItem) {
|
||||||
|
return m_parentItem->m_children.indexOf(const_cast<SpaceTreeItem *>(this));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpaceTreeItem *SpaceTreeItem::parentItem()
|
||||||
|
{
|
||||||
|
return m_parentItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SpaceTreeItem::id() const
|
||||||
|
{
|
||||||
|
return m_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SpaceTreeItem::name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SpaceTreeItem::canonicalAlias() const
|
||||||
|
{
|
||||||
|
return m_canonicalAlias;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SpaceTreeItem::topic() const
|
||||||
|
{
|
||||||
|
return m_topic;
|
||||||
|
}
|
||||||
|
|
||||||
|
int SpaceTreeItem::memberCount() const
|
||||||
|
{
|
||||||
|
return m_memberCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrl SpaceTreeItem::avatarUrl() const
|
||||||
|
{
|
||||||
|
if (m_avatarUrl.isEmpty() || m_avatarUrl.scheme() != QLatin1String("mxc")) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
auto connection = Controller::instance().activeConnection();
|
||||||
|
auto url = connection->makeMediaUrl(m_avatarUrl);
|
||||||
|
if (url.scheme() == QLatin1String("mxc")) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceTreeItem::allowGuests() const
|
||||||
|
{
|
||||||
|
return m_allowGuests;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceTreeItem::worldReadable() const
|
||||||
|
{
|
||||||
|
return m_worldReadable;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceTreeItem::isJoined() const
|
||||||
|
{
|
||||||
|
auto connection = Controller::instance().activeConnection();
|
||||||
|
if (!connection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return connection->room(id(), Quotient::JoinState::Join) != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SpaceTreeItem::isSpace() const
|
||||||
|
{
|
||||||
|
return m_isSpace;
|
||||||
|
}
|
||||||
136
src/models/spacetreeitem.h
Normal file
136
src/models/spacetreeitem.h
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include <Quotient/csapi/space_hierarchy.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class SpaceTreeItem
|
||||||
|
*
|
||||||
|
* This class defines an item in the space tree hierarchy model.
|
||||||
|
*
|
||||||
|
* @note This is separate from Quotient::Room and NeoChatRoom because we don't have
|
||||||
|
* full room information for any room/space the user hasn't joined and we
|
||||||
|
* don't want to create one for ever possible child in a space as that would
|
||||||
|
* be expensive.
|
||||||
|
*
|
||||||
|
* @sa Quotient::Room, NeoChatRoom
|
||||||
|
*/
|
||||||
|
class SpaceTreeItem
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit SpaceTreeItem(SpaceTreeItem *parent = nullptr,
|
||||||
|
const QString &id = {},
|
||||||
|
const QString &name = {},
|
||||||
|
const QString &canonicalAlias = {},
|
||||||
|
const QString &topic = {},
|
||||||
|
int memberCount = {},
|
||||||
|
const QUrl &avatarUrl = {},
|
||||||
|
bool allowGuests = {},
|
||||||
|
bool worldReadable = {},
|
||||||
|
bool isSpace = {});
|
||||||
|
~SpaceTreeItem();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the child at the given row number.
|
||||||
|
*
|
||||||
|
* Nullptr is returned if there is no child at the given row number.
|
||||||
|
*/
|
||||||
|
SpaceTreeItem *child(int number);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of children this item has.
|
||||||
|
*/
|
||||||
|
int childCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Insert the given child at the given row number.
|
||||||
|
*/
|
||||||
|
bool insertChild(int row, SpaceTreeItem *newChild);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove the child at the given row number.
|
||||||
|
*
|
||||||
|
* @return True if a child was removed, false if the given row isn't valid.
|
||||||
|
*/
|
||||||
|
bool removeChild(int row);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return this item's parent.
|
||||||
|
*/
|
||||||
|
SpaceTreeItem *parentItem();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the row number for this child relative to the parent.
|
||||||
|
*
|
||||||
|
* @return The row value if the child has a parent, 0 otherwise.
|
||||||
|
*/
|
||||||
|
int row() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The ID of the room.
|
||||||
|
*/
|
||||||
|
QString id() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The name of the room, if any.
|
||||||
|
*/
|
||||||
|
QString name() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The canonical alias of the room, if any.
|
||||||
|
*/
|
||||||
|
QString canonicalAlias() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The topic of the room, if any.
|
||||||
|
*/
|
||||||
|
QString topic() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The number of members joined to the room.
|
||||||
|
*/
|
||||||
|
int memberCount() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The URL for the room's avatar, if one is set.
|
||||||
|
*
|
||||||
|
* @return A CS API QUrl.
|
||||||
|
*/
|
||||||
|
QUrl avatarUrl() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether guest users may join the room and participate in it.
|
||||||
|
*
|
||||||
|
* If they can, they will be subject to ordinary power level rules like any other users.
|
||||||
|
*/
|
||||||
|
bool allowGuests() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the room may be viewed by guest users without joining.
|
||||||
|
*/
|
||||||
|
bool worldReadable() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the local user is a member of the rooom.
|
||||||
|
*/
|
||||||
|
bool isJoined() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the room is a space.
|
||||||
|
*/
|
||||||
|
bool isSpace() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<SpaceTreeItem *> m_children;
|
||||||
|
SpaceTreeItem *m_parentItem;
|
||||||
|
|
||||||
|
QString m_id;
|
||||||
|
QString m_name;
|
||||||
|
QString m_canonicalAlias;
|
||||||
|
QString m_topic;
|
||||||
|
int m_memberCount;
|
||||||
|
QUrl m_avatarUrl;
|
||||||
|
bool m_allowGuests;
|
||||||
|
bool m_worldReadable;
|
||||||
|
bool m_isSpace;
|
||||||
|
};
|
||||||
@@ -156,9 +156,30 @@ void NeoChatConnection::deactivateAccount(const QString &password)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatConnection::createRoom(const QString &name, const QString &topic)
|
void NeoChatConnection::createRoom(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
|
||||||
{
|
{
|
||||||
const auto job = Connection::createRoom(Connection::PublishRoom, {}, name, topic, {});
|
QVector<CreateRoomJob::StateEvent> initialStateEvents;
|
||||||
|
if (!parent.isEmpty()) {
|
||||||
|
initialStateEvents.append(CreateRoomJob::StateEvent{
|
||||||
|
"m.space.parent"_ls,
|
||||||
|
QJsonObject{
|
||||||
|
{"canonical"_ls, true},
|
||||||
|
{"via"_ls, QJsonArray{domain()}},
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto job = Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents);
|
||||||
|
if (!parent.isEmpty()) {
|
||||||
|
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||||
|
if (setChildParent) {
|
||||||
|
if (auto parentRoom = room(parent)) {
|
||||||
|
parentRoom->setState(QLatin1String("m.space.child"), job->roomId(), QJsonObject{{QLatin1String("via"), QJsonArray{domain()}}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||||
Q_EMIT Controller::instance().errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
Q_EMIT Controller::instance().errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||||
});
|
});
|
||||||
@@ -167,9 +188,30 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatConnection::createSpace(const QString &name, const QString &topic)
|
void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
|
||||||
{
|
{
|
||||||
const auto job = Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, {}, {}, QJsonObject{{"type"_ls, "m.space"_ls}});
|
QVector<CreateRoomJob::StateEvent> initialStateEvents;
|
||||||
|
if (!parent.isEmpty()) {
|
||||||
|
initialStateEvents.append(CreateRoomJob::StateEvent{
|
||||||
|
"m.space.parent"_ls,
|
||||||
|
QJsonObject{
|
||||||
|
{"canonical"_ls, true},
|
||||||
|
{"via"_ls, QJsonArray{domain()}},
|
||||||
|
},
|
||||||
|
parent,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto job = Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_ls, "m.space"_ls}});
|
||||||
|
if (!parent.isEmpty()) {
|
||||||
|
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||||
|
if (setChildParent) {
|
||||||
|
if (auto parentRoom = room(parent)) {
|
||||||
|
parentRoom->setState(QLatin1String("m.space.child"), job->roomId(), QJsonObject{{QLatin1String("via"), QJsonArray{domain()}}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||||
Q_EMIT Controller::instance().errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
Q_EMIT Controller::instance().errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -54,12 +54,12 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Create new room for a group chat.
|
* @brief Create new room for a group chat.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic);
|
Q_INVOKABLE void createRoom(const QString &name, const QString &topic, const QString &parent = {}, bool setChildParent = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create new space.
|
* @brief Create new space.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void createSpace(const QString &name, const QString &topic);
|
Q_INVOKABLE void createSpace(const QString &name, const QString &topic, const QString &parent = {}, bool setChildParent = false);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void labelChanged();
|
void labelChanged();
|
||||||
|
|||||||
@@ -47,6 +47,7 @@
|
|||||||
#include "filetransferpseudojob.h"
|
#include "filetransferpseudojob.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
|
#include "roomlastmessageprovider.h"
|
||||||
#include "texthandler.h"
|
#include "texthandler.h"
|
||||||
#include "urlhelper.h"
|
#include "urlhelper.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
@@ -72,12 +73,10 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
|
|
||||||
connect(this, &Room::aboutToAddHistoricalMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
|
connect(this, &Room::aboutToAddHistoricalMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
|
||||||
|
|
||||||
// Load cached event if available.
|
const auto &roomLastMessageProvider = RoomLastMessageProvider::self();
|
||||||
KConfig dataResource("data"_ls, KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
|
||||||
KConfigGroup eventCacheGroup(&dataResource, "EventCache"_ls);
|
|
||||||
|
|
||||||
if (eventCacheGroup.hasKey(id())) {
|
if (roomLastMessageProvider.hasKey(id())) {
|
||||||
auto eventJson = QJsonDocument::fromJson(eventCacheGroup.readEntry(id(), QByteArray())).object();
|
auto eventJson = QJsonDocument::fromJson(roomLastMessageProvider.read(id())).object();
|
||||||
if (!eventJson.isEmpty()) {
|
if (!eventJson.isEmpty()) {
|
||||||
auto event = loadEvent<RoomEvent>(eventJson);
|
auto event = loadEvent<RoomEvent>(eventJson);
|
||||||
|
|
||||||
@@ -309,11 +308,10 @@ void NeoChatRoom::cacheLastEvent()
|
|||||||
{
|
{
|
||||||
auto event = lastEvent();
|
auto event = lastEvent();
|
||||||
if (event != nullptr) {
|
if (event != nullptr) {
|
||||||
KConfig dataResource("data"_ls, KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
auto &roomLastMessageProvider = RoomLastMessageProvider::self();
|
||||||
KConfigGroup eventCacheGroup(&dataResource, "EventCache"_ls);
|
|
||||||
|
|
||||||
auto eventJson = QJsonDocument(event->fullJson()).toJson();
|
auto eventJson = QJsonDocument(event->fullJson()).toJson();
|
||||||
eventCacheGroup.writeEntry(id(), eventJson);
|
roomLastMessageProvider.write(id(), eventJson);
|
||||||
|
|
||||||
auto uniqueEvent = loadEvent<RoomEvent>(event->fullJson());
|
auto uniqueEvent = loadEvent<RoomEvent>(event->fullJson());
|
||||||
|
|
||||||
@@ -1111,6 +1109,44 @@ bool NeoChatRoom::isSpace()
|
|||||||
return creationEvent->roomType() == RoomType::Space;
|
return creationEvent->roomType() == RoomType::Space;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::addChild(const QString &childId, bool setChildParent)
|
||||||
|
{
|
||||||
|
if (!isSpace()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!canSendEvent("m.space.child"_ls)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState("m.space.child"_ls, childId, QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}});
|
||||||
|
|
||||||
|
if (setChildParent) {
|
||||||
|
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||||
|
if (child->canSendState("m.space.parent"_ls)) {
|
||||||
|
child->setState("m.space.parent"_ls, id(), QJsonObject{{"canonical"_ls, true}, {"via"_ls, QJsonArray{connection()->domain()}}});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::removeChild(const QString &childId, bool unsetChildParent)
|
||||||
|
{
|
||||||
|
if (!isSpace()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!canSendEvent("m.space.child"_ls)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState("m.space.child"_ls, childId, {});
|
||||||
|
|
||||||
|
if (unsetChildParent) {
|
||||||
|
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||||
|
if (child->canSendState("m.space.parent"_ls) && child->currentState().contains("m.space.parent"_ls, id())) {
|
||||||
|
child->setState("m.space.parent"_ls, id(), {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PushNotificationState::State NeoChatRoom::pushNotificationState() const
|
PushNotificationState::State NeoChatRoom::pushNotificationState() const
|
||||||
{
|
{
|
||||||
return m_currentPushNotificationState;
|
return m_currentPushNotificationState;
|
||||||
@@ -1134,9 +1170,9 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
|||||||
m_pushNotificationStateUpdating = true;
|
m_pushNotificationStateUpdating = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* First remove any exisiting room rules of the wrong type.
|
* First remove any existing room rules of the wrong type.
|
||||||
* Note to prevent race conditions any rule that is going ot be overridden later is not removed.
|
* Note to prevent race conditions any rule that is going ot be overridden later is not removed.
|
||||||
* If the default push notification state is chosen any exisiting rule needs to be removed.
|
* If the default push notification state is chosen any existing rule needs to be removed.
|
||||||
*/
|
*/
|
||||||
QJsonObject accountData = connection()->accountDataJson("m.push_rules"_ls);
|
QJsonObject accountData = connection()->accountDataJson("m.push_rules"_ls);
|
||||||
|
|
||||||
|
|||||||
@@ -589,6 +589,10 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] bool isSpace();
|
[[nodiscard]] bool isSpace();
|
||||||
|
|
||||||
|
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false);
|
||||||
|
|
||||||
|
Q_INVOKABLE void removeChild(const QString &childId, bool unsetChildParent = false);
|
||||||
|
|
||||||
bool isInvite() const;
|
bool isInvite() const;
|
||||||
|
|
||||||
Q_INVOKABLE void clearInvitationNotification();
|
Q_INVOKABLE void clearInvitationNotification();
|
||||||
|
|||||||
@@ -59,8 +59,9 @@ class NotificationsManager : public QObject
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
static NotificationsManager &instance();
|
static NotificationsManager &instance();
|
||||||
static NotificationsManager *create(QQmlEngine *, QJSEngine *)
|
static NotificationsManager *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
|
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||||
return &instance();
|
return &instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ QQC2.Control {
|
|||||||
*/
|
*/
|
||||||
property var author
|
property var author
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the author should be shown.
|
||||||
|
*/
|
||||||
|
required property bool showAuthor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The timestamp of the message.
|
* @brief The timestamp of the message.
|
||||||
*/
|
*/
|
||||||
@@ -133,6 +138,7 @@ QQC2.Control {
|
|||||||
contentItem: ColumnLayout {
|
contentItem: ColumnLayout {
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.maximumWidth: root.maxContentWidth
|
Layout.maximumWidth: root.maxContentWidth
|
||||||
|
visible: root.showAuthor
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: root.author.displayName
|
text: root.author.displayName
|
||||||
|
|||||||
@@ -2,44 +2,225 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.formcard as FormCard
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
import org.kde.kirigamiaddons.labs.components as Components
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
FormCard.FormCardPage {
|
FormCard.FormCardPage {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
title: i18nc("@title", "Create a Room")
|
property string parentId: ""
|
||||||
|
|
||||||
|
property bool isSpace: false
|
||||||
|
|
||||||
|
property bool showChildType: false
|
||||||
|
|
||||||
|
property bool showCreateChoice: false
|
||||||
|
|
||||||
required property NeoChatConnection connection
|
required property NeoChatConnection connection
|
||||||
|
|
||||||
|
signal addChild(string childId, bool setChildParent)
|
||||||
|
signal newChild(string childName)
|
||||||
|
|
||||||
|
title: isSpace ? i18nc("@title", "Create a Space") : i18nc("@title", "Create a Room")
|
||||||
|
|
||||||
Component.onCompleted: roomNameField.forceActiveFocus()
|
Component.onCompleted: roomNameField.forceActiveFocus()
|
||||||
|
|
||||||
FormCard.FormHeader {
|
FormCard.FormHeader {
|
||||||
title: i18nc("@title", "Room Information")
|
title: root.isSpace ? i18n("New Space Information") : i18n("New Room Information")
|
||||||
}
|
}
|
||||||
FormCard.FormCard {
|
FormCard.FormCard {
|
||||||
|
FormCard.FormComboBoxDelegate {
|
||||||
|
id: roomTypeCombo
|
||||||
|
property bool isInitialising: true
|
||||||
|
|
||||||
|
visible: root.showChildType
|
||||||
|
|
||||||
|
text: i18n("Select type")
|
||||||
|
model: ListModel {
|
||||||
|
id: roomTypeModel
|
||||||
|
}
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "isSpace"
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
currentIndex = indexOfValue(root.isSpace)
|
||||||
|
roomTypeModel.append({"text": i18n("Room"), "isSpace": false});
|
||||||
|
roomTypeModel.append({"text": i18n("Space"), "isSpace": true});
|
||||||
|
roomTypeCombo.currentIndex = 0
|
||||||
|
roomTypeCombo.isInitialising = false
|
||||||
|
}
|
||||||
|
onCurrentValueChanged: {
|
||||||
|
if (!isInitialising) {
|
||||||
|
root.isSpace = currentValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
FormCard.FormTextFieldDelegate {
|
FormCard.FormTextFieldDelegate {
|
||||||
id: roomNameField
|
id: roomNameField
|
||||||
label: i18n("Room name:")
|
label: i18n("Name:")
|
||||||
onAccepted: if (roomNameField.text.length > 0) roomTopicField.forceActiveFocus();
|
onAccepted: if (roomNameField.text.length > 0) roomTopicField.forceActiveFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
FormCard.FormTextFieldDelegate {
|
FormCard.FormTextFieldDelegate {
|
||||||
id: roomTopicField
|
id: roomTopicField
|
||||||
label: i18n("Room topic:")
|
label: i18n("Topic:")
|
||||||
onAccepted: ok.clicked()
|
onAccepted: ok.clicked()
|
||||||
}
|
}
|
||||||
|
FormCard.FormCheckDelegate {
|
||||||
|
id: newOfficialCheck
|
||||||
|
visible: root.parentId.length > 0
|
||||||
|
text: i18n("Make this parent official")
|
||||||
|
checked: true
|
||||||
|
}
|
||||||
FormCard.FormButtonDelegate {
|
FormCard.FormButtonDelegate {
|
||||||
id: ok
|
id: ok
|
||||||
text: i18nc("@action:button", "Ok")
|
text: i18nc("@action:button", "Ok")
|
||||||
enabled: roomNameField.text.length > 0
|
enabled: roomNameField.text.length > 0
|
||||||
onClicked: {
|
onClicked: {
|
||||||
root.connection.createRoom(roomNameField.text, roomTopicField.text);
|
if (root.isSpace) {
|
||||||
|
root.connection.createSpace(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
|
||||||
|
} else {
|
||||||
|
root.connection.createRoom(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
|
||||||
|
}
|
||||||
|
root.newChild(roomNameField.text)
|
||||||
root.closeDialog()
|
root.closeDialog()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
FormCard.FormHeader {
|
||||||
|
visible: root.showChildType
|
||||||
|
title: i18n("Select Existing Room")
|
||||||
|
}
|
||||||
|
FormCard.FormCard {
|
||||||
|
visible: root.showChildType
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
visible: !chosenRoomDelegate.visible
|
||||||
|
text: i18nc("@action:button", "Pick room")
|
||||||
|
onClicked: {
|
||||||
|
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||||
|
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||||
|
chosenRoomDelegate.roomId = roomId;
|
||||||
|
chosenRoomDelegate.displayName = displayName;
|
||||||
|
chosenRoomDelegate.avatarUrl = avatarUrl;
|
||||||
|
chosenRoomDelegate.alias = alias;
|
||||||
|
chosenRoomDelegate.topic = topic;
|
||||||
|
chosenRoomDelegate.memberCount = memberCount;
|
||||||
|
chosenRoomDelegate.isJoined = isJoined;
|
||||||
|
chosenRoomDelegate.visible = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormCard.AbstractFormDelegate {
|
||||||
|
id: chosenRoomDelegate
|
||||||
|
property string roomId
|
||||||
|
property string displayName
|
||||||
|
property url avatarUrl
|
||||||
|
property string alias
|
||||||
|
property string topic
|
||||||
|
property int memberCount
|
||||||
|
property bool isJoined
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
Components.Avatar {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||||
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
|
||||||
|
source: chosenRoomDelegate.avatarUrl
|
||||||
|
name: chosenRoomDelegate.displayName
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Kirigami.Heading {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
level: 4
|
||||||
|
text: chosenRoomDelegate.displayName
|
||||||
|
font.bold: true
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
visible: chosenRoomDelegate.isJoined
|
||||||
|
text: i18n("Joined")
|
||||||
|
color: Kirigami.Theme.linkColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: text
|
||||||
|
text: chosenRoomDelegate.topic ? chosenRoomDelegate.topic.replace(/(\r\n\t|\n|\r\t)/gm," ") : ""
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Kirigami.Icon {
|
||||||
|
source: "user"
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.small
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.small
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
text: chosenRoomDelegate.memberCount + " " + (chosenRoomDelegate.alias ?? chosenRoomDelegate.roomId)
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
elide: Text.ElideRight
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||||
|
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||||
|
chosenRoomDelegate.roomId = roomId;
|
||||||
|
chosenRoomDelegate.displayName = displayName;
|
||||||
|
chosenRoomDelegate.avatarUrl = avatarUrl;
|
||||||
|
chosenRoomDelegate.alias = alias;
|
||||||
|
chosenRoomDelegate.topic = topic;
|
||||||
|
chosenRoomDelegate.memberCount = memberCount;
|
||||||
|
chosenRoomDelegate.isJoined = isJoined;
|
||||||
|
chosenRoomDelegate.visible = true;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormCard.FormCheckDelegate {
|
||||||
|
id: existingOfficialCheck
|
||||||
|
visible: root.parentId.length > 0
|
||||||
|
text: i18n("Make this parent official")
|
||||||
|
description: enabled ? i18n("You have the required privilege level in the child to set this state") : i18n("You do not have a high enough privilege level in the child to set this state")
|
||||||
|
checked: enabled
|
||||||
|
|
||||||
|
enabled: {
|
||||||
|
if (chosenRoomDelegate.visible) {
|
||||||
|
let room = root.connection.room(chosenRoomDelegate.roomId);
|
||||||
|
if (room) {
|
||||||
|
if (room.canSendState("m.space.parent")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FormCard.FormButtonDelegate {
|
||||||
|
text: i18nc("@action:button", "Ok")
|
||||||
|
enabled: chosenRoomDelegate.visible
|
||||||
|
onClicked: {
|
||||||
|
root.addChild(chosenRoomDelegate.roomId, existingOfficialCheck.checked);
|
||||||
|
root.closeDialog();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import QtQuick.Layouts
|
|
||||||
import org.kde.kirigami as Kirigami
|
|
||||||
|
|
||||||
import org.kde.neochat
|
|
||||||
import org.kde.kirigamiaddons.formcard as FormCard
|
|
||||||
|
|
||||||
FormCard.FormCardPage {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
required property NeoChatConnection connection
|
|
||||||
|
|
||||||
title: i18n("Create a Space")
|
|
||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
|
||||||
|
|
||||||
FormCard.FormHeader {
|
|
||||||
title: i18nc("@title", "Create a Space")
|
|
||||||
}
|
|
||||||
FormCard.FormCard {
|
|
||||||
FormCard.FormTextFieldDelegate {
|
|
||||||
id: nameDelegate
|
|
||||||
label: i18n("Space name")
|
|
||||||
}
|
|
||||||
FormCard.FormTextFieldDelegate {
|
|
||||||
id: topicDelegate
|
|
||||||
label: i18n("Space topic (optional)")
|
|
||||||
}
|
|
||||||
FormCard.FormButtonDelegate {
|
|
||||||
text: i18n("Create space")
|
|
||||||
onClicked: {
|
|
||||||
root.connection.createSpace(nameDelegate.text, topicDelegate.text)
|
|
||||||
root.close()
|
|
||||||
root.destroy()
|
|
||||||
}
|
|
||||||
enabled: nameDelegate.text.length > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -5,6 +5,8 @@ import QtQuick
|
|||||||
import QtQuick.Controls as QQC2
|
import QtQuick.Controls as QQC2
|
||||||
import QtQml
|
import QtQml
|
||||||
|
|
||||||
|
import com.github.quotient_im.libquotient
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ RowLayout {
|
|||||||
text: i18n("Create a Space")
|
text: i18n("Create a Space")
|
||||||
icon.name: "list-add"
|
icon.name: "list-add"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateSpaceDialog.qml", {connection: root.connection}, {title: i18nc("@title", "Create a Space")})
|
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {connection: root.connection, isSpace: true, title: i18nc("@title", "Create a Space")}, {title: i18nc("@title", "Create a Space")})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ import org.kde.neochat
|
|||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: root
|
id: root
|
||||||
|
/**
|
||||||
|
* @brief The current room that user is viewing.
|
||||||
|
*/
|
||||||
|
required property NeoChatRoom room
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
@@ -26,8 +30,8 @@ ColumnLayout {
|
|||||||
Layout.preferredWidth: Kirigami.Units.iconSizes.large
|
Layout.preferredWidth: Kirigami.Units.iconSizes.large
|
||||||
Layout.preferredHeight: Kirigami.Units.iconSizes.large
|
Layout.preferredHeight: Kirigami.Units.iconSizes.large
|
||||||
|
|
||||||
name: room ? room.displayName : ""
|
name: root.room ? root.room.displayName : ""
|
||||||
source: room && room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
|
source: root.room && root.room.avatarMediaId ? ("image://mxc/" + root.room.avatarMediaId) : ""
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
visible: room.usesEncryption
|
visible: room.usesEncryption
|
||||||
@@ -58,7 +62,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: room ? room.displayName : i18n("No name")
|
text: root.room ? root.room.displayName : i18n("No name")
|
||||||
textFormat: Text.PlainText
|
textFormat: Text.PlainText
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
}
|
}
|
||||||
@@ -67,8 +71,8 @@ ColumnLayout {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
font: Kirigami.Theme.smallFont
|
font: Kirigami.Theme.smallFont
|
||||||
textFormat: TextEdit.PlainText
|
textFormat: TextEdit.PlainText
|
||||||
visible: room && room.canonicalAlias
|
visible: root.room && root.room.canonicalAlias
|
||||||
text: room && room.canonicalAlias ? room.canonicalAlias : ""
|
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,7 +82,7 @@ ColumnLayout {
|
|||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
text: root.room && root.room.topic ? root.room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||||
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
||||||
textFormat: TextEdit.MarkdownText
|
textFormat: TextEdit.MarkdownText
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import QtQuick.Controls as QQC2
|
|||||||
import QtQuick.Layouts
|
import QtQuick.Layouts
|
||||||
import QtQml
|
import QtQml
|
||||||
|
|
||||||
|
import com.github.quotient_im.libquotient
|
||||||
|
|
||||||
import org.kde.kirigami as Kirigami
|
import org.kde.kirigami as Kirigami
|
||||||
import org.kde.kirigamiaddons.delegates as Delegates
|
import org.kde.kirigamiaddons.delegates as Delegates
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|||||||
@@ -24,5 +24,6 @@ Column {
|
|||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
text: root.text
|
text: root.text
|
||||||
textFormat: Text.MarkdownText
|
textFormat: Text.MarkdownText
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,7 @@ TimelineDelegate {
|
|||||||
]
|
]
|
||||||
|
|
||||||
author: root.author
|
author: root.author
|
||||||
|
showAuthor: root.showAuthor || root.alwaysShowAuthor
|
||||||
time: root.time
|
time: root.time
|
||||||
timeString: root.timeString
|
timeString: root.timeString
|
||||||
|
|
||||||
|
|||||||
@@ -28,39 +28,45 @@ Flow {
|
|||||||
id: reactionRepeater
|
id: reactionRepeater
|
||||||
|
|
||||||
delegate: QQC2.AbstractButton {
|
delegate: QQC2.AbstractButton {
|
||||||
width: Math.max(reactionTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 4, height)
|
id: reactionDelegate
|
||||||
|
|
||||||
|
required property string textContent
|
||||||
|
required property string reaction
|
||||||
|
required property string toolTip
|
||||||
|
required property bool hasLocalUser
|
||||||
|
|
||||||
|
width: Math.max(contentItem.implicitWidth + leftPadding + rightPadding, height)
|
||||||
|
height: Math.round(Kirigami.Units.gridUnit * 1.5)
|
||||||
|
|
||||||
contentItem: QQC2.Label {
|
contentItem: QQC2.Label {
|
||||||
id: reactionLabel
|
id: reactionLabel
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
text: model.text
|
text: reactionDelegate.textContent
|
||||||
|
background: null
|
||||||
TextMetrics {
|
wrapMode: TextEdit.NoWrap
|
||||||
id: reactionTextMetrics
|
textFormat: Text.RichText
|
||||||
text: reactionLabel.text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
padding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
background: Kirigami.ShadowedRectangle {
|
||||||
color: model.hasLocalUser ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
color: reactionDelegate.hasLocalUser ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
shadow {
|
shadow {
|
||||||
size: Kirigami.Units.smallSpacing
|
size: Kirigami.Units.smallSpacing
|
||||||
color: !model.hasLocalUser ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
color: !reactionDelegate.hasLocalUser ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: reactionClicked(model.reaction)
|
onClicked: reactionClicked(reactionDelegate.reaction)
|
||||||
|
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.text: model.toolTip
|
QQC2.ToolTip.text: reactionDelegate.toolTip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
53
src/qml/RemoveChildDialog.qml
Normal file
53
src/qml/RemoveChildDialog.qml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.formcard as FormCard
|
||||||
|
import org.kde.kirigamiaddons.labs.components as Components
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
|
Kirigami.Dialog {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
required property NeoChatRoom parentRoom
|
||||||
|
|
||||||
|
required property string roomId
|
||||||
|
|
||||||
|
required property string displayName
|
||||||
|
|
||||||
|
required property string parentDisplayName
|
||||||
|
|
||||||
|
required property bool canSetParent
|
||||||
|
|
||||||
|
required property bool isDeclaredParent
|
||||||
|
|
||||||
|
title: i18nc("@title", "Remove Child")
|
||||||
|
|
||||||
|
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||||
|
|
||||||
|
standardButtons: Kirigami.Dialog.Ok | Kirigami.Dialog.Cancel
|
||||||
|
|
||||||
|
onAccepted: parentRoom.removeChild(root.roomId, removeOfficalCheck.checked)
|
||||||
|
|
||||||
|
contentItem: FormCard.FormCardPage {
|
||||||
|
FormCard.FormCard {
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
FormCard.FormTextDelegate {
|
||||||
|
text: i18n("The child %1 will be removed from the space %2", root.displayName, root.parentDisplayName)
|
||||||
|
textItem.wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
FormCard.FormCheckDelegate {
|
||||||
|
id: removeOfficalCheck
|
||||||
|
visible: root.isDeclaredParent
|
||||||
|
enabled: root.canSetParent
|
||||||
|
text: i18n("The current space is the official parent of this room, should this be cleared?")
|
||||||
|
checked: root.canSetParent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -136,6 +136,7 @@ Kirigami.OverlayDrawer {
|
|||||||
Kirigami.NavigationTabBar {
|
Kirigami.NavigationTabBar {
|
||||||
id: navigationBar
|
id: navigationBar
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
visible: !root.room.isSpace
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ Kirigami.Page {
|
|||||||
|
|
||||||
footer: Kirigami.NavigationTabBar {
|
footer: Kirigami.NavigationTabBar {
|
||||||
id: navigationBar
|
id: navigationBar
|
||||||
|
visible: !root.room.isSpace
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ QQC2.ScrollView {
|
|||||||
/**
|
/**
|
||||||
* @brief The title that should be displayed for this component if available.
|
* @brief The title that should be displayed for this component if available.
|
||||||
*/
|
*/
|
||||||
readonly property string title: i18nc("@action:title", "Room information")
|
readonly property string title: root.room.isSpace ? i18nc("@action:title", "Space Members") : i18nc("@action:title", "Room information")
|
||||||
|
|
||||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||||
@@ -57,6 +57,7 @@ QQC2.ScrollView {
|
|||||||
active: true
|
active: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
visible: !root.room.isSpace
|
||||||
sourceComponent: root.room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
|
sourceComponent: root.room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
|
||||||
onItemChanged: if (item) {
|
onItemChanged: if (item) {
|
||||||
userList.positionViewAtBeginning();
|
userList.positionViewAtBeginning();
|
||||||
@@ -64,6 +65,7 @@ QQC2.ScrollView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.ListSectionHeader {
|
Kirigami.ListSectionHeader {
|
||||||
|
visible: !root.room.isSpace
|
||||||
label: i18n("Options")
|
label: i18n("Options")
|
||||||
activeFocusOnTab: false
|
activeFocusOnTab: false
|
||||||
|
|
||||||
@@ -75,7 +77,7 @@ QQC2.ScrollView {
|
|||||||
|
|
||||||
icon.name: "tools"
|
icon.name: "tools"
|
||||||
text: i18n("Open developer tools")
|
text: i18n("Open developer tools")
|
||||||
visible: Config.developerTools
|
visible: Config.developerTools && !root.room.isSpace
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
@@ -86,7 +88,7 @@ QQC2.ScrollView {
|
|||||||
|
|
||||||
Delegates.RoundedItemDelegate {
|
Delegates.RoundedItemDelegate {
|
||||||
id: searchButton
|
id: searchButton
|
||||||
|
visible: !root.room.isSpace
|
||||||
icon.name: "search"
|
icon.name: "search"
|
||||||
text: i18n("Search in this room")
|
text: i18n("Search in this room")
|
||||||
|
|
||||||
@@ -104,7 +106,7 @@ QQC2.ScrollView {
|
|||||||
|
|
||||||
Delegates.RoundedItemDelegate {
|
Delegates.RoundedItemDelegate {
|
||||||
id: favouriteButton
|
id: favouriteButton
|
||||||
|
visible: !root.room.isSpace
|
||||||
icon.name: root.room && root.room.isFavourite ? "rating" : "rating-unrated"
|
icon.name: root.room && root.room.isFavourite ? "rating" : "rating-unrated"
|
||||||
text: root.room && root.room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
text: root.room && root.room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||||
|
|
||||||
@@ -115,7 +117,7 @@ QQC2.ScrollView {
|
|||||||
|
|
||||||
Delegates.RoundedItemDelegate {
|
Delegates.RoundedItemDelegate {
|
||||||
id: locationsButton
|
id: locationsButton
|
||||||
|
visible: !root.room.isSpace
|
||||||
icon.name: "map-flat"
|
icon.name: "map-flat"
|
||||||
text: i18n("Show locations for this room")
|
text: i18n("Show locations for this room")
|
||||||
|
|
||||||
@@ -240,7 +242,9 @@ QQC2.ScrollView {
|
|||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: groupChatDrawerHeader
|
id: groupChatDrawerHeader
|
||||||
GroupChatDrawerHeader {}
|
GroupChatDrawerHeader {
|
||||||
|
room: root.room
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
|
|||||||
162
src/qml/SpaceHierarchyDelegate.qml
Normal file
162
src/qml/SpaceHierarchyDelegate.qml
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.delegates as Delegates
|
||||||
|
import org.kde.kirigamiaddons.labs.components as Components
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
required property TreeView treeView
|
||||||
|
required property bool isTreeNode
|
||||||
|
required property bool expanded
|
||||||
|
required property int hasChildren
|
||||||
|
required property int depth
|
||||||
|
required property string roomId
|
||||||
|
required property string displayName
|
||||||
|
required property url avatarUrl
|
||||||
|
required property bool isSpace
|
||||||
|
required property int memberCount
|
||||||
|
required property string topic
|
||||||
|
required property bool isJoined
|
||||||
|
required property bool canAddChildren
|
||||||
|
required property string parentDisplayName
|
||||||
|
required property bool canSetParent
|
||||||
|
required property bool isDeclaredParent
|
||||||
|
required property bool canRemove
|
||||||
|
required property NeoChatRoom parentRoom
|
||||||
|
|
||||||
|
signal createRoom()
|
||||||
|
signal enterRoom()
|
||||||
|
|
||||||
|
Delegates.RoundedItemDelegate {
|
||||||
|
anchors.centerIn: root
|
||||||
|
width: sizeHelper.currentWidth
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
spacing: 0
|
||||||
|
Item {
|
||||||
|
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium * (root.depth + (root.isSpace ? 0 : 1))
|
||||||
|
}
|
||||||
|
Kirigami.Icon {
|
||||||
|
visible: root.isSpace
|
||||||
|
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
source: root.hasChildren ? (root.expanded ? "go-up" : "go-down") : "go-next"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Components.Avatar {
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
|
source: root.avatarUrl
|
||||||
|
name: root.displayName
|
||||||
|
}
|
||||||
|
ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
QQC2.Label {
|
||||||
|
id: label
|
||||||
|
text: root.displayName
|
||||||
|
elide: Text.ElideRight
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
visible: root.isJoined
|
||||||
|
text: i18n("Joined")
|
||||||
|
color: Kirigami.Theme.linkColor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Label {
|
||||||
|
id: subtitle
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||||
|
|
||||||
|
text: root.memberCount + (root.topic !== "" ? i18nc("number of room members", " members - ") + root.topic : i18nc("number of room members", " members"))
|
||||||
|
elide: Text.ElideRight
|
||||||
|
font: Kirigami.Theme.smallFont
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
maximumLineCount: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
visible: root.isSpace && root.canAddChildren
|
||||||
|
text: i18nc("@button", "Add new child")
|
||||||
|
icon.name: "list-add"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
onClicked: root.createRoom()
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
QQC2.ToolButton {
|
||||||
|
visible: root.canRemove
|
||||||
|
text: i18nc("@button", "Remove")
|
||||||
|
icon.name: "list-remove"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
onClicked: {
|
||||||
|
removeChildDialog.createObject(QQC2.ApplicationWindow.overlay, {
|
||||||
|
parentRoom: root.parentRoom,
|
||||||
|
roomId: root.roomId,
|
||||||
|
displayName: root.displayName,
|
||||||
|
parentDisplayName: root.parentDisplayName,
|
||||||
|
canSetParent: root.canSetParent,
|
||||||
|
isDeclaredParent: root.isDeclaredParent
|
||||||
|
}).open();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TapHandler {
|
||||||
|
onTapped: {
|
||||||
|
if (root.isSpace) {
|
||||||
|
root.treeView.toggleExpanded(row)
|
||||||
|
} else {
|
||||||
|
if (root.isJoined) {
|
||||||
|
root.enterRoom()
|
||||||
|
} else {
|
||||||
|
Controller.joinRoom(root.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateSizeHelper {
|
||||||
|
id: sizeHelper
|
||||||
|
startBreakpoint: Kirigami.Units.gridUnit * 46
|
||||||
|
endBreakpoint: Kirigami.Units.gridUnit * 66
|
||||||
|
startPercentWidth: 100
|
||||||
|
endPercentWidth: 85
|
||||||
|
maxWidth: Kirigami.Units.gridUnit * 60
|
||||||
|
|
||||||
|
parentWidth: root.treeView ? root.treeView.width : 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: removeChildDialog
|
||||||
|
RemoveChildDialog {}
|
||||||
|
}
|
||||||
|
}
|
||||||
178
src/qml/SpaceHomePage.qml
Normal file
178
src/qml/SpaceHomePage.qml
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import QtQuick.Controls as QQC2
|
||||||
|
import QtQuick.Layouts
|
||||||
|
|
||||||
|
import org.kde.kirigami as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat
|
||||||
|
|
||||||
|
Kirigami.Page {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
|
||||||
|
|
||||||
|
padding: 0
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: columnLayout
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: headerItem
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
implicitHeight: headerColumn.implicitHeight
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
id: headerColumn
|
||||||
|
anchors.centerIn: headerItem
|
||||||
|
width: sizeHelper.currentWidth
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
|
GroupChatDrawerHeader {
|
||||||
|
id: header
|
||||||
|
Layout.fillWidth: true
|
||||||
|
room: root.currentRoom
|
||||||
|
}
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
QQC2.Button {
|
||||||
|
visible: root.currentRoom.canSendState("invite")
|
||||||
|
text: i18nc("@button", "Invite user to space")
|
||||||
|
icon.name: "list-add-user"
|
||||||
|
onClicked: applicationWindow().pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/InviteUserPage.qml", {room: root.currentRoom}, {title: i18nc("@title", "Invite a User")})
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
visible: root.currentRoom.canSendState("m.space.child")
|
||||||
|
text: i18nc("@button", "Add new child")
|
||||||
|
icon.name: "list-add"
|
||||||
|
onClicked: _private.createRoom(root.currentRoom.id)
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18nc("@button", "Leave the space")
|
||||||
|
icon.name: "go-previous"
|
||||||
|
onClicked: RoomManager.leaveRoom(root.currentRoom)
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18nc("@button", "Space settings")
|
||||||
|
icon.name: "settings-configure"
|
||||||
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
onClicked: applicationWindow().pageStack.pushDialogLayer('qrc:/org/kde/neochat/qml/Categories.qml', {room: root.currentRoom, connection: root.currentRoom.connection}, { title: i18n("Room Settings") })
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: text
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.SearchField {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
|
onTextChanged: spaceChildSortFilterModel.filterText = text
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DelegateSizeHelper {
|
||||||
|
id: sizeHelper
|
||||||
|
startBreakpoint: Kirigami.Units.gridUnit * 46
|
||||||
|
endBreakpoint: Kirigami.Units.gridUnit * 66
|
||||||
|
startPercentWidth: 100
|
||||||
|
endPercentWidth: 85
|
||||||
|
maxWidth: Kirigami.Units.gridUnit * 60
|
||||||
|
|
||||||
|
parentWidth: columnLayout.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
QQC2.ScrollView {
|
||||||
|
id: hierarchyScrollView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
visible: !spaceChildrenModel.loading
|
||||||
|
|
||||||
|
TreeView {
|
||||||
|
id: spaceTree
|
||||||
|
columnWidthProvider: function (column) { return spaceTree.width }
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
model: SpaceChildSortFilterModel {
|
||||||
|
id: spaceChildSortFilterModel
|
||||||
|
sourceModel: SpaceChildrenModel {
|
||||||
|
id: spaceChildrenModel
|
||||||
|
space: root.currentRoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: SpaceHierarchyDelegate {
|
||||||
|
onCreateRoom: _private.createRoom(roomId)
|
||||||
|
onEnterRoom: _private.enterRoom(roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Item {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
visible: spaceChildrenModel.loading
|
||||||
|
|
||||||
|
Loader {
|
||||||
|
active: spaceChildrenModel.loading
|
||||||
|
anchors.centerIn: parent
|
||||||
|
sourceComponent: Kirigami.LoadingPlaceholder {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
}
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: _private
|
||||||
|
function createRoom(parentId) {
|
||||||
|
let dialog = applicationWindow().pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {
|
||||||
|
title: i18nc("@title", "Create a Child"),
|
||||||
|
connection: root.currentRoom.connection,
|
||||||
|
parentId : parentId,
|
||||||
|
showChildType: true,
|
||||||
|
showCreateChoice: true
|
||||||
|
}, {
|
||||||
|
title: i18nc("@title", "Create a Child")
|
||||||
|
})
|
||||||
|
dialog.addChild.connect((childId, setChildParent) => {
|
||||||
|
// We have to get a room object from the connection as we may not
|
||||||
|
// be adding to the top level parent.
|
||||||
|
let parent = root.currentRoom.connection.room(parentId)
|
||||||
|
if (parent) {
|
||||||
|
parent.addChild(childId, setChildParent)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
dialog.newChild.connect(childName => {spaceChildrenModel.addPendingChild(childName)})
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterRoom(roomId) {
|
||||||
|
let room = root.currentRoom.connection.room(roomId)
|
||||||
|
if (room) {
|
||||||
|
RoomManager.enterRoom(room)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,7 +29,7 @@ Loader {
|
|||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18nc("'Space' is a matrix space", "View Space")
|
text: i18nc("'Space' is a matrix space", "View Space")
|
||||||
icon.name: "view-list-details"
|
icon.name: "view-list-details"
|
||||||
onTriggered: RoomManager.enterRoom(room);
|
onTriggered: RoomManager.enterSpaceHome(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import QtQml
|
import QtQml
|
||||||
|
|
||||||
|
import com.github.quotient_im.libquotient
|
||||||
|
|
||||||
import org.kde.neochat
|
import org.kde.neochat
|
||||||
|
|
||||||
Message {
|
Message {
|
||||||
@@ -40,7 +42,7 @@ Message {
|
|||||||
case KeyVerificationSession.KEY_MISMATCH:
|
case KeyVerificationSession.KEY_MISMATCH:
|
||||||
return i18n("The session verification was canceled because the keys are incorrect.");
|
return i18n("The session verification was canceled because the keys are incorrect.");
|
||||||
case KeyVerificationSession.REMOTE_KEY_MISMATCH:
|
case KeyVerificationSession.REMOTE_KEY_MISMATCH:
|
||||||
return i18n("The remote party canceled the session verification because the keys are incorrect.");
|
return i18n("The remote party canceled the session verification because the keys are incorrect.\n\n**Please log out and log back in, your session is broken/corrupt.**");
|
||||||
case KeyVerificationSession.USER_MISMATCH:
|
case KeyVerificationSession.USER_MISMATCH:
|
||||||
return i18n("The session verification was canceled because it verifies an unexpected user.");
|
return i18n("The session verification was canceled because it verifies an unexpected user.");
|
||||||
case KeyVerificationSession.REMOTE_USER_MISMATCH:
|
case KeyVerificationSession.REMOTE_USER_MISMATCH:
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ Kirigami.ApplicationWindow {
|
|||||||
property bool roomListLoaded: false
|
property bool roomListLoaded: false
|
||||||
|
|
||||||
property RoomPage roomPage
|
property RoomPage roomPage
|
||||||
|
property SpaceHomePage spaceHomePage
|
||||||
|
|
||||||
property NeoChatConnection connection: Controller.activeConnection
|
property NeoChatConnection connection: Controller.activeConnection
|
||||||
|
|
||||||
@@ -96,15 +97,36 @@ Kirigami.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onPushSpaceHome(room) {
|
||||||
|
root.spaceHomePage = pageStack.push("qrc:/org/kde/neochat/qml/SpaceHomePage.qml");
|
||||||
|
root.spaceHomePage.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
function onReplaceRoom(room, event) {
|
function onReplaceRoom(room, event) {
|
||||||
const roomItem = pageStack.get(pageStack.depth - 1);
|
if (root.roomPage) {
|
||||||
pageStack.currentIndex = pageStack.depth - 1;
|
pageStack.currentIndex = pageStack.depth - 1;
|
||||||
|
} else {
|
||||||
|
pageStack.pop();
|
||||||
|
root.roomPage = pageStack.push("qrc:/org/kde/neochat/qml/RoomPage.qml", {connection: root.connection});
|
||||||
|
root.spaceHomePage = null;
|
||||||
|
}
|
||||||
root.roomPage.forceActiveFocus();
|
root.roomPage.forceActiveFocus();
|
||||||
if (event.length > 0) {
|
if (event.length > 0) {
|
||||||
roomItem.goToEvent(event);
|
root.roomPage.goToEvent(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onReplaceSpaceHome(room) {
|
||||||
|
if (root.spaceHomePage) {
|
||||||
|
pageStack.currentIndex = pageStack.depth - 1;
|
||||||
|
} else {
|
||||||
|
pageStack.pop();
|
||||||
|
root.spaceHomePage = pageStack.push("qrc:/org/kde/neochat/qml/SpaceHomePage.qml");
|
||||||
|
root.roomPage = null;
|
||||||
|
}
|
||||||
|
root.spaceHomePage.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
function goToEvent(event) {
|
function goToEvent(event) {
|
||||||
if (event.length > 0) {
|
if (event.length > 0) {
|
||||||
roomItem.goToEvent(event);
|
roomItem.goToEvent(event);
|
||||||
@@ -335,13 +357,6 @@ Kirigami.ApplicationWindow {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: createSpaceDialog
|
|
||||||
CreateSpaceDialog {
|
|
||||||
connection: root.connection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
Component {
|
||||||
id: roomWindow
|
id: roomWindow
|
||||||
RoomWindow {}
|
RoomWindow {}
|
||||||
|
|||||||
@@ -91,8 +91,9 @@ public:
|
|||||||
static Registration _instance;
|
static Registration _instance;
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
static Registration *create(QQmlEngine *, QJSEngine *)
|
static Registration *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
|
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||||
return &instance();
|
return &instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
38
src/roomlastmessageprovider.cpp
Normal file
38
src/roomlastmessageprovider.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "roomlastmessageprovider.h"
|
||||||
|
|
||||||
|
using namespace Qt::Literals::StringLiterals;
|
||||||
|
|
||||||
|
RoomLastMessageProvider::RoomLastMessageProvider()
|
||||||
|
: m_config(KSharedConfig::openConfig(u"data"_s, KConfig::SimpleConfig, QStandardPaths::AppDataLocation))
|
||||||
|
, m_configGroup(KConfigGroup(m_config, u"EventCache"_s))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomLastMessageProvider::~RoomLastMessageProvider()
|
||||||
|
{
|
||||||
|
m_config->sync();
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomLastMessageProvider &RoomLastMessageProvider::self()
|
||||||
|
{
|
||||||
|
static RoomLastMessageProvider instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RoomLastMessageProvider::hasKey(const QString &roomId) const
|
||||||
|
{
|
||||||
|
return m_configGroup.hasKey(roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray RoomLastMessageProvider::read(const QString &roomId) const
|
||||||
|
{
|
||||||
|
return m_configGroup.readEntry(roomId, QByteArray{});
|
||||||
|
}
|
||||||
|
|
||||||
|
void RoomLastMessageProvider::write(const QString &roomId, const QByteArray &json)
|
||||||
|
{
|
||||||
|
m_configGroup.writeEntry(roomId, json);
|
||||||
|
}
|
||||||
41
src/roomlastmessageprovider.h
Normal file
41
src/roomlastmessageprovider.h
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <KConfigGroup>
|
||||||
|
#include <KSharedConfig>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store and retrieve the last message of a room.
|
||||||
|
*/
|
||||||
|
class RoomLastMessageProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Get the global instance of RoomLastMessageProvider.
|
||||||
|
*/
|
||||||
|
static RoomLastMessageProvider &self();
|
||||||
|
~RoomLastMessageProvider();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we have the last message content for the specified roomId.
|
||||||
|
*/
|
||||||
|
bool hasKey(const QString &roomId) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read the last message content of the specified roomId.
|
||||||
|
*/
|
||||||
|
QByteArray read(const QString &roomId) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Write the last message content for the specified roomId.
|
||||||
|
*/
|
||||||
|
void write(const QString &roomId, const QByteArray &json);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RoomLastMessageProvider();
|
||||||
|
|
||||||
|
KSharedConfig::Ptr m_config;
|
||||||
|
KConfigGroup m_configGroup;
|
||||||
|
};
|
||||||
@@ -179,7 +179,11 @@ void RoomManager::openRoomForActiveConnection()
|
|||||||
const auto room = qobject_cast<NeoChatRoom *>(Controller::instance().activeConnection()->room(roomId));
|
const auto room = qobject_cast<NeoChatRoom *>(Controller::instance().activeConnection()->room(roomId));
|
||||||
|
|
||||||
if (room) {
|
if (room) {
|
||||||
enterRoom(room);
|
if (room->isSpace()) {
|
||||||
|
enterSpaceHome(room);
|
||||||
|
} else {
|
||||||
|
enterRoom(room);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,6 +226,34 @@ void RoomManager::openWindow(NeoChatRoom *room)
|
|||||||
Q_EMIT openRoomInNewWindow(room);
|
Q_EMIT openRoomInNewWindow(room);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RoomManager::enterSpaceHome(NeoChatRoom *spaceRoom)
|
||||||
|
{
|
||||||
|
if (!spaceRoom->isSpace()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If replacing a normal room message timeline make sure any edit is cancelled.
|
||||||
|
if (m_currentRoom && !m_currentRoom->chatBoxEditId().isEmpty()) {
|
||||||
|
m_currentRoom->setChatBoxEditId({});
|
||||||
|
}
|
||||||
|
// Save the chatbar text for the current room if any before switching
|
||||||
|
if (m_currentRoom && m_chatDocumentHandler) {
|
||||||
|
if (m_chatDocumentHandler->document()) {
|
||||||
|
m_currentRoom->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_lastCurrentRoom = std::exchange(m_currentRoom, spaceRoom);
|
||||||
|
Q_EMIT currentRoomChanged();
|
||||||
|
|
||||||
|
if (!m_lastCurrentRoom) {
|
||||||
|
Q_EMIT pushSpaceHome(spaceRoom);
|
||||||
|
} else {
|
||||||
|
Q_EMIT replaceSpaceHome(m_currentRoom);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save last open room
|
||||||
|
m_lastRoomConfig.writeEntry(Controller::instance().activeConnection()->userId(), spaceRoom->id());
|
||||||
|
}
|
||||||
|
|
||||||
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
|
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
|
||||||
{
|
{
|
||||||
if (action == "mention"_ls || action.isEmpty()) {
|
if (action == "mention"_ls || action.isEmpty()) {
|
||||||
|
|||||||
@@ -90,8 +90,9 @@ public:
|
|||||||
explicit RoomManager(QObject *parent = nullptr);
|
explicit RoomManager(QObject *parent = nullptr);
|
||||||
virtual ~RoomManager();
|
virtual ~RoomManager();
|
||||||
static RoomManager &instance();
|
static RoomManager &instance();
|
||||||
static RoomManager *create(QQmlEngine *, QJSEngine *)
|
static RoomManager *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
|
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||||
return &instance();
|
return &instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,6 +128,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
Q_INVOKABLE void leaveRoom(NeoChatRoom *room);
|
Q_INVOKABLE void leaveRoom(NeoChatRoom *room);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Enter the home page of the given space.
|
||||||
|
*
|
||||||
|
* This method will tell NeoChat to open the home page for the given space.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void enterSpaceHome(NeoChatRoom *spaceRoom);
|
||||||
|
|
||||||
// Overrided methods from UriResolverBase
|
// Overrided methods from UriResolverBase
|
||||||
/**
|
/**
|
||||||
* @brief Resolve a user URI.
|
* @brief Resolve a user URI.
|
||||||
@@ -263,6 +271,26 @@ Q_SIGNALS:
|
|||||||
*/
|
*/
|
||||||
void replaceRoom(NeoChatRoom *room, const QString &event);
|
void replaceRoom(NeoChatRoom *room, const QString &event);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Push a new space home page.
|
||||||
|
*
|
||||||
|
* Signal triggered when the main window pageStack should push a new page with
|
||||||
|
* the space home for the given space room.
|
||||||
|
*
|
||||||
|
* @param spaceRoom the space room to be shown on the new page.
|
||||||
|
*/
|
||||||
|
void pushSpaceHome(NeoChatRoom *spaceRoom);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Replace the existing space home.
|
||||||
|
*
|
||||||
|
* Signal triggered when the currently displayed room page should be changed
|
||||||
|
* to the space home for the given space room.
|
||||||
|
*
|
||||||
|
* @param spaceRoom the space room to be shown on the new page.
|
||||||
|
*/
|
||||||
|
void replaceSpaceHome(NeoChatRoom *spaceRoom);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Go to the specified event in the current room.
|
* @brief Go to the specified event in the current room.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
|
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
|
||||||
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
|
||||||
|
|
||||||
@@ -33,8 +34,9 @@ public:
|
|||||||
static SpaceHierarchyCache _instance;
|
static SpaceHierarchyCache _instance;
|
||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
static SpaceHierarchyCache *create(QQmlEngine *, QJSEngine *)
|
static SpaceHierarchyCache *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
|
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||||
return &instance();
|
return &instance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user