Compare commits

...

74 Commits

Author SHA1 Message Date
Carl Schwan
fab2a81aee Ton down the intensity of the background on dark theme 2024-12-15 16:12:37 +01:00
Carl Schwan
4daff26747 Add background to room page
Similar to What's App, Telegram and Kaidan.
2024-12-12 12:57:18 +01:00
Kai Uwe Broulik
fe09dc23a6 Main: Set window title to current room name 2024-12-11 17:36:41 +01:00
Tobias Fella
9cd4a7416e Fix crash when sending messages
ECM recently started adding -fhardened, which makes us crash here since we're doing things that aren't valid, but happened to work out fine previously.
2024-12-11 16:05:19 +01:00
l10n daemon script
f6e3210b0d GIT_SILENT Sync po/docbooks with svn 2024-12-11 01:30:44 +00:00
Tobias Fella
16d33eb02c Don't show "This message was deleted" for state events
The result is unexpected and confusing
2024-12-10 12:58:24 +01:00
Tobias Fella
326512697c Show displayname instead of user id for join events 2024-12-10 12:46:54 +01:00
l10n daemon script
f25de891bf GIT_SILENT Sync po/docbooks with svn 2024-12-10 01:34:07 +00:00
l10n daemon script
e785533858 GIT_SILENT Sync po/docbooks with svn 2024-12-09 01:35:58 +00:00
l10n daemon script
0699bc4147 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-12-09 01:26:04 +00:00
James Graham
d07a8258e9 Room Custom Filter Prep
This is basically prep work for customisable sort orders. The room sort parameters are detached from the room sort model as multiple components will need to access the values. The sorting is then generified.

Some defunct sorting parameters are also removed.
2024-12-08 13:10:00 +00:00
l10n daemon script
d490e65315 GIT_SILENT Sync po/docbooks with svn 2024-12-08 01:30:05 +00:00
Tobias Fella
7a632c9561 Fix janky behavior of room drawer swipes
When not modal, dragging the edge of the room drawer to change its width felt very broken.
This seems to be a collision between Qt's dragging logic and our dragging logic,
so we disable theirs if the drawer is not modal
2024-12-07 12:39:19 +01:00
l10n daemon script
ec4b35fa5f GIT_SILENT Sync po/docbooks with svn 2024-12-07 01:32:24 +00:00
l10n daemon script
b263755629 GIT_SILENT Sync po/docbooks with svn 2024-12-06 01:31:21 +00:00
l10n daemon script
828585a260 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-12-06 01:25:13 +00:00
l10n daemon script
3b33121dbc GIT_SILENT made messages (after extraction) 2024-12-06 00:40:30 +00:00
Tobias Fella
21484c8184 Convert reuse data to new format 2024-12-05 18:12:07 +01:00
Soumyadeep Ghosh
020385c850 backend: allow users to sort based on last activity
This MR allows an option to prefer the last activity as the most
favorable parameter for sorting.
2024-12-05 16:32:40 +00:00
Kai Uwe Broulik
c585f3d8ae Add "Copy Link Address" context menu
Allows copying just the link address of a hyperlink.
2024-12-05 15:58:22 +00:00
l10n daemon script
3356e6c6cf GIT_SILENT Sync po/docbooks with svn 2024-12-05 01:31:30 +00:00
Joshua Goins
2fa6ad22a3 Expose access token under developer tools
I need this from time to time. For example, debugging an API call or
scripting something with the admin API. This is buried under developer
settings so hopefully no one starts sharing this willy-nilly.

Element Web does something similar, except theirs is hidden under Help &
About.
2024-12-04 21:42:10 +00:00
Joshua Goins
b887519f26 Add missing contexts for the rest of the settings header and page titles 2024-12-04 15:48:43 -05:00
Joshua Goins
b1e54a834c Fix capitalization of labels under General Settings
Buttons should be title case, and form headers should be sentence case.
Also, add an icon to the "Reset to defaults" button.
2024-12-04 15:41:36 -05:00
Joshua Goins
171e62a272 AccountMenu: Fix capitalization of items
This is a menu full of menu items, and should be using title case as
suggested by our HIG.
2024-12-04 15:38:00 -05:00
Joshua Goins
053770c117 snap: update libquotient 2024-12-04 20:05:07 +00:00
Tobias Fella
911f3e1f54 Fix some compilation warnings 2024-12-04 17:31:02 +01:00
l10n daemon script
1494ba95b3 GIT_SILENT Sync po/docbooks with svn 2024-12-04 01:32:09 +00:00
l10n daemon script
c0b00ce146 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-12-04 01:24:49 +00:00
l10n daemon script
47a08b829e GIT_SILENT made messages (after extraction) 2024-12-04 00:40:08 +00:00
Joshua Goins
953bb60aac Remove explicit quit connection with QQmlApplicationEngine
This is not needed, it already does this by itself.
2024-12-03 22:19:49 +00:00
Tobias Fella
fc4cb31277 Devtools: Use ChooseRoomDialog to select a room for inspection
The combobox has several drawbacks:
- It's not sorted in any meaningful way
- It doesn't have a search
- It doesn't show the icon and last message

This makes it hard to find the intended room in that dialog. The ChooseRoomDialog provides these things for us
2024-12-03 10:07:11 +01:00
l10n daemon script
9876636dbc GIT_SILENT Sync po/docbooks with svn 2024-12-03 01:31:25 +00:00
Heiko Becker
8314ab03bd GIT_SILENT Update Appstream for new release
(cherry picked from commit 1e29eca59a)
2024-12-03 01:10:01 +01:00
Tobias Fella
9d887ba3e7 Remove system information from device display name
BUG: 496901
2024-12-02 15:50:50 +00:00
l10n daemon script
1612a8a960 GIT_SILENT Sync po/docbooks with svn 2024-12-02 01:54:40 +00:00
l10n daemon script
99af210e62 GIT_SILENT made messages (after extraction) 2024-12-02 00:41:39 +00:00
Paul Brown
8fd108cde1 Added Stuart Turton as supporter 2024-12-01 19:09:18 +00:00
James Graham
ca81d35936 Post Message Refactor 2
Remove the need for NeoChat to have overloaded functions for posting messages and just use what quotient gives
2024-12-01 19:05:31 +00:00
James Graham
d65aacac6f postHtml Refactor
Use EventRelation and EventContent to form messages rather than writing custom Json.
2024-12-01 17:19:55 +00:00
l10n daemon script
43f052a363 GIT_SILENT Sync po/docbooks with svn 2024-12-01 01:31:34 +00:00
Paul Brown
0e0a38ffa2 Added supporter Joshua Strobl 2024-11-30 19:33:33 +00:00
Joshua Goins
819586fc4e Fix state keys developer tool page not working
Yet another applicationWindow failure
2024-11-30 16:15:47 +00:00
Joshua Goins
6b8a331428 Add icon for "Open developer tools" under Settings, add separator
It makes it look a little bit nicer, I think.
2024-11-30 16:15:28 +00:00
James Graham
57e7004e05 Fix removeConnection
Check m_accountsLoading and m_connectionsLoading separately for removal as when loadAccessTokenFromKeyChain() fails m_connectionsLoading won't have an entry for it
2024-11-30 15:27:09 +00:00
l10n daemon script
25c95cafe3 GIT_SILENT Sync po/docbooks with svn 2024-11-30 01:30:42 +00:00
l10n daemon script
54be1a8918 GIT_SILENT Sync po/docbooks with svn 2024-11-29 01:34:41 +00:00
l10n daemon script
d4a0573051 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-29 01:25:40 +00:00
Paul Brown
4a96eac67b Update org.kde.neochat.appdata.xml 2024-11-28 09:02:11 +00:00
Ingo Klöcker
2f4134a6d2 Add icon for F-Droid store listing
Our fastlane metadata tooling looks for an icon named like the application
icon specified in AndroidManifest.xml followed by "-playstore.png".
2024-11-27 22:08:34 +01:00
Laurent Montel
4f87dcc0c0 Add missing include moc 2024-11-27 20:44:46 +00:00
Nate Graham
47eba6b720 Make the room list slightly narrower by default
`GridUnit * 17` is 306px, which is quite wide. Given that the focus in
this app is on the content (i.e. the chat view) let's make the sidebar
a 36px narrower to make more room for content.

BUG: 496722
FIXED-IN: 24.12.0
2024-11-27 16:07:06 +00:00
l10n daemon script
cba537d561 GIT_SILENT Sync po/docbooks with svn 2024-11-27 01:31:59 +00:00
l10n daemon script
c9d03cb042 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-27 01:25:14 +00:00
Tobias Fella
e42c002fbd Fix visibility check 2024-11-26 21:58:39 +01:00
Tobias Fella
7c7b073a47 Fix some unqualified access warnings 2024-11-26 21:58:34 +01:00
l10n daemon script
20090d21eb GIT_SILENT Sync po/docbooks with svn 2024-11-26 01:30:45 +00:00
l10n daemon script
b0e69ff4b8 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-26 01:24:17 +00:00
l10n daemon script
552bb0a98b GIT_SILENT made messages (after extraction) 2024-11-26 00:40:17 +00:00
l10n daemon script
19510858af GIT_SILENT Sync po/docbooks with svn 2024-11-25 01:33:01 +00:00
l10n daemon script
44b2f6ee63 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-11-25 01:24:13 +00:00
l10n daemon script
c5d3002f31 GIT_SILENT made messages (after extraction) 2024-11-25 00:40:24 +00:00
James Graham
2e3659d4ee Don't open the space home page when changing spaces on android
When I change space on android I just want to be able to select a room I'm in.
2024-11-24 13:13:47 +00:00
l10n daemon script
f8a5509a91 GIT_SILENT Sync po/docbooks with svn 2024-11-24 01:32:27 +00:00
James Graham
421f436871 Fix MessageContentModel crash
Make sure we check that the RoomMessageEvent exists before accessing anything
2024-11-23 22:20:05 +00:00
James Graham
a37c9d6cea Make sure space drawer icons are available for android 2024-11-23 17:09:44 +00:00
Tobias Fella
d14d576d99 Implement MSC 4228 Search Redirection
See https://github.com/matrix-org/matrix-spec-proposals/pull/4228 for details.
Since this is tricky to test without server-side support, I have added a basic implementation
to the mock server in appiumtests/login-server.py

1. Start appiumtests/login-server.py
2. Start neochat with "--test --ignore-ssl-errors" options
3. Open "Explore Rooms"
4. Search for the exact string "forbidden"
5. See new error message provided by server
2024-11-23 15:50:12 +01:00
James Graham
9391e44e4b EventHandler Cleanup
Remove functions from eventhandler and replace with now uplifted functions in libquotient
2024-11-23 14:21:13 +00:00
l10n daemon script
a7aebe3a61 GIT_SILENT Sync po/docbooks with svn 2024-11-23 01:29:28 +00:00
Paul Brown
1fca9021a4 Added supporter dabe 2024-11-22 22:04:28 +00:00
Joshua Goins
a39194b2ad Fix ShareActionStub for Windows and Android
Apparently, we are supposed to be setting source file properties for our QML files *before*
the QML module is created. Doing it after seemed to work until Qt 6.8, where it finally
broke. Notably, this makes the Android version work again but might also affect Windows.
2024-11-22 13:44:17 +00:00
l10n daemon script
bff93d9352 GIT_SILENT Sync po/docbooks with svn 2024-11-22 01:32:35 +00:00
l10n daemon script
d76c9cd16d GIT_SILENT Sync po/docbooks with svn 2024-11-21 01:30:26 +00:00
l10n daemon script
14774fe235 GIT_SILENT Sync po/docbooks with svn 2024-11-20 01:31:20 +00:00
117 changed files with 26636 additions and 15324 deletions

View File

@@ -1,55 +0,0 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: NeoChat
Upstream-Contact: Carl Schwan <carlschwan@kde.org>
Files: 128-logo.png icons/* logo.png org.kde.neochat.svg org.kde.neochat.tray.svg android/res/drawable/neochat.png
Copyright: 2020 Carson Black <uhhadd@gmail.com>
License: LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
Files: android/res/drawable/splash.xml
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: .gitignore
Copyright: None
License: CC0-1.0
Files: .gitlab/issue_templates/bug.md
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: CC0-1.0
Files: src/res.qrc src/res_android.qrc src/res_desktop.qrc
Copyright: None
License: CC0-1.0
Files: cmake/Flatpak/99-noto-mono-color-emoji.conf
Copyright: 2021 Carl Schwan <carlschwan@kde.org>
License: BSD-2-Clause
Files: src/neochatconfig.kcfg
Copyright: 2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/neochat.notifyrc
Copyright: 2020 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: src/qml/confetti.png src/qml/glowdot.png
Copyright: 2021 Alexey Andreyev <aa13q@ya.ru>
License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: autotests/data/*
Copyright: none
License: CC0-1.0
Files: appiumtests/data/*
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: CC0-1.0
Files: src/purpose/purposeplugin.json
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause

84
REUSE.toml Normal file
View File

@@ -0,0 +1,84 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: CC0-1.0
version = 1
SPDX-PackageName = "NeoChat"
SPDX-PackageSupplier = "Carl Schwan <carlschwan@kde.org>"
[[annotations]]
path = ["128-logo.png", "icons/**", "logo.png", "org.kde.neochat.svg", "org.kde.neochat.tray.svg", "android/res/drawable/neochat.png", "android/neochat-playstore.png"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Carson Black <uhhadd@gmail.com>"
SPDX-License-Identifier = "LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL"
[[annotations]]
path = "android/res/drawable/splash.xml"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = ".gitignore"
precedence = "aggregate"
SPDX-FileCopyrightText = "None"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".gitlab/issue_templates/bug.md"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ["src/res.qrc", "src/res_android.qrc", "src/res_desktop.qrc"]
precedence = "aggregate"
SPDX-FileCopyrightText = "None"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "cmake/Flatpak/99-noto-mono-color-emoji.conf"
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "src/neochatconfig.kcfg"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "src/neochat.notifyrc"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = ["src/qml/confetti.png", "src/qml/glowdot.png"]
precedence = "aggregate"
SPDX-FileCopyrightText = "2021 Alexey Andreyev <aa13q@ya.ru>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = ".flatpak-manifest.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2020-2022 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "autotests/data/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "none"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "appiumtests/data/**"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "CC0-1.0"
[[annotations]]
path = "src/purpose/purposeplugin.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause"

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -83,6 +83,15 @@ def create_room():
next_sync_payload = "sync_response_new_room"
return response
@app.route("/_matrix/client/v3/publicRooms", methods=["POST"])
def public_rooms():
if request.get_json()["filter"]["generic_search_term"] == "forbidden":
data = dict()
data["errcode"] = "M_FORBIDDEN"
data["error"] = "You are not allowed to search for this. Go to https://wikipedia.org for more information"
return data, 403
return dict()
if __name__ == "__main__":

View File

@@ -32,8 +32,6 @@ private:
private Q_SLOTS:
void initTestCase();
void eventId();
void nullEventId();
void authorDisplayName();
void nullAuthorDisplayName();
void singleLineSidplayName();
@@ -56,14 +54,8 @@ private Q_SLOTS:
void nullSubtitle();
void mediaInfo();
void nullMediaInfo();
void hasReply();
void nullHasReply();
void replyId();
void nullReplyId();
void replyAuthor();
void nullReplyAuthor();
void thread();
void nullThread();
void location();
void nullLocation();
};
@@ -74,17 +66,6 @@ void EventHandlerTest::initTestCase()
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
}
void EventHandlerTest::eventId()
{
QCOMPARE(EventHandler::id(room->messageEvents().at(0).get()), QStringLiteral("$153456789:example.org"));
}
void EventHandlerTest::nullEventId()
{
QTest::ignoreMessage(QtWarningMsg, "id called with event set to nullptr.");
QCOMPARE(EventHandler::id(nullptr), QString());
}
void EventHandlerTest::authorDisplayName()
{
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), QStringLiteral("before"));
@@ -118,8 +99,9 @@ void EventHandlerTest::time()
{
const auto event = room->messageEvents().at(0).get();
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)));
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC))),
QDateTime::fromMSecsSinceEpoch(1234, QTimeZone(QTimeZone::UTC)));
}
void EventHandlerTest::nullTime()
@@ -138,19 +120,19 @@ void EventHandlerTest::timeString()
KFormat format;
QCOMPARE(EventHandler::timeString(event, false),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, true),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::ShortFormat));
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC))),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, QTimeZone(QTimeZone::UTC)).toLocalTime().date(), QLocale::LongFormat));
QCOMPARE(EventHandler::timeString(event, QStringLiteral("hh:mm")),
QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
QDateTime::fromMSecsSinceEpoch(1432735824654, QTimeZone(QTimeZone::UTC)).toString(QStringLiteral("hh:mm")));
}
void EventHandlerTest::highlighted()
@@ -295,30 +277,6 @@ void EventHandlerTest::nullMediaInfo()
QCOMPARE(EventHandler::mediaInfo(room, nullptr), QVariantMap());
}
void EventHandlerTest::hasReply()
{
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(5).get()), true);
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(0).get()), false);
}
void EventHandlerTest::nullHasReply()
{
QTest::ignoreMessage(QtWarningMsg, "hasReply called with event set to nullptr.");
QCOMPARE(EventHandler::hasReply(nullptr), false);
}
void EventHandlerTest::replyId()
{
QCOMPARE(EventHandler::replyId(room->messageEvents().at(5).get()), QStringLiteral("$153456789:example.org"));
QCOMPARE(EventHandler::replyId(room->messageEvents().at(0).get()), QStringLiteral(""));
}
void EventHandlerTest::nullReplyId()
{
QTest::ignoreMessage(QtWarningMsg, "replyId called with event set to nullptr.");
QCOMPARE(EventHandler::replyId(nullptr), QString());
}
void EventHandlerTest::replyAuthor()
{
auto replyEvent = room->messageEvents().at(0).get();
@@ -344,29 +302,6 @@ void EventHandlerTest::nullReplyAuthor()
QCOMPARE(EventHandler::replyAuthor(room, nullptr), RoomMember());
}
void EventHandlerTest::thread()
{
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(0).get()), false);
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(0).get()), QString());
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(9).get()), true);
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
QCOMPARE(EventHandler::replyId(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(10).get()), true);
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(10).get()), QStringLiteral("$threadroot:example.org"));
QCOMPARE(EventHandler::replyId(room->messageEvents().at(10).get()), QStringLiteral("$threadmessage1:example.org"));
}
void EventHandlerTest::nullThread()
{
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with event set to nullptr.");
QCOMPARE(EventHandler::isThreaded(nullptr), false);
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with event set to nullptr.");
QCOMPARE(EventHandler::threadRoot(nullptr), QString());
}
void EventHandlerTest::location()
{
QCOMPARE(EventHandler::latitude(room->messageEvents().at(7).get()), QStringLiteral("51.7035").toFloat());

View File

@@ -54,19 +54,24 @@
<summary xml:lang="ar">دردش على ماتركس</summary>
<summary xml:lang="ca">Xat a Matrix</summary>
<summary xml:lang="ca-valencia">Xat a Matrix</summary>
<summary xml:lang="de">Über Matrix unterhalten</summary>
<summary xml:lang="en-GB">Chat on Matrix</summary>
<summary xml:lang="es">Charle en Matrix</summary>
<summary xml:lang="eu">Berriketa Matrix-en</summary>
<summary xml:lang="fi">Keskustelu Matrixissä</summary>
<summary xml:lang="fr">Discuter sur Matrix</summary>
<summary xml:lang="gl">Charlar en Matrix</summary>
<summary xml:lang="he">התכתבות דרך Matrix</summary>
<summary xml:lang="hu">Csevegés Matrixon</summary>
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
<summary xml:lang="it">Chat su Matrix</summary>
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
<summary xml:lang="lv">Tērzējiet „Matrix“ tīklā</summary>
<summary xml:lang="nl">Chat op Matrix</summary>
<summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary>
<summary xml:lang="sv">Chatta på Matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary>
@@ -288,7 +293,7 @@
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
<value key="KDE::supporters">Tanguy Fardet</value>
<value key="KDE::supporters">Tanguy Fardet;[dabe](https://freeradical.zone/@dabe);[lengau](https://mastodon.world/@lengau);Joshua Strobl;Stuart Turton</value>
</custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots>
@@ -443,6 +448,7 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.12.0" date="2024-12-12"/>
<release version="24.08.3" date="2024-11-07"/>
<release version="24.08.2" date="2024-10-10"/>
<release version="24.08.1" date="2024-09-12"/>

View File

@@ -88,24 +88,31 @@ GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端
Comment=Chat on Matrix
Comment[ar]=دردش على ماتركس
Comment[ca]=Xat a Matrix
Comment[ca@valencia]=Xat a Matrix
Comment[de]=Über Matrix unterhalten
Comment[en_GB]=Chat on Matrix
Comment[es]=Chat en Matrix
Comment[eu]=Berriketa Matrix-en
Comment[fi]=Keskustele Matrixissä
Comment[fr]=Clavarder sur Matrix
Comment[gl]=Charle en Matrix
Comment[he]=התכתבות דרך Matrix
Comment[hu]=Csevegés Matrixon
Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix
Comment[ka]=ჩატი Matrix-ზე
Comment[lv]=Tērzējiet „Matrix“ tīklā
Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie
Comment[sl]=Klepet na Matrixu
Comment[sv]=Chatta på Matrix
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
Comment[tr]=Matrix Üzerinde Sohbet Et
Comment[uk]=Спілкування у Matrix
Comment[x-test]=xxChat on Matrixxx
Comment[zh_TW]=在 Matrix 上聊天
MimeType=x-scheme-handler/matrix;
Exec=neochat %u
Terminal=false

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -92,7 +92,7 @@ parts:
- olm
- qtkeychain
source: https://github.com/quotient-im/libQuotient.git
source-tag: 0.9.0
source-tag: 0.9.1
source-depth: 1
plugin: cmake
build-packages:

View File

@@ -194,12 +194,20 @@ add_library(neochat STATIC
models/threadmodel.h
enums/messagetype.h
messagecomponent.h
enums/roomsortparameter.cpp
enums/roomsortparameter.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES
@@ -311,13 +319,9 @@ if(NOT ANDROID AND NOT WIN32)
qml/EditMenu.qml
)
else()
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_RESOURCE_ALIAS qml/ShareAction.qml
)
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
endif()
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
if(WIN32)
@@ -531,6 +535,7 @@ if(ANDROID)
"kde"
"list-remove-symbolic"
"edit-delete"
"user-home-symbolic"
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()

View File

@@ -11,6 +11,8 @@
#include "neochatroom.h"
#include "texthandler.h"
using namespace Qt::StringLiterals;
ChatBarCache::ChatBarCache(QObject *parent)
: QObject(parent)
{
@@ -319,7 +321,25 @@ void ChatBarCache::postMessage()
return;
}
room->postMessage(text(), sendText, *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), replyId(), editId(), threadId());
bool isReply = !replyId().isEmpty();
const auto replyIt = room->findInTimeline(replyId());
if (replyIt == room->historyEdge()) {
isReply = false;
}
auto content = std::make_unique<Quotient::EventContent::TextContent>(sendText, u"text/html"_s);
std::optional<Quotient::EventRelation> relatesTo = std::nullopt;
if (!threadId().isEmpty()) {
relatesTo = Quotient::EventRelation::replyInThread(threadId(), !isReply, isReply ? replyId() : threadId());
} else if (!editId().isEmpty()) {
relatesTo = Quotient::EventRelation::replace(editId());
} else if (isReply) {
relatesTo = Quotient::EventRelation::replyTo(replyId());
}
const auto type = std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result);
room->post<Quotient::RoomMessageEvent>(text(), type ? *type : Quotient::RoomMessageEvent::MsgType::Text, std::move(content), relatesTo);
clearCache();
}

View File

@@ -8,7 +8,6 @@
ColorSchemer::ColorSchemer(QObject *parent)
: QObject(parent)
, c(new KColorSchemeManager(this))
{
}
@@ -18,17 +17,17 @@ ColorSchemer::~ColorSchemer()
QAbstractItemModel *ColorSchemer::model() const
{
return c->model();
return KColorSchemeManager::instance()->model();
}
void ColorSchemer::apply(int idx)
{
c->activateScheme(c->model()->index(idx, 0));
KColorSchemeManager::instance()->activateScheme(KColorSchemeManager::instance()->model()->index(idx, 0));
}
int ColorSchemer::indexForCurrentScheme()
{
return c->indexForSchemeId(c->activeSchemeId()).row();
return KColorSchemeManager::instance()->indexForSchemeId(KColorSchemeManager::instance()->activeSchemeId()).row();
}
#include "moc_colorschemer.cpp"

View File

@@ -49,7 +49,4 @@ public:
* @sa KColorScheme
*/
Q_INVOKABLE int indexForCurrentScheme();
private:
KColorSchemeManager *c;
};

View File

@@ -423,10 +423,14 @@ void Controller::setTestMode(bool test)
void Controller::removeConnection(const QString &userId)
{
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId];
// When loadAccessTokenFromKeyChain() fails m_connectionsLoading won't have an
// entry for it so we need to check both separately.
if (m_accountsLoading.contains(userId)) {
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
}
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId];
SettingsGroup("Accounts"_ls).remove(userId);
}
}

View File

@@ -33,4 +33,28 @@ ColumnLayout {
}
}
}
FormCard.FormCard {
FormCard.FormSwitchDelegate {
id: showAccessTokenCheckbox
text: i18nc("@info", "Show Access Token")
description: i18n("This should not be shared with anyone, even other users. This token gives full access to your account.")
}
FormCard.FormTextDelegate {
text: i18nc("@info", "Access Token")
description: root.connection.accessToken
visible: showAccessTokenCheckbox.checked
contentItem.children: QQC2.Button {
text: i18nc("@action:button", "Copy access token to clipboard")
icon.name: "edit-copy"
display: QQC2.AbstractButton.IconOnly
onClicked: Clipboard.saveText(root.connection.accessToken)
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
}
}
}
}

View File

@@ -21,19 +21,22 @@ ColumnLayout {
title: i18nc("@title", "Choose Room")
}
FormCard.FormCard {
FormCard.FormComboBoxDelegate {
id: roomComboBox
text: i18n("Room")
textRole: "escapedDisplayName"
valueRole: "roomId"
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.EscapedDisplayNameRole)
model: RoomManager.roomListModel
currentIndex: 0
displayMode: FormCard.FormComboBoxDelegate.Page
Component.onCompleted: currentIndex = RoomManager.roomListModel.rowForRoom(root.room)
onCurrentValueChanged: root.room = RoomManager.roomListModel.roomByAliasOrId(roomComboBox.currentValue)
FormCard.FormButtonDelegate {
text: root.room?.displayNameForHtml ?? i18nc("@info", "No room selected")
description: i18nc("@info", "Click to choose a room");
onClicked: {
let dialog = root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
connection: root.connection,
}, {
title: i18nc("@title:dialog", "Choose Room"),
width: Kirigami.Units.gridUnit * 24
});
dialog.chosen.connect(id => root.room = root.connection.room(id))
}
}
FormCard.FormTextDelegate {
visible: root.room
text: i18n("Room Id: %1", root.room.id)
}
}

View File

@@ -37,7 +37,7 @@ FormCard.FormCardPage {
}
function openEventSource(stateKey: string): void {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateKeysModel,
allowEdit: true,
room: root.room,

View File

@@ -0,0 +1,85 @@
// SPDX-FileCopyrightText: 2024 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 "roomsortparameter.h"
namespace
{
template<typename T>
int typeCompare(T left, T right)
{
return left == right ? 0 : left > right ? 1 : -1;
}
template<>
int typeCompare<QString>(QString left, QString right)
{
return left.localeAwareCompare(right);
}
}
int RoomSortParameter::compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
switch (parameter) {
case AlphabeticalAscending:
return compareParameter<AlphabeticalAscending>(leftRoom, rightRoom);
case AlphabeticalDescending:
return compareParameter<AlphabeticalDescending>(leftRoom, rightRoom);
case HasUnread:
return compareParameter<HasUnread>(leftRoom, rightRoom);
case MostUnread:
return compareParameter<MostUnread>(leftRoom, rightRoom);
case HasHighlight:
return compareParameter<HasHighlight>(leftRoom, rightRoom);
case MostHighlights:
return compareParameter<MostHighlights>(leftRoom, rightRoom);
case LastActive:
return compareParameter<LastActive>(leftRoom, rightRoom);
default:
return false;
}
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return -typeCompare(leftRoom->displayName(), rightRoom->displayName());
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->displayName(), rightRoom->displayName());
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->contextAwareNotificationCount() > 0, rightRoom->contextAwareNotificationCount() > 0);
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->contextAwareNotificationCount(), rightRoom->contextAwareNotificationCount());
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
const auto leftHighlight = leftRoom->highlightCount() > 0 && leftRoom->contextAwareNotificationCount() > 0;
const auto rightHighlight = rightRoom->highlightCount() > 0 && rightRoom->contextAwareNotificationCount() > 0;
return typeCompare(leftHighlight, rightHighlight);
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(int(leftRoom->highlightCount()), int(rightRoom->highlightCount()));
}
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom)
{
return typeCompare(leftRoom->lastActiveTime(), rightRoom->lastActiveTime());
}

View File

@@ -0,0 +1,122 @@
// SPDX-FileCopyrightText: 2024 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 "neochatroom.h"
#include <QObject>
#include <QQmlEngine>
#include <KLocalizedString>
/**
* @class RoomSortParameter
*
* A class with the Parameter enum for room sort parameters.
*/
class RoomSortParameter : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**
* @brief Defines the available sort parameters.
*/
enum Parameter {
AlphabeticalAscending,
AlphabeticalDescending,
HasUnread,
MostUnread,
HasHighlight,
MostHighlights,
LastActive,
};
Q_ENUM(Parameter)
/**
* @brief Translate the Parameter enum value to a human readable name string.
*
* @sa Parameter
*/
static QString parameterName(Parameter parameter)
{
switch (parameter) {
case Parameter::AlphabeticalAscending:
return i18nc("As in sorting alphabetically with A first and Z last", "Alphabetical Ascending");
case Parameter::AlphabeticalDescending:
return i18nc("As in sorting alphabetically with Z first and A last", "Alphabetical Descending");
case Parameter::HasUnread:
return i18nc("As in sorting rooms with unread message above those without", "Has Unread Messages");
case Parameter::MostUnread:
return i18nc("As in sorting rooms with the most unread messages higher", "Most Unread Messages");
case Parameter::HasHighlight:
return i18nc("As in sorting rooms with highlighted message above those without", "Has Highlighted Messages");
case Parameter::MostHighlights:
return i18nc("As in sorting rooms with the most highlighted messages higher", "Most Highlighted Messages");
case Parameter::LastActive:
return i18nc("As in sorting the chat room with the newest meassage first", "Last Active");
default:
return {};
}
};
/**
* @brief Translate the Parameter enum value to a human readable description string.
*
* @sa Parameter
*/
static QString parameterDescription(Parameter parameter)
{
switch (parameter) {
case Parameter::AlphabeticalAscending:
return i18nc("@info", "Room names closer to A alphabetically are higher");
case Parameter::AlphabeticalDescending:
return i18nc("@info", "Room names closer to Z alphabetically are higher");
case Parameter::HasUnread:
return i18nc("@info", "Rooms with unread messages are higher");
case Parameter::MostUnread:
return i18nc("@info", "Rooms with the most unread message are higher");
case Parameter::HasHighlight:
return i18nc("@info", "Rooms with highlighted messages are higher");
case Parameter::MostHighlights:
return i18nc("@info", "Rooms with the most highlighted messages are higher");
case Parameter::LastActive:
return i18nc("@info", "Rooms with the newer messages are higher");
default:
return {};
}
};
/**
* @brief Compare the given parameter of the two given rooms.
*
* @return 0 if they are equal, 1 if the left is greater and -1 if the right is greater.
*
* @sa Parameter
*/
static int compareParameter(Parameter parameter, NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
private:
template<Parameter parameter>
static int compareParameter(NeoChatRoom *, NeoChatRoom *)
{
return false;
}
};
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalAscending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::AlphabeticalDescending>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostUnread>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::HasHighlight>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::MostHighlights>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);
template<>
int RoomSortParameter::compareParameter<RoomSortParameter::LastActive>(NeoChatRoom *leftRoom, NeoChatRoom *rightRoom);

View File

@@ -49,16 +49,6 @@ Q_DECLARE_FLAGS(MemberChanges, MemberChange)
Q_DECLARE_OPERATORS_FOR_FLAGS(MemberChanges)
};
QString EventHandler::id(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "id called with event set to nullptr.";
return {};
}
return !event->id().isEmpty() ? event->id() : event->transactionId();
}
QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
{
if (room == nullptr) {
@@ -70,7 +60,7 @@ QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient:
return {};
}
if (is<RoomMemberEvent>(*event) && !event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].isNull()
if (is<RoomMemberEvent>(*event) && event->unsignedJson()[QStringLiteral("prev_content")].toObject().contains("displayname"_L1)
&& event->stateKey() == event->senderId()) {
auto previousDisplayName = event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].toString().toHtmlEscaped();
if (previousDisplayName.isEmpty()) {
@@ -291,7 +281,7 @@ QString EventHandler::markdownBody(const Quotient::RoomEvent *event)
QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines)
{
if (event->isRedacted()) {
if (event->isRedacted() && !event->isStateEvent()) {
auto reason = event->redactedBecause()->reason();
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") : i18n("<i>[This message was deleted: %1]</i>", reason.toHtmlEscaped());
}
@@ -508,7 +498,7 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
return {};
}
if (event->isRedacted()) {
if (event->isRedacted() && !event->isStateEvent()) {
return i18n("<i>[This message was deleted]</i>");
}
@@ -834,31 +824,6 @@ QVariantMap EventHandler::getMediaInfoFromTumbnail(const NeoChatRoom *room, cons
return thumbnailInfo;
}
bool EventHandler::hasReply(const Quotient::RoomEvent *event, bool showFallbacks)
{
if (event == nullptr) {
qCWarning(EventHandling) << "hasReply called with event set to nullptr.";
return false;
}
const auto relations = event->contentPart<QJsonObject>("m.relates_to"_ls);
if (!relations.isEmpty()) {
const bool hasReplyRelation = relations.contains("m.in_reply_to"_ls);
bool isFallingBack = relations["is_falling_back"_ls].toBool();
return hasReplyRelation && (showFallbacks ? true : !isFallingBack);
}
return false;
}
QString EventHandler::replyId(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "replyId called with event set to nullptr.";
return {};
}
return event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
}
Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event)
{
if (room == nullptr) {
@@ -877,38 +842,6 @@ Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Qu
}
}
bool EventHandler::isThreaded(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "isThreaded called with event set to nullptr.";
return false;
}
return (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|| (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
}
QString EventHandler::threadRoot(const Quotient::RoomEvent *event)
{
if (event == nullptr) {
qCWarning(EventHandling) << "threadRoot called with event set to nullptr.";
return {};
}
// Get the thread root ID from m.relates_to if it exists.
if (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
return event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString();
}
// For thread root events they have an m.relations in the unsigned part with a m.thread object.
// If so return the event ID as it is the root.
if (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
return id(event);
}
return {};
}
float EventHandler::latitude(const Quotient::RoomEvent *event)
{
if (event == nullptr) {

View File

@@ -37,14 +37,6 @@ class NeoChatRoom;
class EventHandler
{
public:
/**
* @brief Return the ID of the event.
*
* Returns the transaction ID if the Matrix ID is empty, which may be the case
* for a pending event.
*/
static QString id(const Quotient::RoomEvent *event);
/**
* @brief Get the display name of the event author.
*
@@ -220,20 +212,6 @@ public:
*/
static QVariantMap mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event);
/**
* @brief Whether the event is a reply to another in the timeline.
*
* @param showFallbacks whether message that have is_falling_back set true should
* show the fallback reply. Leave true for non-threaded
* timelines.
*/
static bool hasReply(const Quotient::RoomEvent *event, bool showFallbacks = true);
/**
* @brief Return the Matrix ID of the event replied to.
*/
static QString replyId(const Quotient::RoomEvent *event);
/**
* @brief Get the author of the event replied to in context of the room.
*
@@ -249,20 +227,6 @@ public:
*/
static Quotient::RoomMember replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event);
/**
* @brief Whether the message is part of a thread.
*
* i.e. There is a rel_type of m.thread.
*/
static bool isThreaded(const Quotient::RoomEvent *event);
/**
* @brief Return the Matrix ID of the thread's root message.
*
* Empty if this not part of a thread.
*/
static QString threadRoot(const Quotient::RoomEvent *event);
/**
* @brief Return the latitude for the event.
*

View File

@@ -4,11 +4,12 @@
#include "linkpreviewer.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/authed-content-repo.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "utils.h"
using namespace Quotient;
@@ -61,7 +62,13 @@ void LinkPreviewer::loadUrlPreview()
if (conn == nullptr) {
return;
}
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url);
BaseJob *job = nullptr;
if (conn->supportedMatrixSpecVersions().contains("v1.11"_L1)) {
job = conn->callApi<GetUrlPreviewAuthedJob>(m_url);
} else {
QT_IGNORE_DEPRECATIONS(job = conn->callApi<GetUrlPreviewJob>(m_url);)
}
connect(job, &BaseJob::success, this, [this, job, conn]() {
const auto json = job->jsonData();

View File

@@ -25,8 +25,7 @@ void LoginHelper::init()
m_connection = new NeoChatConnection();
m_matrixId = QString();
m_password = QString();
m_deviceName = QStringLiteral("NeoChat %1 %2 %3 %4")
.arg(QSysInfo::machineHostName(), QSysInfo::productType(), QSysInfo::productVersion(), QSysInfo::currentCpuArchitecture());
m_deviceName = QStringLiteral("NeoChat");
m_supportsSso = false;
m_supportsPassword = false;
m_ssoUrl = QUrl();

View File

@@ -271,7 +271,6 @@ int main(int argc, char *argv[])
#endif
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
if (parser.isSet("ignore-ssl-errors"_ls)) {

View File

@@ -109,11 +109,10 @@ QList<ActionsModel::Action> actions{
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
}
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
EventRelation relatesTo =
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
room->post<Quotient::RoomMessageEvent>("/rainbow %1"_L1.arg(text), MessageEventType::Text, std::move(content), relatesTo);
return QString();
},
false,
@@ -129,11 +128,10 @@ QList<ActionsModel::Action> actions{
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
}
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Emote,
chatBarCache->replyId(),
chatBarCache->editId());
auto content = std::make_unique<Quotient::EventContent::TextContent>(rainbowText, u"text/html"_s);
EventRelation relatesTo =
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
room->post<Quotient::RoomMessageEvent>(u"/rainbow %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
return QString();
},
false,
@@ -144,7 +142,7 @@ QList<ActionsModel::Action> actions{
Action{
QStringLiteral("plain"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
room->postPlainText(text.toHtmlEscaped());
return QString();
},
false,
@@ -156,11 +154,10 @@ QList<ActionsModel::Action> actions{
QStringLiteral("spoiler"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
room->postMessage(QStringLiteral("/spoiler %1").arg(text),
QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
auto content = std::make_unique<Quotient::EventContent::TextContent>(u"<span data-mx-spoiler>%1</span>"_s.arg(text), u"text/html"_s);
EventRelation relatesTo =
chatBarCache->isReplying() ? EventRelation::replyTo(chatBarCache->replyId()) : EventRelation::replace(chatBarCache->editId());
room->post<Quotient::RoomMessageEvent>(u"/spoiler %1"_s.arg(text), MessageEventType::Text, std::move(content), relatesTo);
return QString();
},
false,
@@ -605,15 +602,15 @@ bool ActionsModel::handleQuickEditAction(NeoChatRoom *room, const QString &messa
if (eventRelation && eventRelation->type == "m.replace"_L1) {
replaceId = eventRelation->eventId;
}
std::unique_ptr<EventContent::TextContent> content = nullptr;
if (flags == "/g"_L1) {
room->postHtmlMessage(messageText, originalString.replace(regex, replacement), event->msgtype(), {}, replaceId);
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
} else {
room->postHtmlMessage(messageText,
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
event->msgtype(),
{},
replaceId);
content = std::make_unique<Quotient::EventContent::TextContent>(originalString.replace(regex, replacement), u"text/html"_s);
}
Quotient::EventRelation relatesTo = Quotient::EventRelation::replace(replaceId);
room->post<Quotient::RoomMessageEvent>(messageText, event->msgtype(), std::move(content), relatesTo);
return true;
}
}

View File

@@ -305,7 +305,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return component.attributes;
}
if (role == EventIdRole) {
return EventHandler::id(event.first);
return event.first->displayId();
}
if (role == TimeRole) {
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) {
@@ -348,7 +348,9 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
}
if (role == ReplyEventIdRole) {
return EventHandler::replyId(event.first);
if (const auto roomMessageEvent = eventCast<const RoomMessageEvent>(event.first)) {
return roomMessageEvent->replyEventId();
}
}
if (role == ReplyAuthorRole) {
return QVariant::fromValue(EventHandler::replyAuthor(m_room, event.first));
@@ -456,8 +458,8 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
QList<MessageComponent> newComponents;
if (eventCast<const Quotient::RoomMessageEvent>(event.first)
&& eventCast<const Quotient::RoomMessageEvent>(event.first)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
if (roomMessageEvent && roomMessageEvent->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
return newComponents;
}
@@ -482,7 +484,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
}
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
if (isThreading && !EventHandler::isThreaded(event.first)) {
if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded()) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
}
@@ -496,7 +498,11 @@ void MessageContentModel::updateReplyModel()
return;
}
if (!EventHandler::hasReply(event.first) || (EventHandler::isThreaded(event.first) && NeoChatConfig::self()->threads())) {
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event.first);
if (roomMessageEvent == nullptr) {
return;
}
if (!roomMessageEvent->isReply() || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
if (m_replyModel) {
delete m_replyModel;
}
@@ -507,7 +513,7 @@ void MessageContentModel::updateReplyModel()
return;
}
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event.first), true, false, this);
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(), true, false, this);
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
@@ -606,6 +612,7 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
}
}
}
[[fallthrough]];
default:
return {MessageComponent{type, QString(), {}}};
}

View File

@@ -501,7 +501,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return EventStatus::Hidden;
}
if (EventHandler::isThreaded(&evt) && EventHandler::threadRoot(&evt) != EventHandler::id(&evt) && NeoChatConfig::threads()) {
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
if (roomMessageEvent && roomMessageEvent->isThreaded() && roomMessageEvent->threadRootEventId() != evt.id() && NeoChatConfig::threads()) {
return EventStatus::Hidden;
}
@@ -509,7 +510,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == EventIdRole) {
return EventHandler::id(&evt);
return evt.displayId();
}
if (role == ProgressInfoRole) {
@@ -534,11 +535,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == IsThreadedRole) {
return EventHandler::isThreaded(&evt);
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt)) {
return roomMessageEvent->isThreaded();
}
return {};
}
if (role == ThreadRootRole) {
return EventHandler::threadRoot(&evt);
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&evt);
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
return roomMessageEvent->threadRootEventId();
}
return {};
}
if (role == ShowSectionRole) {
@@ -654,8 +662,10 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event, boo
}
}
if (EventHandler::isThreaded(event) && !m_threadModels.contains(EventHandler::threadRoot(event))) {
m_threadModels[EventHandler::threadRoot(event)] = QSharedPointer<ThreadModel>(new ThreadModel(EventHandler::threadRoot(event), m_currentRoom));
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
if (roomMessageEvent && roomMessageEvent->isThreaded() && !m_threadModels.contains(roomMessageEvent->threadRootEventId())) {
m_threadModels[roomMessageEvent->threadRootEventId()] =
QSharedPointer<ThreadModel>(new ThreadModel(roomMessageEvent->threadRootEventId(), m_currentRoom));
}
// ReadMarkerModel handles updates to add and remove markers, we only need to

View File

@@ -8,6 +8,23 @@
using namespace Quotient;
class NeoChatQueryPublicRoomsJob : public QueryPublicRoomsJob
{
public:
explicit NeoChatQueryPublicRoomsJob(const QString &server = {},
std::optional<int> limit = std::nullopt,
const QString &since = {},
const std::optional<Filter> &filter = std::nullopt,
std::optional<bool> includeAllNetworks = std::nullopt,
const QString &thirdPartyInstanceId = {})
: QueryPublicRoomsJob(server, limit, since, filter, includeAllNetworks, thirdPartyInstanceId)
{
// TODO Remove once we can use libQuotient's job directly
// This is to make libQuotient happy about results not having the "chunk" field
setExpectedKeys({});
}
};
PublicRoomListModel::PublicRoomListModel(QObject *parent)
: QAbstractListModel(parent)
{
@@ -153,6 +170,8 @@ void PublicRoomListModel::next(int limit)
if (m_connection == nullptr || limit < 1) {
return;
}
m_redirectedText.clear();
Q_EMIT redirectedChanged();
if (job) {
qCDebug(PublicRoomList) << "Other job running, ignore";
@@ -163,7 +182,7 @@ void PublicRoomListModel::next(int limit)
if (m_showOnlySpaces) {
roomTypes += QLatin1String("m.space");
}
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
job = m_connection->callApi<NeoChatQueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
Q_EMIT searchingChanged();
connect(job, &BaseJob::finished, this, [this] {
@@ -181,6 +200,9 @@ void PublicRoomListModel::next(int limit)
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
rooms.append(job->chunk());
this->endInsertRows();
} else if (job->error() == BaseJob::ContentAccessError) {
m_redirectedText = job->jsonData()[u"error"_s].toString();
Q_EMIT redirectedChanged();
}
this->job = nullptr;
@@ -302,4 +324,9 @@ bool PublicRoomListModel::searching() const
return job != nullptr;
}
QString PublicRoomListModel::redirectedText() const
{
return m_redirectedText;
}
#include "moc_publicroomlistmodel.cpp"

View File

@@ -52,6 +52,11 @@ class PublicRoomListModel : public QAbstractListModel
*/
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
/**
* @brief The text returned by the server after redirection
*/
Q_PROPERTY(QString redirectedText READ redirectedText NOTIFY redirectedChanged)
public:
/**
* @brief Defines the model roles.
@@ -113,6 +118,8 @@ public:
*/
Q_INVOKABLE void search(int limit = 50);
QString redirectedText() const;
private:
QPointer<NeoChatConnection> m_connection = nullptr;
QString m_server;
@@ -135,6 +142,7 @@ private:
QList<Quotient::PublicRoomsChunk> rooms;
Quotient::QueryPublicRoomsJob *job = nullptr;
QString m_redirectedText;
Q_SIGNALS:
void connectionChanged();
@@ -142,4 +150,5 @@ Q_SIGNALS:
void searchTextChanged();
void showOnlySpacesChanged();
void searchingChanged();
void redirectedChanged();
};

View File

@@ -129,7 +129,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
refresh(room);
});
connect(room, &Room::addedMessages, this, [this, room] {
refresh(room, {SubtitleTextRole, LastActiveTimeRole});
refresh(room, {SubtitleTextRole});
});
connect(room, &Room::pendingEventMerged, this, [this, room] {
refresh(room, {SubtitleTextRole});
@@ -229,9 +229,6 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
if (role == HasHighlightNotificationsRole) {
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
}
if (role == LastActiveTimeRole) {
return room->lastActiveTime();
}
if (role == JoinStateRole) {
if (!room->successorId().isEmpty()) {
return QStringLiteral("upgraded");
@@ -291,7 +288,6 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
roles[CategoryRole] = "category";
roles[ContextNotificationCountRole] = "contextNotificationCount";
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
roles[LastActiveTimeRole] = "lastActiveTime";
roles[JoinStateRole] = "joinState";
roles[CurrentRoomRole] = "currentRoom";
roles[SubtitleTextRole] = "subtitleText";

View File

@@ -43,7 +43,6 @@ public:
CategoryRole, /**< The room category, e.g favourite. */
ContextNotificationCountRole, /**< The context aware notification count for the room. */
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
JoinStateRole, /**< The local user's join state in the room. */
CurrentRoomRole, /**< The room object for the room. */
SubtitleTextRole, /**< The text to show as the room subtitle. */

View File

@@ -176,7 +176,7 @@ void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
refreshRoomRoles(room);
});
connect(room, &Room::addedMessages, this, [this, room] {
refreshRoomRoles(room, {SubtitleTextRole, LastActiveTimeRole});
refreshRoomRoles(room, {SubtitleTextRole});
});
connect(room, &Room::pendingEventMerged, this, [this, room] {
refreshRoomRoles(room, {SubtitleTextRole});
@@ -274,7 +274,6 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
roles[CategoryRole] = "category";
roles[ContextNotificationCountRole] = "contextNotificationCount";
roles[HasHighlightNotificationsRole] = "hasHighlightNotifications";
roles[LastActiveTimeRole] = "lastActiveTime";
roles[JoinStateRole] = "joinState";
roles[CurrentRoomRole] = "currentRoom";
roles[SubtitleTextRole] = "subtitleText";
@@ -284,8 +283,6 @@ QHash<int, QByteArray> RoomTreeModel::roleNames() const
roles[IsDirectChat] = "isDirectChat";
roles[DelegateTypeRole] = "delegateType";
roles[IconRole] = "icon";
roles[AttentionRole] = "attention";
roles[FavouriteRole] = "favourite";
roles[RoomTypeRole] = "roomType";
return roles;
}
@@ -341,9 +338,6 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
if (role == HasHighlightNotificationsRole) {
return room->highlightCount() > 0 && room->contextAwareNotificationCount() > 0;
}
if (role == LastActiveTimeRole) {
return room->lastActiveTime();
}
if (role == JoinStateRole) {
if (!room->successorId().isEmpty()) {
return QStringLiteral("upgraded");
@@ -380,12 +374,6 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
if (role == DelegateTypeRole) {
return QStringLiteral("normal");
}
if (role == AttentionRole) {
return room->notificationCount() + room->highlightCount() > 0;
}
if (role == FavouriteRole) {
return room->isFavourite();
}
if (role == RoomTypeRole) {
if (room->creation()) {
return room->creation()->contentPart<QString>("type"_L1);

View File

@@ -36,7 +36,6 @@ public:
CategoryRole, /**< The room category, e.g favourite. */
ContextNotificationCountRole, /**< The context aware notification count for the room. */
HasHighlightNotificationsRole, /**< Whether there are any highlight notifications. */
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
JoinStateRole, /**< The local user's join state in the room. */
CurrentRoomRole, /**< The room object for the room. */
SubtitleTextRole, /**< The text to show as the room subtitle. */
@@ -48,8 +47,6 @@ public:
IsDirectChat, /**< Whether this room is a direct chat. */
DelegateTypeRole,
IconRole,
AttentionRole, /**< Whether there are any notifications. */
FavouriteRole, /**< Whether the room is favourited. */
RoomTypeRole, /**< The room's type. */
};
Q_ENUM(EventRoles)

View File

@@ -108,11 +108,17 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
case HighlightRole:
return EventHandler::isHighlighted(m_room, &event);
case EventIdRole:
return EventHandler::id(&event);
return event.displayId();
case IsThreadedRole:
return EventHandler::isThreaded(&event);
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event)) {
return roomMessageEvent->isThreaded();
}
return {};
case ThreadRootRole:
return EventHandler::threadRoot(&event);
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event); roomMessageEvent->isThreaded()) {
return roomMessageEvent->threadRootEventId();
}
return {};
case ContentModelRole: {
if (!event.isStateEvent()) {
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, event.id()));

View File

@@ -6,6 +6,7 @@
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "neochatroomtype.h"
#include "roommanager.h"
#include "roomtreemodel.h"
@@ -44,58 +45,49 @@ SortFilterRoomTreeModel::SortFilterRoomTreeModel(RoomTreeModel *sourceModel, QOb
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
{
m_sortOrder = sortOrder;
if (sortOrder == SortFilterRoomTreeModel::Alphabetical) {
setSortRole(RoomTreeModel::DisplayNameRole);
} else if (sortOrder == SortFilterRoomTreeModel::Activity) {
setSortRole(RoomTreeModel::LastActiveTimeRole);
}
invalidate();
}
static const QVector<RoomTreeModel::EventRoles> alphabeticalSortPriorities{
static const QVector<RoomSortParameter::Parameter> alphabeticalSortPriorities{
// Does exactly what it says on the tin.
RoomTreeModel::DisplayNameRole,
RoomSortParameter::AlphabeticalAscending,
};
static const QVector<RoomTreeModel::EventRoles> activitySortPriorities{
// Anything useful at the top, quiet rooms at the bottom
RoomTreeModel::AttentionRole,
// Organize by highlights, notifications, unread favorites, all other unread, in that order
RoomTreeModel::HasHighlightNotificationsRole,
RoomTreeModel::ContextNotificationCountRole,
RoomTreeModel::FavouriteRole,
// Finally sort by last activity time
RoomTreeModel::LastActiveTimeRole,
static const QVector<RoomSortParameter::Parameter> activitySortPriorities{
RoomSortParameter::HasHighlight,
RoomSortParameter::MostHighlights,
RoomSortParameter::HasUnread,
RoomSortParameter::MostUnread,
RoomSortParameter::LastActive,
};
bool SortFilterRoomTreeModel::roleCmp(const QVariant &sortLeft, const QVariant &sortRight) const
{
switch (sortLeft.typeId()) {
case QMetaType::Bool:
return (sortLeft == sortRight) ? false : sortLeft.toBool();
case QMetaType::QString:
return sortLeft.toString() < sortRight.toString();
case QMetaType::Int:
return sortLeft.toInt() > sortRight.toInt();
case QMetaType::QDateTime:
return sortLeft.toDateTime() > sortRight.toDateTime();
default:
return false;
}
}
static const QVector<RoomSortParameter::Parameter> lastMessageSortPriorities{
RoomSortParameter::LastActive,
};
bool SortFilterRoomTreeModel::prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities,
bool SortFilterRoomTreeModel::prioritiesCmp(const QVector<RoomSortParameter::Parameter> &priorities,
const QModelIndex &source_left,
const QModelIndex &source_right) const
{
for (RoomTreeModel::EventRoles sortRole : priorities) {
const auto sortLeft = sourceModel()->data(source_left, sortRole);
const auto sortRight = sourceModel()->data(source_right, sortRole);
if (sortLeft != sortRight) {
return roleCmp(sortLeft, sortRight);
const auto treeModel = dynamic_cast<RoomTreeModel *>(sourceModel());
if (treeModel == nullptr) {
return false;
}
const auto leftRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_left.data(RoomTreeModel::RoomIdRole).toString()));
const auto rightRoom = dynamic_cast<NeoChatRoom *>(treeModel->connection()->room(source_right.data(RoomTreeModel::RoomIdRole).toString()));
if (leftRoom == nullptr || rightRoom == nullptr) {
return false;
}
for (auto sortRole : priorities) {
auto result = RoomSortParameter::compareParameter(sortRole, leftRoom, rightRoom);
if (result != 0) {
return result > 0;
}
}
return QSortFilterProxyModel::lessThan(source_left, source_right);
return false;
}
bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
@@ -110,8 +102,9 @@ bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QMo
return prioritiesCmp(alphabeticalSortPriorities, source_left, source_right);
case SortFilterRoomTreeModel::Activity:
return prioritiesCmp(activitySortPriorities, source_left, source_right);
case SortFilterRoomTreeModel::LastMessage:
return prioritiesCmp(lastMessageSortPriorities, source_left, source_right);
}
return QSortFilterProxyModel::lessThan(source_left, source_right);
}

View File

@@ -7,6 +7,7 @@
#include <QQmlEngine>
#include <QSortFilterProxyModel>
#include "enums/roomsortparameter.h"
#include "models/roomtreemodel.h"
/**
@@ -53,6 +54,7 @@ public:
enum RoomSortOrder {
Alphabetical,
Activity,
LastMessage,
};
Q_ENUM(RoomSortOrder)
@@ -104,6 +106,5 @@ private:
QString m_filterText;
QString m_activeSpaceId;
bool roleCmp(const QVariant &left, const QVariant &right) const;
bool prioritiesCmp(const QVector<RoomTreeModel::EventRoles> &priorities, const QModelIndex &left, const QModelIndex &right) const;
bool prioritiesCmp(const QVector<RoomSortParameter::Parameter> &priorities, const QModelIndex &left, const QModelIndex &right) const;
};

View File

@@ -25,7 +25,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
connect(room, &Quotient::Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) {
addNewEvent(event);
addModels();
}
@@ -34,7 +34,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
connect(room, &Quotient::Room::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
for (const auto &event : events) {
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
if (roomEvent->isThreaded() && roomEvent->threadRootEventId() == m_threadRootId) {
addNewEvent(roomEvent);
}
}

View File

@@ -10,6 +10,8 @@
#include <QTemporaryFile>
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/eventrelation.h>
#include <Quotient/events/roommessageevent.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h>
#include <qcoro/qcorosignal.h>
@@ -482,119 +484,6 @@ QString msgTypeToString(MessageEventType msgType)
}
}
void NeoChatRoom::postMessage(const QString &rawText,
const QString &text,
MessageEventType type,
const QString &replyEventId,
const QString &relateToEventId,
const QString &threadRootId,
const QString &fallbackId)
{
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId, threadRootId, fallbackId);
}
void NeoChatRoom::postHtmlMessage(const QString &text,
const QString &html,
MessageEventType type,
const QString &replyEventId,
const QString &relateToEventId,
const QString &threadRootId,
const QString &fallbackId)
{
bool isReply = !replyEventId.isEmpty();
bool isEdit = !relateToEventId.isEmpty();
bool isThread = !threadRootId.isEmpty();
const auto replyIt = findInTimeline(replyEventId);
if (replyIt == historyEdge()) {
isReply = false;
}
if (isThread) {
bool isFallingBack = !fallbackId.isEmpty();
QString replyEventId = isFallingBack ? fallbackId : QString();
if (isReply) {
isFallingBack = false;
replyEventId = EventHandler::id(replyIt->get());
}
// If we are not replying and there is no fallback ID it means a new thread
// is being created.
if (!isFallingBack && !isReply) {
isFallingBack = true;
replyEventId = threadRootId;
}
// clang-format off
QJsonObject json{
{"msgtype"_ls, msgTypeToString(type)},
{"body"_ls, text},
{"format"_ls, "org.matrix.custom.html"_ls},
{"m.relates_to"_ls,
QJsonObject {
{"rel_type"_ls, "m.thread"_ls},
{"event_id"_ls, threadRootId},
{"is_falling_back"_ls, isFallingBack},
{"m.in_reply_to"_ls,
QJsonObject {
{"event_id"_ls, replyEventId}
}
}
}
},
{"formatted_body"_ls, html}
};
// clang-format on
postJson("m.room.message"_ls, json);
return;
}
if (isEdit) {
QJsonObject json{
{"type"_ls, "m.room.message"_ls},
{"msgtype"_ls, msgTypeToString(type)},
{"body"_ls, "* %1"_ls.arg(text)},
{"format"_ls, "org.matrix.custom.html"_ls},
{"formatted_body"_ls, html},
{"m.new_content"_ls,
QJsonObject{{"body"_ls, text}, {"msgtype"_ls, msgTypeToString(type)}, {"format"_ls, "org.matrix.custom.html"_ls}, {"formatted_body"_ls, html}}},
{"m.relates_to"_ls, QJsonObject{{"rel_type"_ls, "m.replace"_ls}, {"event_id"_ls, relateToEventId}}}};
postJson("m.room.message"_ls, json);
return;
}
if (isReply) {
const auto &replyEvt = **replyIt;
// clang-format off
QJsonObject json{
{"msgtype"_ls, msgTypeToString(type)},
{"body"_ls, "> <%1> %2\n\n%3"_ls.arg(replyEvt.senderId(), EventHandler::plainBody(this, &replyEvt), text)},
{"format"_ls, "org.matrix.custom.html"_ls},
{"m.relates_to"_ls,
QJsonObject {
{"m.in_reply_to"_ls,
QJsonObject {
{"event_id"_ls, replyEventId}
}
}
}
},
{"formatted_body"_ls,
"<mx-reply><blockquote><a href=\"https://matrix.to/#/%1/%2\">In reply to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br>%5</blockquote></mx-reply>%6"_ls.arg(id(), replyEventId, replyEvt.senderId(), replyEvt.senderId(), EventHandler::richBody(this, &replyEvt), html)
}
};
// clang-format on
postJson("m.room.message"_ls, json);
return;
}
Room::postHtmlMessage(text, html, type);
}
void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction)
{
if (eventId.isEmpty() || reaction.isEmpty()) {

View File

@@ -691,40 +691,6 @@ public Q_SLOTS:
*/
void sendTypingNotification(bool isTyping);
/**
* @brief Send a message to the room.
*
* @param rawText the text as it was typed.
* @param cleanedText the text marked up as html.
* @param type the type of message being sent.
* @param replyEventId the id of the message being replied to if a reply.
* @param relateToEventId the id of the message being edited if an edit.
*/
void postMessage(const QString &rawText,
const QString &cleanedText,
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
const QString &replyEventId = QString(),
const QString &relateToEventId = QString(),
const QString &threadRootId = QString(),
const QString &fallbackId = QString());
/**
* @brief Send an html message to the room.
*
* @param text the text as it was typed.
* @param html the text marked up as html.
* @param type the type of message being sent.
* @param replyEventId the id of the message being replied to if a reply.
* @param relateToEventId the id of the message being edited if an edit.
*/
void postHtmlMessage(const QString &text,
const QString &html,
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
const QString &replyEventId = QString(),
const QString &relateToEventId = QString(),
const QString &threadRootId = QString(),
const QString &fallbackId = QString());
/**
* @brief Set the room avatar.
*/

View File

@@ -161,3 +161,5 @@ QUrl NeochatRoomMember::avatarUrl() const
return m_room->member(m_memberId).avatarUrl();
}
#include "moc_neochatroommember.cpp"

View File

@@ -248,7 +248,9 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
connect(replyAction.get(), &KNotificationReplyAction::replied, this, [room, replyEventId](const QString &text) {
TextHandler textHandler;
textHandler.setData(text);
room->postMessage(text, textHandler.handleSendText(), RoomMessageEvent::MsgType::Text, replyEventId, QString());
auto content = std::make_unique<Quotient::EventContent::TextContent>(textHandler.handleSendText(), u"text/html"_s);
EventRelation relatesTo = EventRelation::replyTo(replyEventId);
room->post<Quotient::RoomMessageEvent>(text, MessageEventType::Text, std::move(content), relatesTo);
});
notification->setReplyAction(std::move(replyAction));
}

View File

@@ -168,9 +168,8 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
ownAnswers.insert(0, answerId);
}
auto response = new PollResponseEvent(eventId, ownAnswers);
const auto &response = room->post<PollResponseEvent>(eventId, ownAnswers);
handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime());
room->postEvent(response);
}
bool PollHandler::hasEnded() const

View File

@@ -20,7 +20,7 @@ QQC2.Menu {
margins: Kirigami.Units.smallSpacing
QQC2.MenuItem {
text: i18nc("@action:button", "Show QR code")
text: i18nc("@action:button", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
onTriggered: {
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
@@ -37,7 +37,7 @@ QQC2.Menu {
}
}
QQC2.MenuItem {
text: i18n("Edit this account")
text: i18n("Edit This Account")
icon.name: "document-edit"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.settings', 'AccountEditorPage'), {
connection: root.connection
@@ -46,7 +46,7 @@ QQC2.Menu {
})
}
QQC2.MenuItem {
text: i18n("Notification settings")
text: i18n("Notification Settings")
icon.name: "notifications"
onTriggered: {
NeoChatSettingsView.open('notifications');
@@ -60,7 +60,7 @@ QQC2.Menu {
}
}
QQC2.MenuItem {
text: i18n("Open developer tools")
text: i18n("Open Developer Tools")
icon.name: "tools"
visible: NeoChatConfig.developerTools
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
@@ -80,7 +80,7 @@ QQC2.Menu {
})
}
QQC2.MenuItem {
text: i18nc("@action:inmenu", "Verify this Device")
text: i18nc("@action:inmenu", "Verify This Device")
icon.name: "security-low"
onTriggered: root.connection.startSelfVerification()
enabled: Controller.csSupported

View File

@@ -23,7 +23,11 @@ SearchPage {
model: RoomManager.sortFilterRoomListModel
modelDelegate: RoomDelegate {
onClicked: root.chosen(currentRoom.id)
onClicked: {
root.chosen(currentRoom.id);
root.closeDialog();
}
connection: root.connection
openOnClick: false
}
}

View File

@@ -57,6 +57,11 @@ Loader {
*/
property string selectedText: ""
/**
* @brief The link the user has currently hovered.
*/
property string hoveredLink: ""
/**
* @brief The list of menu item actions that have sub-actions.
*

View File

@@ -50,6 +50,8 @@ SearchPage {
signal roomSelected(string roomId, string displayName, url avatarUrl, string alias, string topic, int memberCount, bool isJoined)
title: i18nc("@action:title", "Explore Rooms")
customPlaceholderText: publicRoomListModel.redirectedText
customPlaceholderIcon: "data-warning"
Component.onCompleted: focusSearch()
@@ -93,6 +95,7 @@ SearchPage {
activeFocusOnTab: false // We handle moving to this item via up/down arrows, otherwise the tab order is wacky
text: i18n("Enter a Room Manually")
visible: publicRoomListModel.redirectedText.length === 0
icon.name: "compass"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2

View File

@@ -20,8 +20,15 @@ Kirigami.ApplicationWindow {
property bool initialized: false
title: NeoChatConfig.windowTitleFocus ? activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "") : "NeoChat"
title: {
if (NeoChatConfig.windowTitleFocus) {
return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "");
} else if (RoomManager.currentRoom) {
return RoomManager.currentRoom.displayName;
} else {
return Application.displayName;
}
}
minimumWidth: Kirigami.Units.gridUnit * 20
minimumHeight: Kirigami.Units.gridUnit * 15
@@ -85,7 +92,7 @@ Kirigami.ApplicationWindow {
target: RoomManager
function onCurrentRoomChanged() {
if (RoomManager.currentRoom && pageStack.depth <= 1 && initialized && Kirigami.Settings.isMobile) {
if (RoomManager.currentRoom && pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
connection: root.connection
});
@@ -300,7 +307,7 @@ Kirigami.ApplicationWindow {
target: ShareHandler
function onTextChanged(): void {
if (root.connection && ShareHandler.text.length > 0) {
handleShare();
root.handleShare();
}
}
}

View File

@@ -63,6 +63,12 @@ DelegateContextMenu {
separator: true
},
DelegateContextMenu.RemoveMessageAction {},
Kirigami.Action {
text: i18nc("@action:inmenu", "Copy Link Address")
icon.name: "edit-copy"
visible: root.hoveredLink.length > 0
onTriggered: Clipboard.saveText(root.hoveredLink)
},
Kirigami.Action {
text: i18nc("@action:inmenu", "Copy Text")
icon.name: "edit-copy"

View File

@@ -25,6 +25,7 @@ Delegates.RoundedItemDelegate {
required property string subtitleText
required property string displayName
property bool openOnClick: true
property bool showConfigure: true
property bool collapsed: false
@@ -35,8 +36,10 @@ Delegates.RoundedItemDelegate {
Accessible.onPressAction: clicked()
onClicked: {
RoomManager.resolveResource(currentRoom.id);
pageStack.currentIndex = 1;
if (root.openOnClick) {
RoomManager.resolveResource(currentRoom.id);
pageStack.currentIndex = 1;
}
}
onPressAndHold: createRoomListContextMenu()

View File

@@ -19,6 +19,7 @@ Kirigami.OverlayDrawer {
required property NeoChatConnection connection
width: actualWidth
interactive: modal
readonly property int minWidth: Kirigami.Units.gridUnit * 15
readonly property int maxWidth: Kirigami.Units.gridUnit * 25

View File

@@ -325,7 +325,7 @@ Kirigami.Page {
QtObject {
id: _private
property int currentWidth: NeoChatConfig.collapsed ? collapsedSize : defaultWidth
readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
readonly property int defaultWidth: Kirigami.Units.gridUnit * 15
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
readonly property int collapsedSize: Kirigami.Units.gridUnit + (NeoChatConfig.compactRoomList ? 0 : Kirigami.Units.largeSpacing * 2) + Kirigami.Units.largeSpacing * 2 + (scrollView.QQC2.ScrollBar.vertical.visible ? scrollView.QQC2.ScrollBar.vertical.width : 0)
}

View File

@@ -163,6 +163,23 @@ Kirigami.Page {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: NeoChatConfig.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
Image {
visible: !NeoChatConfig.compactLayout && !NeoChatConfig.blur
source: "qrc:/qt/qml/org/kde/neochat/timeline/images/chat-page-background.svg"
anchors.fill: parent
fillMode: Image.Tile
horizontalAlignment: Image.AlignLeft
verticalAlignment: Image.AlignTop
function isDarkColor(background: color): bool {
const temp = Qt.darker(background, 1);
const darkness = 1 - (0.299 * temp.r + 0.587 * temp.g + 0.114 * temp.b);
return temp.a > 0 && darkness >= 0.4;
}
opacity: isDarkColor(Kirigami.Theme.backgroundColor) ? 0.2 : 1
}
}
footer: Loader {
@@ -245,9 +262,10 @@ Kirigami.Page {
});
}
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, isThread) {
function onShowMessageMenu(eventId, author, messageComponentType, plainText, htmlText, selectedText, hoveredLink, isThread) {
const contextMenu = messageDelegateContextMenu.createObject(root, {
selectedText: selectedText,
hoveredLink: hoveredLink,
author: author,
eventId: eventId,
messageComponentType: messageComponentType,

View File

@@ -80,6 +80,17 @@ Kirigami.ScrollablePage {
*/
property bool showSearchButton: true
/**
* @brief Message to be shown in a custom placeholder.
* The custom placeholder will be shown if the text is not empty
*/
property alias customPlaceholderText: customPlaceholder.text
/**
* @brief icon for the custom placeholder
*/
property string customPlaceholderIcon: ""
/**
* @brief Force the search field to be focussed.
*/
@@ -167,18 +178,25 @@ Kirigami.ScrollablePage {
Kirigami.PlaceholderMessage {
id: noSearchMessage
anchors.centerIn: parent
visible: searchField.text.length === 0 && listView.count === 0
visible: searchField.text.length === 0 && listView.count === 0 && customPlaceholder.text.length === 0
}
Kirigami.PlaceholderMessage {
id: noResultMessage
anchors.centerIn: parent
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && customPlaceholder.text.length === 0
}
Kirigami.PlaceholderMessage {
id: customPlaceholder
anchors.centerIn: parent
visible: searchField.text.length > 0 && listView.count === 0 && !root.model.searching && text.length > 0
icon.name: root.customPlaceholderIcon
}
Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching
visible: searchField.text.length > 0 && listView.count === 0 && root.model.searching && customPlaceholder.text.length === 0
}
Keys.onUpPressed: {

View File

@@ -131,7 +131,7 @@ QQC2.Control {
text: i18nc("@button View all one-on-one chats with your friends.", "Friends")
contentItem: Kirigami.Icon {
source: "system-users"
source: "system-users-symbolic"
QQC2.Label {
id: directChatNotificationCountLabel
@@ -200,7 +200,6 @@ QQC2.Control {
activeFocusOnTab: true
onSelected: {
RoomManager.resolveResource(spaceDelegate.roomId);
RoomManager.currentSpace = spaceDelegate.roomId;
}
checked: RoomManager.currentSpace === roomId

View File

@@ -93,10 +93,7 @@ void Registration::registerAccount()
auto matrixId = "@%1:%2"_ls.arg(m_username, m_homeserver);
connection->resolveServer(matrixId);
auto displayName = "NeoChat %1 %2 %3 %4"_ls.arg(QSysInfo::machineHostName(),
QSysInfo::productType(),
QSysInfo::productVersion(),
QSysInfo::currentCpuArchitecture());
auto displayName = "NeoChat"_ls;
connection->loginWithPassword(matrixId, m_password, displayName);
connect(connection, &Connection::connected, this, [this, displayName, connection] {

View File

@@ -195,7 +195,7 @@ void RoomManager::viewEventSource(const QString &eventId)
Q_EMIT showEventSource(eventId);
}
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText)
void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText, const QString &hoveredLink)
{
const auto &event = **room->findInTimeline(eventId);
@@ -214,7 +214,8 @@ void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, Neoch
MessageComponentType::typeForEvent(event),
EventHandler::plainBody(room, &event),
EventHandler::richBody(room, &event),
selectedText);
selectedText,
hoveredLink);
}
bool RoomManager::hasOpenRoom() const
@@ -455,10 +456,12 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
return;
}
if (spaceId.length() > 3) {
resolveResource(spaceId, "no_join"_ls);
} else if (!m_isMobile) {
visitRoom({}, {});
if (!m_isMobile) {
if (spaceId.length() > 3) {
resolveResource(spaceId, "no_join"_ls);
} else {
visitRoom({}, {});
}
}
}

View File

@@ -232,7 +232,8 @@ public:
/**
* @brief Show a context menu for the given event.
*/
Q_INVOKABLE void viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText = {});
Q_INVOKABLE void
viewEventMenu(const QString &eventId, NeoChatRoom *room, NeochatRoomMember *sender, const QString &selectedText = {}, const QString &hoveredLink = {});
ChatDocumentHandler *chatDocumentHandler() const;
void setChatDocumentHandler(ChatDocumentHandler *handler);
@@ -313,7 +314,8 @@ Q_SIGNALS:
MessageComponentType::Type messageComponentType,
const QString &plainText,
const QString &htmlText,
const QString &selectedText);
const QString &selectedText,
const QString &hoveredLink);
/**
* @brief Request to show a menu for the given media event.

View File

@@ -17,7 +17,7 @@ import org.kde.neochat
FormCard.FormCardPage {
id: root
title: i18n("Edit Account")
title: i18nc("@title:window", "Edit Account")
property NeoChatConnection connection
KirigamiComponents.AvatarButton {
@@ -99,7 +99,7 @@ FormCard.FormCardPage {
}
FormCard.FormHeader {
title: i18n("User Information")
title: i18nc("@title:group", "User Information")
}
FormCard.FormCard {
FormCard.FormTextFieldDelegate {
@@ -151,7 +151,7 @@ FormCard.FormCardPage {
}
FormCard.FormHeader {
title: i18n("Password")
title: i18nc("@title:group", "Password")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
@@ -204,18 +204,18 @@ FormCard.FormCardPage {
}
ThreePIdCard {
connection: root.connection
title: i18n("Email Addresses")
title: i18nc("@title:group", "Email Addresses")
medium: "email"
}
ThreePIdCard {
visible: NeoChatConfig.phone3PId
connection: root.connection
title: i18n("Phone Numbers")
title: i18nc("@title:group", "Phone Numbers")
medium: "msisdn"
}
FormCard.FormHeader {
Layout.fillWidth: true
title: i18n("Identity Server")
title: i18nc("@title:group", "Identity Server")
}
FormCard.FormCard {
IdentityServerDelegate {
@@ -224,7 +224,7 @@ FormCard.FormCardPage {
}
FormCard.FormHeader {
Layout.fillWidth: true
title: i18n("Server Information")
title: i18nc("@title:group", "Server Information")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
@@ -249,7 +249,7 @@ FormCard.FormCardPage {
}*/
}
FormCard.FormHeader {
title: i18nc("@title", "Account Management")
title: i18nc("@title:group", "Account Management")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {

View File

@@ -17,7 +17,7 @@ FormCard.FormCardPage {
property NeoChatConnection initialAccount
title: i18n("Accounts")
title: i18nc("@title:window", "Accounts")
Component.onCompleted: if (initialAccount) {
intialAccountTimer.restart()
@@ -35,7 +35,7 @@ FormCard.FormCardPage {
}
FormCard.FormHeader {
title: i18n("Accounts")
title: i18nc("@title:group", "Accounts")
}
FormCard.FormCard {
Repeater {

Some files were not shown because too many files have changed in this diff Show More