Compare commits

..

37 Commits

Author SHA1 Message Date
James Graham
41603e7aa2 Make use of the new RoomMember objects in libquotient in UserListModel 2023-08-13 10:42:20 +01:00
l10n daemon script
65a1df4a75 GIT_SILENT Sync po/docbooks with svn 2023-08-12 01:49:08 +00:00
l10n daemon script
d15157703a GIT_SILENT made messages (after extraction) 2023-08-12 00:46:19 +00:00
l10n daemon script
2d21330a36 GIT_SILENT Sync po/docbooks with svn 2023-08-11 01:45:25 +00:00
l10n daemon script
fbc4611d02 GIT_SILENT Sync po/docbooks with svn 2023-08-10 01:47:01 +00:00
l10n daemon script
e257865b19 GIT_SILENT made messages (after extraction) 2023-08-10 00:46:30 +00:00
James Graham
018154f555 No emoji chatbar button on mobile
Hide the emoji button in the `chatbar` and don't instantiate the dialog component on mobile. This isn't needed on mobile as the virtual keyboard can add emojis. Should also save a small amount of memory on mobile from the dialog which is normally always instantiated.
2023-08-09 18:10:53 +00:00
l10n daemon script
80a5daa1f7 GIT_SILENT Sync po/docbooks with svn 2023-08-09 01:48:44 +00:00
Carl Schwan
bf5ce049d9 Port to new settings component 2023-08-08 21:40:05 +00:00
Carl Schwan
58213ee3e6 Display big emoji correctly
We already check with a regex if the message only contains emoji so it is safe
to hardcode the font family as emoji for these messages.
2023-08-08 12:07:16 +02:00
l10n daemon script
2d1862a637 GIT_SILENT Sync po/docbooks with svn 2023-08-08 01:49:39 +00:00
Tobias Fella
67453d9fb8 Remove ifdefs for E2EE 2023-08-07 21:15:11 +00:00
Tobias Fella
3ab04583ae Force plain text in room delegate 2023-08-07 22:58:11 +02:00
l10n daemon script
794683b5d2 GIT_SILENT Sync po/docbooks with svn 2023-08-07 02:08:13 +00:00
l10n daemon script
9f7f5409bd 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"
2023-08-07 01:51:19 +00:00
l10n daemon script
7d7b9d7ae0 GIT_SILENT made messages (after extraction) 2023-08-07 00:48:50 +00:00
James Graham
c7c892c51c Fix message long press
Add a tap handler so that long press events in the text of a message can be captured. This should make mobile menus work on text messages again.
2023-08-06 17:08:46 +00:00
l10n daemon script
97903a7cb0 GIT_SILENT Sync po/docbooks with svn 2023-08-06 01:50:23 +00:00
l10n daemon script
0c8f488ac4 GIT_SILENT made messages (after extraction) 2023-08-06 00:46:42 +00:00
Tobias Fella
88368cefaa Remove leftover debug statement 2023-08-06 00:51:28 +02:00
Tobias Fella
ab4bb41979 Fix last active time in devices page 2023-08-05 20:32:47 +02:00
James Graham
c2398b19dc Fix mark all read on room change
Fix it so that the view is correctly positioned at the bottom when the room is changed and make sure this is not seen as a scroll event (i.e. messages are not automatically marked as read but the timer has to time out first and all visible on screen).
2023-08-05 17:38:21 +00:00
James Graham
4b3dac025f Declare currentRoom property
Make sure `currentRoom` is a property for every qml component where it's used in `chatbar`.
2023-08-05 16:50:06 +00:00
l10n daemon script
9eeff06328 GIT_SILENT Sync po/docbooks with svn 2023-08-05 01:46:52 +00:00
l10n daemon script
657a8291d0 GIT_SILENT made messages (after extraction) 2023-08-05 00:46:28 +00:00
James Graham
bc4431462a Chatbox id for all property refs
Change the chatbox id to root and use for all references to it properties. Using root.currentRoom is needed for the chatbar to be visible in KF6
2023-08-04 13:56:26 +00:00
l10n daemon script
fdd8a62873 GIT_SILENT Sync po/docbooks with svn 2023-08-04 01:47:18 +00:00
l10n daemon script
c164228894 GIT_SILENT made messages (after extraction) 2023-08-04 00:46:08 +00:00
Carl Schwan
7d418f0883 Shorten appstream description 2023-08-03 16:17:58 +00:00
Tobias Fella
e90aecbead Port UserDetailDialog to Kirigami.Dialog 2023-08-03 17:26:46 +02:00
l10n daemon script
221c27fbbe GIT_SILENT Sync po/docbooks with svn 2023-08-03 02:09:50 +00:00
l10n daemon script
1907404124 GIT_SILENT Sync po/docbooks with svn 2023-08-02 01:54:58 +00:00
James Graham
81e953dd8c use QTEST_MAIN 2023-08-01 15:57:58 +00:00
James Graham
9311052e39 Move polishcolor to a utils namespace and rename getusercolor 2023-08-01 15:57:58 +00:00
James Graham
648fff20f2 Updates based on comments 2023-08-01 15:57:58 +00:00
James Graham
980211f36a Remove unneeded include 2023-08-01 15:57:58 +00:00
James Graham
25e0e3fa8e Remove NeoChatUser and just use Quotient::User this simplifies things and avoids casting from one to the other which address santizer didn't like 2023-08-01 15:57:58 +00:00
85 changed files with 8159 additions and 8052 deletions

View File

@@ -10,6 +10,8 @@
#include <Quotient/syncdata.h>
#include <qnamespace.h>
#include "utils.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
@@ -555,10 +557,10 @@ void TextHandlerTest::receiveRichPlainUrl()
void TextHandlerTest::receiveRichEmote()
{
auto event = room->messageEvents().at(1).get();
auto author = static_cast<NeoChatUser *>(room->user(event->senderId()));
auto author = room->user(event->senderId());
const QString testInputString = QStringLiteral("This is an emote.");
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:") + author->color().name()
+ QStringLiteral("\">@example:example.org</a> This is an emote.");
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:")
+ Utils::getUserColor(author->hueF()).name() + QStringLiteral("\">@example:example.org</a> This is an emote.");
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
@@ -644,5 +646,5 @@ void TextHandlerTest::linkPreviewsReject()
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
QTEST_GUILESS_MAIN(TextHandlerTest)
QTEST_MAIN(TextHandlerTest)
#include "texthandlertest.moc"

View File

@@ -46,43 +46,22 @@
<name xml:lang="uk">NeoChat</name>
<name xml:lang="x-test">xxNeoChatxx</name>
<name xml:lang="zh-CN">NeoChat</name>
<summary>A client for matrix, the decentralized communication protocol</summary>
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary>
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
<summary xml:lang="ca">Un client de Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="ca-valencia">Un client de Matrix, el protocol de comunicacions descentralitzat</summary>
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
<summary xml:lang="el">Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας</summary>
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
<summary xml:lang="eo">Kliento por matrix, la malcentra komunikprotokolo</summary>
<summary xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado</summary>
<summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary>
<summary xml:lang="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</summary>
<summary xml:lang="fr">Un client pour « Matrix », le protocole décentralisé de communications.</summary>
<summary xml:lang="gl">Un cliente para Matrix, o protocolo de comunicación descentralizada.</summary>
<summary xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
<summary xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate</summary>
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
<summary xml:lang="ie">Un cliente de Matrix, li protocol de communication decentralisat</summary>
<summary xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
<summary xml:lang="ka">კლიენტი Matrix-სთვის, დეცენტრალიზებული კომუნიკაციის პროტოკოლისთვის</summary>
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
<summary xml:lang="nn">Ein klient for Matrix  protokollen for desentralisert kommunikasjon</summary>
<summary xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ</summary>
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
<summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
<summary xml:lang="ru">Клиент для Matrix — децентрализованного коммуникационного протокола</summary>
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
<summary xml:lang="ta">மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி</summary>
<summary xml:lang="tr">Merkezi olmayan iletişim protokolü Matrix için bir istemci</summary>
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
<summary xml:lang="zh-CN">分布式通讯协议 Matrix 的客户端</summary>
<summary>Chat with your friends on matrix</summary>
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
<description>
<p>NeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami
to provide a convergent experience across multiple platforms.</p>
@@ -154,6 +133,7 @@ to provide a convergent experience across multiple platforms.</p>
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
<p xml:lang="sv">På grund av sättet Matrix-specifikationens utvecklas, stöder NeoChat också ett stor antal instabila funktioner. För närvarande är de:</p>
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
@@ -291,8 +271,9 @@ to provide a convergent experience across multiple platforms.</p>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="gl">Vista principal coa lista de salas, a conversa, e información da sala.</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</value>
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</value>
@@ -312,6 +293,7 @@ to provide a convergent experience across multiple platforms.</p>
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="eo">Ensaluta ekrano</value>
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="es">Pantalla de inicio de sesión</value>
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="eu">Saio-hasteko pantaila</value>
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fi">Kirjautumisnäkymä</value>
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fr">Écran de connexion</value>
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="gl">Pantalla de identificación.</value>
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="it">Schermata di accesso</value>

View File

@@ -93,7 +93,7 @@ Comment[es]=Cliente para el protocolo Matrix
Comment[eu]=Matrix protokolorako bezeroa
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
Comment[fr]=Client pour le protocole « Matrix »
Comment[gl]=Cliente para o protocolo Matrix
Comment[gl]=Cliente para o protocolo Matrix.
Comment[hu]=Kliens a Matrix protokollhoz
Comment[ia]=Cliente per le protocollo de Matrix
Comment[id]=Klien untuk protokol Matrix

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

@@ -0,0 +1,122 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Swedish "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>NeoChat användarmanual</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>NeoChat manualsida.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>2022-11-01</date>
<releaseinfo
>22.09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Klient för att interagera med meddelandeprotokollet matrix</refpurpose>
</refnamediv>
<!-- body begins here -->
<refsynopsisdiv id='synopsis'>
<cmdsynopsis
><command
>neochat</command
> <arg choice="opt"
><replaceable
>URI</replaceable
></arg
> </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title
>Beskrivning</title>
<para
><command
>neochat</command
> är ett chattprogram för protokollet matrix som fungerar både på skrivbord och mobil. </para>
</refsect1>
<refsect1 id="options"
><title
>Väljare</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>Matrix webbadress för en användare eller ett rum, t.ex. matrix:u/användare:exempel.org. Det gör att NeoChat försöker öppna det angivna rummet eller konversationen. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Rapportera fel</title>
<para
>Rapportera fel och funktionsönskemål på <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General</ulink
></para>
</refsect1>
<refsect1>
<title
>Se också</title>
<simplelist>
<member
>En lista över vanliga frågor om Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Copyright</title>
<para
>Copyright &copy; 2020-2022 Tobias Fella </para>
<para
>Copyright &copy; 2020-2022 Carl Schwan </para>
<para
>Licens: GNU General Public Version 3 eller senare &lt;<ulink url="https://wwwgnuorg/licenses/gpl-html"
>http://www.gnu.org/licenses/gpl-3.0.html</ulink
>&gt;</para>
</refsect1>
</refentry>

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

@@ -5,12 +5,10 @@
configure_file(qml/Page/RoomList/RoomDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Page/RoomList/RoomDelegate.qml)
configure_file(qml/Component/QuickSwitcher.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/QuickSwitcher.qml)
configure_file(qml/Dialog/UserDetailDialog.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Dialog/UserDetailDialog.qml)
configure_file(qml/Dialog/PowerLevelDialog.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Dialog/PowerLevelDialog.qml)
configure_file(qml/Component/Timeline/AudioDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/AudioDelegate.qml)
configure_file(qml/Component/Timeline/VideoDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/VideoDelegate.qml)
configure_file(qml/Component/Timeline/OsmLocationPlugin.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/OsmLocationPlugin.qml)
configure_file(qml/Component/NeochatMaximizeComponent.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/NeochatMaximizeComponent.qml)
configure_file(res.qrc ${CMAKE_CURRENT_SOURCE_DIR}/res.generated.qrc)
@@ -45,8 +43,6 @@ add_library(neochat STATIC
roommanager.h
neochatroom.cpp
neochatroom.h
neochatuser.cpp
neochatuser.h
models/userlistmodel.cpp
models/userlistmodel.h
models/userfiltermodel.cpp
@@ -128,6 +124,7 @@ add_library(neochat STATIC
locationhelper.h
events/pollevent.cpp
pollhandler.cpp
utils.h
)
ecm_qt_declare_logging_category(neochat

View File

@@ -35,15 +35,15 @@
#include <Quotient/connection.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/csapi/logout.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/csapi/profile.h>
#include <Quotient/eventstats.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/eventstats.h>
#include <Quotient/user.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include "notificationsmanager.h"
#include "roommanager.h"
#include "windowcontroller.h"
@@ -60,7 +60,6 @@ Controller::Controller(QObject *parent)
: QObject(parent)
{
Connection::setRoomType<NeoChatRoom>();
Connection::setUserType<NeoChatUser>();
setApplicationProxy();
@@ -532,7 +531,7 @@ void Controller::joinRoom(const QString &alias)
RoomManager::instance().joinRoom(m_connection, alias, QStringList{knownServer});
}
void Controller::openOrCreateDirectChat(NeoChatUser *user)
void Controller::openOrCreateDirectChat(User *user)
{
const auto existing = activeConnection()->directChats();
@@ -582,11 +581,6 @@ bool Controller::hasWindowSystem() const
#endif
}
bool Controller::encryptionSupported() const
{
return Quotient::encryptionSupported();
}
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
{
// HACK: Workaround bug QTBUG 93281

View File

@@ -14,7 +14,6 @@
#include <Quotient/settings.h>
class NeoChatRoom;
class NeoChatUser;
class TrayIcon;
class QWindow;
class QQuickTextDocument;
@@ -23,6 +22,7 @@ namespace Quotient
{
class Connection;
class Room;
class User;
}
namespace QKeychain
@@ -87,11 +87,6 @@ class Controller : public QObject
*/
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
/**
* @brief Whether the ecryption support has been enabled.
*/
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
/**
* @brief The current minor version number of libQuotient being used.
*
@@ -183,7 +178,7 @@ public:
*
* If a direct chat with the user doesn't exist one is created and then joined.
*/
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
[[nodiscard]] bool supportSystemTray() const;
@@ -194,8 +189,6 @@ public:
bool isOnline() const;
bool encryptionSupported() const;
/**
* @brief Sets the QNetworkProxy for the application.
*

View File

@@ -29,10 +29,11 @@
#include "neochat-version.h"
#include <Quotient/keyverificationsession.h>
#include <Quotient/accountregistry.h>
#include <Quotient/keyverificationsession.h>
#include <Quotient/networkaccessmanager.h>
#include <Quotient/room.h>
#include <Quotient/user.h>
#include <Quotient/util.h>
#include "actionshandler.h"
@@ -76,7 +77,6 @@
#include "models/webshortcutmodel.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include "notificationsmanager.h"
#include "pollhandler.h"
#include "roommanager.h"
@@ -89,7 +89,6 @@
#endif
#include "models/completionmodel.h"
#include "models/statemodel.h"
#include "neochatuser.h"
#ifdef HAVE_RUNNER
#include "runner.h"
@@ -260,24 +259,23 @@ int main(int argc, char *argv[])
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
qmlRegisterUncreatableType<NeoChatUser>("org.kde.neochat", 1, 0, "NeoChatUser", {});
qmlRegisterUncreatableType<User>("org.kde.neochat", 1, 0, "User", {});
qmlRegisterUncreatableType<NeoChatRoom>("org.kde.neochat", 1, 0, "NeoChatRoom", {});
qRegisterMetaType<User *>("User*");
qRegisterMetaType<User *>("const User*");
qRegisterMetaType<User *>("const Quotient::User*");
qRegisterMetaType<RoomMember *>("RoomMember*");
qRegisterMetaType<Room *>("Room*");
qRegisterMetaType<Connection *>("Connection*");
qRegisterMetaType<MessageEventType>("MessageEventType");
qRegisterMetaType<NeoChatRoom *>("NeoChatRoom*");
qRegisterMetaType<NeoChatUser *>("NeoChatUser*");
qRegisterMetaType<User *>("User*");
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
qRegisterMetaType<QMimeType>("QMimeType");
#ifdef Quotient_E2EE_ENABLED
qRegisterMetaType<KeyVerificationSession *>("KeyVerificationSession*");
qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
qRegisterMetaType<QVector<EmojiEntry>>("QVector<EmojiEntry>");
#endif
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "About", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
return engine->toScriptValue(KAboutData::applicationData());
});

View File

@@ -5,6 +5,9 @@
#include "controller.h"
#include <QDateTime>
#include <QLocale>
#include <KLocalizedString>
#include <Quotient/csapi/device_management.h>
@@ -52,6 +55,12 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const
} else {
return false;
}
case TimestampString:
if (device.lastSeenTs) {
return QDateTime::fromMSecsSinceEpoch(*device.lastSeenTs).toString(QLocale().dateTimeFormat(QLocale::ShortFormat));
} else {
return false;
}
case Type:
if (device.deviceId == m_connection->deviceId()) {
return This;
@@ -81,6 +90,7 @@ QHash<int, QByteArray> DevicesModel::roleNames() const
{DisplayName, "displayName"},
{LastIp, "lastIp"},
{LastTimestamp, "lastTimestamp"},
{TimestampString, "timestamp"},
{Type, "type"},
};
}

View File

@@ -40,6 +40,7 @@ public:
DisplayName, /**< Display name set by the user for this device. */
LastIp, /**< The IP address where this device was last seen. */
LastTimestamp, /**< The timestamp when this devices was last seen. */
TimestampString, /**< String for the timestamp when this devices was last seen. */
Type, /**< The category to sort this device into. */
};
Q_ENUM(Roles)

View File

@@ -3,6 +3,8 @@
#include "locationsmodel.h"
#include <QGuiApplication>
using namespace Quotient;
LocationsModel::LocationsModel(QObject *parent)
@@ -43,6 +45,10 @@ LocationsModel::LocationsModel(QObject *parent)
});
connect(this, &LocationsModel::rowsInserted, this, &LocationsModel::boundingBoxChanged);
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
});
}
void LocationsModel::addLocation(const RoomMessageEvent *event)
@@ -61,7 +67,7 @@ void LocationsModel::addLocation(const RoomMessageEvent *event)
.latitude = latitude,
.longitude = longitude,
.content = event->contentJson(),
.author = dynamic_cast<NeoChatUser *>(m_room->user(event->senderId())),
.author = m_room->user(event->senderId()),
};
endInsertRows();
}

View File

@@ -10,6 +10,7 @@
#include "neochatroom.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/user.h>
class LocationsModel : public QAbstractListModel
{
@@ -51,7 +52,7 @@ private:
float latitude;
float longitude;
QJsonObject content;
NeoChatUser *author;
Quotient::User *author;
};
QList<LocationData> m_locations;
void addLocation(const Quotient::RoomMessageEvent *event);

View File

@@ -25,7 +25,6 @@
#include <KLocalizedString>
#include "models/reactionmodel.h"
#include "neochatuser.h"
#include "texthandler.h"
using namespace Quotient;
@@ -78,7 +77,7 @@ MessageEventModel::MessageEventModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyRole});
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyAuthor, ReadMarkersRole});
});
}
@@ -531,7 +530,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == AuthorRole) {
auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
auto author = isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId());
return m_currentRoom->getUser(author);
}
@@ -679,7 +678,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
auto replyPtr = m_currentRoom->getReplyForEvent(evt);
if (replyPtr) {
auto replyUser = static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId()));
auto replyUser = m_currentRoom->user(replyPtr->senderId());
return m_currentRoom->getUser(replyUser);
} else {
return m_currentRoom->getUser(nullptr);
@@ -808,7 +807,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
QVariantList users;
users.reserve(userIds.size());
for (const auto &userId : userIds) {
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
auto user = m_currentRoom->user(userId);
users += m_currentRoom->getUser(user);
}
@@ -836,7 +835,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
*/
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) {
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
auto user = m_currentRoom->user(userId);
readMarkersString += user->displayname(m_currentRoom) + i18nc("list separator", ", ");
}
readMarkersString.chop(2);
@@ -866,18 +865,16 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == VerifiedRole) {
#ifdef Quotient_E2EE_ENABLED
if (evt.originalEvent()) {
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
Q_ASSERT(encrypted);
return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId().toLatin1());
}
#endif
return false;
}
if (role == DisplayNameForInitialsRole) {
auto user = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
auto user = isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId());
return user->displayname(m_currentRoom).remove(QStringLiteral(" (%1)").arg(user->id()));
}
@@ -889,7 +886,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
return previousDisplayName;
} else {
auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
auto author = isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId());
return m_currentRoom->htmlSafeMemberName(author->id());
}
}
@@ -1062,13 +1059,13 @@ void MessageEventModel::createReactionModelForEvent(const Quotient::RoomMessageE
return;
};
QMap<QString, QList<NeoChatUser *>> reactions = {};
QMap<QString, QList<Quotient::User *>> reactions = {};
for (const auto &a : annotations) {
if (a->isRedacted()) { // Just in case?
continue;
}
if (const auto &e = eventCast<const ReactionEvent>(a)) {
reactions[e->key()].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
reactions[e->key()].append(m_currentRoom->user(e->senderId()));
}
}
@@ -1095,7 +1092,7 @@ void MessageEventModel::createReactionModelForEvent(const Quotient::RoomMessageE
if (m_reactionModels.contains(eventId)) {
m_reactionModels[eventId]->setReactions(res);
} else if (res.size() > 0) {
m_reactionModels[eventId] = new ReactionModel(this, res, static_cast<NeoChatUser *>(m_currentRoom->localUser()));
m_reactionModels[eventId] = new ReactionModel(this, res, m_currentRoom->localUser());
} else {
if (m_reactionModels.contains(eventId)) {
delete m_reactionModels[eventId];

View File

@@ -7,9 +7,9 @@
#include <KLocalizedString>
#include "neochatuser.h"
#include <Quotient/user.h>
ReactionModel::ReactionModel(QObject *parent, QList<Reaction> reactions, NeoChatUser *localUser)
ReactionModel::ReactionModel(QObject *parent, QList<Reaction> reactions, Quotient::User *localUser)
: QAbstractListModel(parent)
, m_localUser(localUser)
{

View File

@@ -5,7 +5,10 @@
#include <QAbstractListModel>
class NeoChatUser;
namespace Quotient
{
class User;
}
/**
* @class ReactionModel
@@ -36,7 +39,7 @@ public:
HasLocalUser, /**< Whether the local user is in the list of authors. */
};
explicit ReactionModel(QObject *parent = nullptr, QList<Reaction> reactions = {}, NeoChatUser *localUser = nullptr);
explicit ReactionModel(QObject *parent = nullptr, QList<Reaction> reactions = {}, Quotient::User *localUser = nullptr);
/**
* @brief Get the given role value at the given index.
@@ -67,6 +70,6 @@ public:
private:
QList<Reaction> m_reactions;
NeoChatUser *m_localUser;
Quotient::User *m_localUser;
};
Q_DECLARE_METATYPE(ReactionModel *)

View File

@@ -5,13 +5,14 @@
#include "messageeventmodel.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include <QGuiApplication>
#include <Quotient/connection.h>
#include <Quotient/events/stickerevent.h>
#include <KLocalizedString>
#include <Quotient/connection.h>
using namespace Quotient;
// TODO search only in the current room
@@ -19,6 +20,9 @@ using namespace Quotient;
SearchModel::SearchModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReadMarkersRole});
});
}
QString SearchModel::searchText() const
@@ -109,7 +113,7 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
return false;
case ReplyAuthorRole:
if (const auto &replyPtr = m_room->getReplyForEvent(event)) {
return m_room->getUser(static_cast<NeoChatUser *>(m_room->user(replyPtr->senderId())));
return m_room->getUser(m_room->user(replyPtr->senderId()));
} else {
return m_room->getUser(nullptr);
}

View File

@@ -3,6 +3,8 @@
#include "userlistmodel.h"
#include <QGuiApplication>
#include <Quotient/connection.h>
#include <Quotient/events/roompowerlevelsevent.h>
@@ -14,6 +16,9 @@ UserListModel::UserListModel(QObject *parent)
: QAbstractListModel(parent)
, m_currentRoom(nullptr)
{
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this]() {
refreshAllUsers();
});
}
void UserListModel::setRoom(NeoChatRoom *room)
@@ -28,11 +33,9 @@ void UserListModel::setRoom(NeoChatRoom *room)
m_currentRoom = room;
if (m_currentRoom) {
connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded);
connect(m_currentRoom, &Room::userRemoved, this, &UserListModel::userRemoved);
connect(m_currentRoom, &Room::memberAboutToRename, this, &UserListModel::userRemoved);
connect(m_currentRoom, &Room::memberRenamed, this, &UserListModel::userAdded);
connect(m_currentRoom, &Room::changed, this, &UserListModel::refreshAllUsers);
connect(m_currentRoom, &Room::memberAdded, this, &UserListModel::memberAdded);
connect(m_currentRoom, &Room::memberRemoved, this, &UserListModel::memberRemoved);
connect(m_currentRoom, &Room::memberUpdated, this, &UserListModel::memberUpdated);
}
refreshAllUsers();
@@ -44,38 +47,29 @@ NeoChatRoom *UserListModel::room() const
return m_currentRoom;
}
Quotient::User *UserListModel::userAt(QModelIndex index) const
{
if (index.row() < 0 || index.row() >= m_users.size()) {
return nullptr;
}
return m_users.at(index.row());
}
QVariant UserListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.row() >= m_users.count()) {
if (index.row() >= rowCount()) {
qDebug() << "UserListModel, something's wrong: index.row() >= "
"users.count()";
return {};
}
auto user = m_users.at(index.row());
auto user = m_members.at(index.row());
if (role == DisplayNameRole) {
return user->displayname(m_currentRoom);
return user->displayName();
}
if (role == UserIdRole) {
return user->id();
}
if (role == AvatarRole) {
auto neoChatUser = static_cast<NeoChatUser *>(user);
return m_currentRoom->avatarForMember(neoChatUser);
return user->avatarUrl();
}
if (role == ObjectRole) {
return QVariant::fromValue(user);
return QVariant::fromValue<RoomMember *>(user.get());
}
if (role == PowerLevelRole) {
auto plEvent = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
@@ -114,59 +108,47 @@ int UserListModel::rowCount(const QModelIndex &parent) const
if (parent.isValid()) {
return 0;
}
return m_users.count();
return m_members.count();
}
void UserListModel::userAdded(Quotient::User *user)
void UserListModel::memberAdded(QString memberId)
{
auto pos = findUserPos(user);
beginInsertRows(QModelIndex(), pos, pos);
m_users.insert(pos, user);
if (m_currentRoom->memberIds().contains(memberId)) {
return;
}
beginInsertRows(QModelIndex(), m_currentRoom->memberIds().size(), m_currentRoom->memberIds().size());
endInsertRows();
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
refreshUser(user, {AvatarRole});
});
}
void UserListModel::userRemoved(Quotient::User *user)
void UserListModel::memberUpdated(QString memberId)
{
auto pos = findUserPos(user);
if (pos != m_users.size()) {
beginRemoveRows(QModelIndex(), pos, pos);
m_users.removeAt(pos);
endRemoveRows();
user->disconnect(this);
} else {
qWarning() << "Trying to remove a room member not in the user list";
if (!m_currentRoom->memberIds().contains(memberId)) {
return;
}
auto row = m_currentRoom->memberIds().indexOf(memberId);
if (row >= 0 && row < m_currentRoom->memberIds().size()) {
Q_EMIT dataChanged(index(row), index(row));
}
}
void UserListModel::refreshUser(Quotient::User *user, const QVector<int> &roles)
void UserListModel::memberRemoved(QString memberId)
{
auto pos = findUserPos(user);
if (pos != m_users.size()) {
Q_EMIT dataChanged(index(pos), index(pos), roles);
} else {
qWarning() << "Trying to access a room member not in the user list";
if (!m_currentRoom->memberIds().contains(memberId)) {
return;
}
auto row = m_currentRoom->memberIds().indexOf(memberId);
if (row >= 0 && row < m_currentRoom->memberIds().size()) {
beginRemoveRows(QModelIndex(), row, row);
endRemoveRows();
}
}
void UserListModel::refreshAllUsers()
{
beginResetModel();
for (User *user : std::as_const(m_users)) {
user->disconnect(this);
}
m_users.clear();
m_members.clear();
m_members = m_currentRoom->members();
m_users = m_currentRoom->users();
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
for (User *user : std::as_const(m_users)) {
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
refreshUser(user, {AvatarRole});
});
}
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
setRoom(nullptr);
});
@@ -174,19 +156,6 @@ void UserListModel::refreshAllUsers()
Q_EMIT usersRefreshed();
}
int UserListModel::findUserPos(Quotient::User *user) const
{
return findUserPos(m_currentRoom->safeMemberName(user->id()));
}
int UserListModel::findUserPos(const QString &username) const
{
if (!m_currentRoom) {
return 0;
}
return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username);
}
QHash<int, QByteArray> UserListModel::roleNames() const
{
QHash<int, QByteArray> roles;

View File

@@ -54,11 +54,6 @@ public:
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
/**
* @brief The user at the given index of the model.
*/
[[nodiscard]] Quotient::User *userAt(QModelIndex index) const;
/**
* @brief Get the given role value at the given index.
*
@@ -85,15 +80,12 @@ Q_SIGNALS:
void usersRefreshed();
private Q_SLOTS:
void userAdded(Quotient::User *user);
void userRemoved(Quotient::User *user);
void refreshUser(Quotient::User *user, const QVector<int> &roles = {});
void memberAdded(QString memberId);
void memberUpdated(QString memberId);
void memberRemoved(QString memberId);
void refreshAllUsers();
private:
QPointer<NeoChatRoom> m_currentRoom;
QList<Quotient::User *> m_users;
int findUserPos(Quotient::User *user) const;
[[nodiscard]] int findUserPos(const QString &username) const;
QList<QSharedPointer<Quotient::RoomMember>> m_members;
};

View File

@@ -54,7 +54,7 @@ Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentraliza
Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma
Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications.
Comment[gl]=Un cliente para Matrix, o protocolo de comunicación descentralizada
Comment[gl]=Un cliente para Matrix, o protocolo de comunicación descentralizada.
Comment[hu]=Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz
Comment[ia]=Un cliente per Matrix, le protocollo de communication decentralisate
Comment[id]=Sebuah klien untuk matrix, protokol komunikasi terdecentralisasi
@@ -133,7 +133,7 @@ Comment[es]=Hay un mensaje nuevo
Comment[eu]=Mezu berri bat dago
Comment[fi]=Saapui uusi viesti
Comment[fr]=Il y a un nouveau message
Comment[gl]=Hai unha nova mensaxe
Comment[gl]=Hai unha nova mensaxe.
Comment[hu]=Új üzenet érkezett
Comment[ia]=Il ha un nove message
Comment[id]=Ada pesan baru
@@ -209,7 +209,7 @@ Comment[es]=Hay una nueva invitación a una sala
Comment[eu]=Gela baterako gonbidapen berri bat dago
Comment[fi]=Uusi kutsu huoneeseen
Comment[fr]=Il y a une nouvelle invitation dans un salon.
Comment[gl]=Tes un novo convite para unha sala
Comment[gl]=Tes un novo convite para unha sala.
Comment[ia]=Il ha un nove invitation a un sala
Comment[id]=Ada undangan baru ke sebuah ruangan
Comment[ie]=Vu have un nov invitation a un chambre

View File

@@ -4,8 +4,10 @@
#include "neochatroom.h"
#include <QFileInfo>
#include <QGuiApplication>
#include <QMetaObject>
#include <QMimeDatabase>
#include <QPalette>
#include <QTemporaryFile>
#include <QTextDocument>
@@ -13,6 +15,7 @@
#include <QMediaPlayer>
#include <Quotient/jobs/basejob.h>
#include <Quotient/user.h>
#include <qcoro/qcorosignal.h>
#include <Quotient/connection.h>
@@ -43,6 +46,7 @@
#include "neochatconfig.h"
#include "notificationsmanager.h"
#include "texthandler.h"
#include "utils.h"
#include <KConfig>
#include <KConfigGroup>
@@ -412,11 +416,11 @@ QVariantList NeoChatRoom::getUsers(const QString &keyword, int limit) const
int count = 0;
for (const auto u : userList) {
if (u->displayname(this).contains(keyword, Qt::CaseInsensitive)) {
NeoChatUser user(u->id(), u->connection());
Quotient::User user(u->id(), u->connection());
QVariantMap userVariant{{QStringLiteral("id"), user.id()},
{QStringLiteral("displayName"), user.displayname(this)},
{QStringLiteral("avatarMediaId"), user.avatarMediaId(this)},
{QStringLiteral("color"), user.color()}};
{QStringLiteral("color"), Utils::getUserColor(user.hueF())}};
matchedList.append(QVariant::fromValue(userVariant));
count++;
@@ -442,11 +446,10 @@ static const QVariantMap emptyUser = {
QVariantMap NeoChatRoom::getUser(const QString &userID) const
{
NeoChatUser *userObject = static_cast<NeoChatUser *>(user(userID));
return getUser(userObject);
return getUser(user(userID));
}
QVariantMap NeoChatRoom::getUser(NeoChatUser *user) const
QVariantMap NeoChatRoom::getUser(User *user) const
{
if (user == nullptr) {
return emptyUser;
@@ -458,7 +461,7 @@ QVariantMap NeoChatRoom::getUser(NeoChatUser *user) const
{QStringLiteral("displayName"), user->displayname(this)},
{QStringLiteral("avatarSource"), avatarForMember(user)},
{QStringLiteral("avatarMediaId"), user->avatarMediaId(this)},
{QStringLiteral("color"), user->color()},
{QStringLiteral("color"), Utils::getUserColor(user->hueF())},
{QStringLiteral("object"), QVariant::fromValue(user)},
};
}
@@ -539,7 +542,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
if (prettyPrint) {
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
.arg(e.userId(), static_cast<NeoChatUser *>(user(e.userId()))->color().name(), subjectName);
.arg(e.userId(), Utils::getUserColor(user(e.userId())->hueF()).name(), subjectName);
}
// The below code assumes senderName output in AuthorRole
@@ -1682,7 +1685,7 @@ QVariantMap NeoChatRoom::chatBoxReplyUser() const
if (m_chatBoxReplyId.isEmpty()) {
return emptyUser;
}
return getUser(static_cast<NeoChatUser *>(user((*findInTimeline(m_chatBoxReplyId))->senderId())));
return getUser(user((*findInTimeline(m_chatBoxReplyId))->senderId()));
}
QString NeoChatRoom::chatBoxReplyMessage() const
@@ -1698,7 +1701,7 @@ QVariantMap NeoChatRoom::chatBoxEditUser() const
if (m_chatBoxEditId.isEmpty()) {
return emptyUser;
}
return getUser(static_cast<NeoChatUser *>(user((*findInTimeline(m_chatBoxEditId))->senderId())));
return getUser(user((*findInTimeline(m_chatBoxEditId))->senderId()));
}
QString NeoChatRoom::chatBoxEditMessage() const
@@ -1809,10 +1812,7 @@ void NeoChatRoom::editLastMessage()
bool NeoChatRoom::canEncryptRoom() const
{
#ifdef Quotient_E2EE_ENABLED
return !usesEncryption() && canSendState("m.room.encryption");
#endif
return false;
}
PollHandler *NeoChatRoom::poll(const QString &eventId)
@@ -1904,9 +1904,10 @@ int NeoChatRoom::maxRoomVersion() const
}
return maxVersion;
}
NeoChatUser *NeoChatRoom::directChatRemoteUser() const
Quotient::User *NeoChatRoom::directChatRemoteUser() const
{
return dynamic_cast<NeoChatUser *>(connection()->directChatUsers(this)[0]);
return connection()->directChatUsers(this)[0];
}
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
@@ -1938,7 +1939,7 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
return QJsonDocument(accountData(eventType)->fullJson()).toJson();
}
QUrl NeoChatRoom::avatarForMember(NeoChatUser *user) const
QUrl NeoChatRoom::avatarForMember(Quotient::User *user) const
{
const auto &url = memberAvatarUrl(user->id());
if (url.isEmpty() || url.scheme() != "mxc"_ls) {

View File

@@ -10,10 +10,15 @@
#include <QTextCursor>
#include <QCoroTask>
#include <Quotient/user.h>
#include "neochatuser.h"
#include "pollhandler.h"
namespace Quotient
{
class User;
}
class PushNotificationState : public QObject
{
Q_OBJECT
@@ -64,7 +69,7 @@ class NeoChatRoom : public Quotient::Room
*
* The list does not include the local user.
*
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
* This is different to getting a list of Quotient::User objects
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* provides the room context and puts the result as a list of QVariantMap objects.
@@ -76,7 +81,7 @@ class NeoChatRoom : public Quotient::Room
* - displayName - Display name in the context of this room.
* - display - Name in the context of this room.
*
* @sa Quotient::User, NeoChatUser
* @sa Quotient::User
*/
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
@@ -119,7 +124,7 @@ class NeoChatRoom : public Quotient::Room
/**
* @brief Get a user object for the other person in a direct chat.
*/
Q_PROPERTY(NeoChatUser *directChatRemoteUser READ directChatRemoteUser CONSTANT)
Q_PROPERTY(Quotient::User *directChatRemoteUser READ directChatRemoteUser CONSTANT)
/**
* @brief If the room is a space.
@@ -181,9 +186,6 @@ class NeoChatRoom : public Quotient::Room
/**
* @brief Whether the local user can encrypt the room.
*
* Requires libQuotient 0.7 compiled with the Quotient_E2EE_ENABLED parameter to
* be able to return true.
*
* A local user can encrypt a room if they have permission to send the m.room.encryption
* state event.
*
@@ -316,7 +318,7 @@ class NeoChatRoom : public Quotient::Room
/**
* @brief Get the user for the message being replied to.
*
* This is different to getting a NeoChatUser object or Quotient::User object
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
@@ -329,9 +331,9 @@ class NeoChatRoom : public Quotient::Room
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The NeoChatUser object for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User, NeoChatUser
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
@@ -345,7 +347,7 @@ class NeoChatRoom : public Quotient::Room
/**
* @brief Get the user for the message being edited.
*
* This is different to getting a NeoChatUser object or Quotient::User object
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
@@ -358,9 +360,9 @@ class NeoChatRoom : public Quotient::Room
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The NeoChatUser object for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User, NeoChatUser
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
@@ -392,7 +394,7 @@ public:
/**
* @brief Get a list of users in the context of this room.
*
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
* This is different to getting a list of Quotient::User objects
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* provides the room context and returns the result as a list of QVariantMap objects.
@@ -407,14 +409,14 @@ public:
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
*
* @sa Quotient::User, NeoChatUser
* @sa Quotient::User
*/
Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword, int limit = -1) const;
/**
* @brief Get a user in the context of this room.
*
* This is different to getting a NeoChatUser object or Quotient::User object
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* provides the room context and outputs the result as QVariantMap.
@@ -431,16 +433,16 @@ public:
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The NeoChatUser object for the user.
* - object - The Quotient::User object for the user.
*
* @sa Quotient::User, NeoChatUser
* @sa Quotient::User
*/
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
/**
* @brief Get a user in the context of this room.
*
* This is different to getting a NeoChatUser object or Quotient::User object
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* provides the room context and outputs the result as QVariantMap.
@@ -457,11 +459,11 @@ public:
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The NeoChatUser object for the user.
* - object - The Quotient::User object for the user.
*
* @sa Quotient::User, NeoChatUser
* @sa Quotient::User
*/
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(NeoChatUser *user) const;
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(Quotient::User *user) const;
[[nodiscard]] QVariantList getUsersTyping() const;
@@ -602,7 +604,7 @@ public:
[[nodiscard]] QString avatarMediaId() const;
NeoChatUser *directChatRemoteUser() const;
Quotient::User *directChatRemoteUser() const;
[[nodiscard]] bool isSpace();
@@ -818,7 +820,7 @@ public:
*/
Q_INVOKABLE QByteArray roomAcountDataJson(const QString &eventType);
Q_INVOKABLE [[nodiscard]] QUrl avatarForMember(NeoChatUser *user) const;
Q_INVOKABLE [[nodiscard]] QUrl avatarForMember(Quotient::User *user) const;
/**
* @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply.

View File

@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
#include "neochatuser.h"
#include <QGuiApplication>
#include <QJsonObject>
#include <QPalette>
#include <Quotient/connection.h>
using namespace Quotient;
NeoChatUser::NeoChatUser(QString userId, Connection *connection)
: User(std::move(userId), connection)
{
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, &NeoChatUser::polishColor);
polishColor();
}
QColor NeoChatUser::color()
{
return m_color;
}
void NeoChatUser::setColor(const QColor &color)
{
if (m_color == color) {
return;
}
m_color = color;
Q_EMIT colorChanged(m_color);
}
void NeoChatUser::polishColor()
{
const auto lightness = static_cast<QGuiApplication *>(QGuiApplication::instance())->palette().color(QPalette::Active, QPalette::Window).lightnessF();
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
setColor(QColor::fromHslF(hueF(), 1, -0.7 * lightness + 0.9, 1));
}
#include "moc_neochatuser.cpp"

View File

@@ -1,39 +0,0 @@
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <QObject>
#include <Quotient/user.h>
/**
* @class NeoChatUser
*
* A class inherited from Quotient::User to add a user color function.
*
* @sa Quotient::User
*/
class NeoChatUser : public Quotient::User
{
Q_OBJECT
/**
* @brief The color to use for the user.
*/
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
public:
explicit NeoChatUser(QString userId, Quotient::Connection *connection);
public Q_SLOTS:
QColor color();
void setColor(const QColor &color);
Q_SIGNALS:
void colorChanged(QColor _t1);
private:
QColor m_color;
void polishColor();
};

View File

@@ -122,10 +122,8 @@ void NotificationsManager::processNotificationJob(QPointer<Quotient::Connection>
}
if (notification["event"]["type"] == "m.room.encrypted") {
#ifdef Quotient_E2EE_ENABLED
auto decrypted = connection->decryptNotification(notification);
body = decrypted["content"].toObject()["body"].toString();
#endif
if (body.isEmpty()) {
body = i18n("Encrypted Message");
}

View File

@@ -53,7 +53,7 @@ Comment[es]=Buscar salas en NeoChat
Comment[eu]=Bilatu gelak NeoChat-en
Comment[fi]=Etsi huoneita NeoChatissä
Comment[fr]=Trouver des salons dans NeoChat
Comment[gl]=Atopa salas en NeoChat
Comment[gl]=Atopa salas en NeoChat.
Comment[ia]=Trova salas in NeoChat
Comment[id]=Cari ruangan di NeoChat
Comment[ie]=Trovar chambres in NeoChat

View File

@@ -36,7 +36,7 @@ QQC2.Control {
/**
* @brief The current room that user is viewing.
*/
property NeoChatRoom currentRoom
required property NeoChatRoom currentRoom
/**
* @brief The QQC2.TextArea object.
@@ -55,10 +55,10 @@ QQC2.Control {
Kirigami.Action {
id: attachmentAction
property bool isBusy: currentRoom && currentRoom.hasFileUploading
property bool isBusy: root.currentRoom && root.currentRoom.hasFileUploading
// Matrix does not allow sending attachments in replies
visible: currentRoom.chatBoxReplyId.length === 0 && currentRoom.chatBoxAttachmentPath.length === 0
visible: root.currentRoom.chatBoxReplyId.length === 0 && root.currentRoom.chatBoxAttachmentPath.length === 0
icon.name: "mail-attachment"
text: i18n("Attach an image or file")
displayHint: Kirigami.DisplayHint.IconOnly
@@ -72,7 +72,7 @@ QQC2.Control {
if (!path) {
return;
}
currentRoom.chatBoxAttachmentPath = path;
root.currentRoom.chatBoxAttachmentPath = path;
})
fileDialog.open()
}
@@ -85,16 +85,17 @@ QQC2.Control {
property bool isBusy: false
visible: !Kirigami.Settings.isMobile
icon.name: "smiley"
text: i18n("Emojis & Stickers")
displayHint: Kirigami.DisplayHint.IconOnly
checkable: true
onTriggered: {
if (emojiDialog.visible) {
emojiDialog.close()
if (emojiDialog.item.visible) {
emojiDialog.item.close()
} else {
emojiDialog.open()
emojiDialog.item.open()
}
}
},
@@ -106,7 +107,7 @@ QQC2.Control {
displayHint: QQC2.AbstractButton.IconOnly
onTriggered: {
locationChooserComponent.createObject(QQC2.ApplicationWindow.overlay, {room: currentRoom}).open()
locationChooserComponent.createObject(QQC2.ApplicationWindow.overlay, {room: root.currentRoom}).open()
}
},
Kirigami.Action {
@@ -162,10 +163,9 @@ QQC2.Control {
leftPadding: LayoutMirroring.enabled ? actionsRow.width : Kirigami.Units.largeSpacing
rightPadding: LayoutMirroring.enabled ? Kirigami.Units.largeSpacing : actionsRow.width + x * 2 + Kirigami.Units.largeSpacing * 2
placeholderText: readOnly ? i18n("This room is encrypted. Build libQuotient with encryption enabled to send encrypted messages.") : currentRoom.usesEncryption ? i18n("Send an encrypted message…") : currentRoom.chatBoxAttachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…")
placeholderText: root.currentRoom.usesEncryption ? i18n("Send an encrypted message…") : root.currentRoom.chatBoxAttachmentPath.length > 0 ? i18n("Set an attachment caption...") : i18n("Send a message…")
verticalAlignment: TextEdit.AlignVCenter
wrapMode: Text.Wrap
readOnly: (currentRoom.usesEncryption && !Controller.encryptionSupported)
Accessible.description: placeholderText
@@ -177,10 +177,10 @@ QQC2.Control {
onTextChanged: {
if (!repeatTimer.running && Config.typingNotifications) {
var textExists = text.length > 0
currentRoom.sendTypingNotification(textExists)
root.currentRoom.sendTypingNotification(textExists)
textExists ? repeatTimer.start() : repeatTimer.stop()
}
currentRoom.chatBoxText = text
root.currentRoom.chatBoxText = text
}
onCursorRectangleChanged: chatBarScrollView.ensureVisible(cursorRectangle)
onSelectedTextChanged: {
@@ -207,7 +207,7 @@ QQC2.Control {
remove(cursorPosition, cursorPosition + 1)
}
if (textField.text == selectedText || textField.text.length <= 1) {
currentRoom.sendTypingNotification(false)
root.currentRoom.sendTypingNotification(false)
repeatTimer.stop()
}
if (quickFormatBar.visible) {
@@ -241,16 +241,16 @@ QQC2.Control {
if (event.key === Qt.Key_V && event.modifiers & Qt.ControlModifier) {
root.pasteImage();
} else if (event.key === Qt.Key_Up && event.modifiers & Qt.ControlModifier) {
currentRoom.replyLastMessage();
root.currentRoom.replyLastMessage();
} else if (event.key === Qt.Key_Up && textField.text.length === 0) {
currentRoom.editLastMessage();
root.currentRoom.editLastMessage();
} else if (event.key === Qt.Key_Up && completionMenu.visible) {
completionMenu.decrementIndex()
} else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex()
} else if (event.key === Qt.Key_Backspace) {
if (textField.text == selectedText || textField.text.length <= 1) {
currentRoom.sendTypingNotification(false)
root.currentRoom.sendTypingNotification(false)
repeatTimer.stop()
}
if (quickFormatBar.visible && selectedText.length > 0) {
@@ -282,19 +282,19 @@ QQC2.Control {
Component {
id: replyPane
ReplyPane {
userName: currentRoom.chatBoxReplyUser.displayName
userColor: currentRoom.chatBoxReplyUser.color
userAvatar: currentRoom.chatBoxReplyUser.avatarSource
text: currentRoom.chatBoxReplyMessage
userName: root.currentRoom.chatBoxReplyUser.displayName
userColor: root.currentRoom.chatBoxReplyUser.color
userAvatar: root.currentRoom.chatBoxReplyUser.avatarSource
text: root.currentRoom.chatBoxReplyMessage
}
}
Component {
id: attachmentPane
AttachmentPane {
attachmentPath: currentRoom.chatBoxAttachmentPath
attachmentPath: root.currentRoom.chatBoxAttachmentPath
onAttachmentCancelled: {
currentRoom.chatBoxAttachmentPath = "";
root.currentRoom.chatBoxAttachmentPath = "";
root.forceActiveFocus()
}
}
@@ -346,8 +346,8 @@ QQC2.Control {
text: i18nc("@action:button", "Cancel reply")
icon.name: "dialog-close"
onTriggered: {
currentRoom.chatBoxReplyId = "";
currentRoom.chatBoxAttachmentPath = "";
root.currentRoom.chatBoxReplyId = "";
root.currentRoom.chatBoxAttachmentPath = "";
root.forceActiveFocus()
}
}
@@ -388,23 +388,28 @@ QQC2.Control {
PieProgressBar {
visible: modelData.isBusy
progress: currentRoom.fileUploadingProgress
progress: root.currentRoom.fileUploadingProgress
}
}
}
}
EmojiDialog {
Loader {
id: emojiDialog
x: parent.width - width
y: -implicitHeight // - Kirigami.Units.smallSpacing
active: !Kirigami.Settings.isMobile
sourceComponent: EmojiDialog {
x: root.width - width
y: -implicitHeight // - Kirigami.Units.smallSpacing
modal: false
includeCustom: true
closeOnChosen: false
modal: false
includeCustom: true
closeOnChosen: false
onChosen: insertText(emoji)
onClosed: if (emojiButton.checked) emojiButton.checked = false
currentRoom: root.currentRoom
onChosen: insertText(emoji)
onClosed: if (emojiAction.checked) emojiAction.checked = false
}
}
background: Rectangle {
@@ -468,15 +473,15 @@ QQC2.Control {
if (localPath.length === 0) {
return;
}
currentRoom.chatBoxAttachmentPath = localPath
root.currentRoom.chatBoxAttachmentPath = localPath
}
function postMessage() {
actionsHandler.handleNewMessage();
repeatTimer.stop()
currentRoom.markAllMessagesAsRead();
root.currentRoom.markAllMessagesAsRead();
textField.clear();
currentRoom.chatBoxReplyId = "";
root.currentRoom.chatBoxReplyId = "";
messageSent()
}

View File

@@ -30,12 +30,12 @@ import org.kde.neochat 1.0
* @sa ChatBar
*/
ColumnLayout {
id: chatBox
id: root
/**
* @brief The current room that user is viewing.
*/
property NeoChatRoom currentRoom
required property NeoChatRoom currentRoom
/**
* @brief A message has been sent from the chat bar.
@@ -63,7 +63,7 @@ ColumnLayout {
ChatBar {
id: chatBar
visible: currentRoom.canSendEvent("m.room.message")
visible: root.currentRoom.canSendEvent("m.room.message")
Layout.fillWidth: true
Layout.minimumHeight: Math.max(Kirigami.Units.gridUnit * 2, implicitHeight + Kirigami.Units.largeSpacing)
@@ -78,7 +78,7 @@ ColumnLayout {
}
onMessageSent: {
chatBox.messageSent();
root.messageSent();
}
}

View File

@@ -10,6 +10,11 @@ import org.kde.neochat 1.0
ColumnLayout {
id: root
/**
* @brief The current room that user is viewing.
*/
property NeoChatRoom currentRoom
property bool includeCustom: false
property bool showQuickReaction: false
@@ -151,7 +156,7 @@ ColumnLayout {
ImagePacksModel {
id: stickerPackModel
room: currentRoom
room: root.currentRoom
showStickers: true
showEmoticons: false
}
@@ -160,7 +165,7 @@ ColumnLayout {
id: stickerModel
model: stickerPackModel
packIndex: 0
room: currentRoom
room: root.currentRoom
}
EmoticonFilterModel {

View File

@@ -6,6 +6,7 @@ import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.neochat 1.0
/**
* @brief A component that provides a set of actions when a message is hovered in the timeline.
@@ -16,6 +17,11 @@ import org.kde.kirigami 2.15 as Kirigami
QQC2.Control {
id: root
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom currentRoom
/**
* @brief Whether the actions should be shown.
*/
@@ -108,6 +114,7 @@ QQC2.Control {
EmojiDialog {
id: emojiDialog
currentRoom: root.currentRoom
showQuickReaction: true
onChosen: (emoji) => root.reactClicked(emoji)
}

View File

@@ -28,7 +28,7 @@ Components.AlbumMaximizeComponent {
readonly property var currentJsonSource: model.data(model.index(content.currentIndex, 0), MessageEventModel.SourceRole)
@QTMULTIMEDIA_AUDIO_AUTOLOAD@
autoLoad: false
downloadAction: Components.DownloadAction {
id: downloadAction

View File

@@ -50,6 +50,12 @@ TimelineContainer {
isReply: root.isReply
textMessage: root.display
TapHandler {
enabled: !label.hoveredLink
acceptedButtons: Qt.LeftButton
onLongPressed: root.openContextMenu()
}
}
Loader {
Layout.fillWidth: true

View File

@@ -34,9 +34,9 @@ Item {
* - displayName - The display name of the reply author.
* - display - The name of the reply author.
* - color - The color for the reply author.
* - object - The NeoChatUser object for the reply author.
* - object - The Quotient::User object for the reply author.
*
* @sa NeoChatUser
* @sa Quotient::User
*/
required property var author

View File

@@ -27,7 +27,12 @@ TextEdit {
/**
* @brief Regex for detecting a message with a single emoji.
*/
readonly property var isEmoji: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
readonly property var isEmojiRegex: /^(<span style='.*'>)?(\u00a9|\u00ae|[\u20D0-\u2fff]|[\u3190-\u3300]|\ud83c[\ud000-\udfff]|\ud83d[\ud000-\udfff]|\ud83e[\ud000-\udfff])+(<\/span>)?$/
/**
* @brief Whether the message is an emoji
*/
readonly property var isEmoji: isEmojiRegex.test(textMessage)
/**
* @brief Regex for detecting a message with a spoiler.
@@ -85,7 +90,10 @@ a{
color: Kirigami.Theme.textColor
selectedTextColor: Kirigami.Theme.highlightedTextColor
selectionColor: Kirigami.Theme.highlightColor
font.pointSize: !root.isReply && isEmoji.test(textMessage) ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
font {
pointSize: !root.isReply && root.isEmoji ? Kirigami.Theme.defaultFont.pointSize * 4 : Kirigami.Theme.defaultFont.pointSize
family: root.isEmoji ? 'emoji' : Kirigami.Theme.defaultFont.family
}
selectByMouse: !Kirigami.Settings.isMobile
readOnly: true
wrapMode: Text.Wrap

View File

@@ -54,9 +54,9 @@ ColumnLayout {
* - displayName - The display name of the author.
* - display - The name of the author.
* - color - The color for the author.
* - object - The NeoChatUser object for the author.
* - object - The Quotient::User object for the author.
*
* @sa NeoChatUser
* @sa Quotient::User
*/
required property var author
@@ -137,9 +137,9 @@ ColumnLayout {
* - displayName - The display name of the reply author.
* - display - The name of the reply author.
* - color - The color for the reply author.
* - object - The NeoChatUser object for the reply author.
* - object - The Quotient::User object for the reply author.
*
* @sa NeoChatUser
* @sa Quotient::User
*/
required property var replyAuthor

View File

@@ -16,9 +16,19 @@ import org.kde.neochat 1.0
QQC2.ScrollView {
id: root
required property NeoChatRoom currentRoom
readonly property bool isLoaded: root.width * root.height > 10
onCurrentRoomChanged: {
roomChanging = true;
applicationWindow().hoverLinkIndicator.text = "";
messageListView.positionViewAtBeginning();
hasScrolledUpBefore = false;
roomChanging = true;
}
property bool roomChanging: false
readonly property bool atYEnd: messageListView.atYEnd
/// Used to determine if scrolling to the bottom should mark the message as unread
property bool hasScrolledUpBefore: false;
signal focusChatBox()
ListView {
@@ -38,7 +48,7 @@ QQC2.ScrollView {
interactive: Kirigami.Settings.isMobile
bottomMargin: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2)
model: !isLoaded ? undefined : collapseStateProxyModel
model: collapseStateProxyModel
MessageEventModel {
id: messageEventModel
@@ -70,13 +80,15 @@ QQC2.ScrollView {
messageEventModel.fetchMore(messageEventModel.index(0, 0));
}
onAtYEndChanged: if (atYEnd && hasScrolledUpBefore) {
if (QQC2.ApplicationWindow.window && (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden)) {
root.currentRoom.markAllMessagesAsRead();
onAtYEndChanged: if (!root.roomChanging) {
if (atYEnd && root.hasScrolledUpBefore) {
if (QQC2.ApplicationWindow.window && (QQC2.ApplicationWindow.window.visibility !== QQC2.ApplicationWindow.Hidden)) {
root.currentRoom.markAllMessagesAsRead();
}
root.hasScrolledUpBefore = false;
} else if (!atYEnd) {
root.hasScrolledUpBefore = true;
}
hasScrolledUpBefore = false;
} else if (!atYEnd) {
hasScrolledUpBefore = true;
}
// Not rendered because the sections are part of the TimelineContainer.qml, this is only so that items have the section property available for use by sectionBanner.
@@ -310,6 +322,7 @@ QQC2.ScrollView {
y: delegate ? delegate.mapToItem(parent, 0, 0).y + delegate.bubbleY - height + Kirigami.Units.smallSpacing : 0
width: delegate ? delegate.bubbleWidth : Kirigami.Units.gridUnit * 4
currentRoom: root.currentRoom
showActions: delegate && delegate.hovered
verified: delegate && delegate.verified
editable: delegate && delegate.author.isLocalUser && (delegate.delegateType === MessageEventModel.Emote || delegate.delegateType === MessageEventModel.Message)

View File

@@ -11,6 +11,11 @@ import org.kde.neochat 1.0
QQC2.Popup {
id: emojiPopup
/**
* @brief The current room that user is viewing.
*/
property NeoChatRoom currentRoom
property bool includeCustom: false
property bool closeOnChosen: true
property bool showQuickReaction: false
@@ -52,6 +57,7 @@ QQC2.Popup {
contentItem: EmojiPicker {
id: emojiPicker
height: 400
currentRoom: root.currentRoom
includeCustom: emojiPopup.includeCustom
showQuickReaction: emojiPopup.showQuickReaction
onChosen: {

View File

@@ -11,7 +11,7 @@ import org.kde.kirigamiaddons.labs.components 1.0 as KirigamiComponents
import org.kde.neochat 1.0
Kirigami.OverlaySheet {
Kirigami.Dialog {
id: root
signal closed()
@@ -26,6 +26,9 @@ Kirigami.OverlaySheet {
topPadding: 0
bottomPadding: 0
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title:menu Account detail dialog", "Account detail")
contentItem: ColumnLayout {
@@ -196,10 +199,5 @@ Kirigami.OverlaySheet {
}
}
}
@OVERLAYSHEET_OPEN@: {
if (!sheetOpen) {
closed()
}
}
}

View File

@@ -67,6 +67,7 @@ Delegates.RoundedItemDelegate {
text: root.displayName
elide: Text.ElideRight
font.weight: root.hasNotifications ? Font.Bold : Font.Normal
textFormat: Text.PlainText
Layout.fillWidth: true
Layout.alignment: subtitle.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter

View File

@@ -19,25 +19,17 @@ Kirigami.Page {
/// Not readonly because of the separate window view.
property NeoChatRoom currentRoom: RoomManager.currentRoom
property bool loading: !root.currentRoom || (root.currentRoom.timelineSize === 0 && !root.currentRoom.allHistoryLoaded)
/// Used to determine if scrolling to the bottom should mark the message as unread
property bool hasScrolledUpBefore: false;
/// Disable cancel shortcut. Used by the separate window since it provides its own cancel implementation.
property bool disableCancelShortcut: false
title: currentRoom.displayName
title: root.currentRoom.displayName
focus: true
padding: 0
KeyNavigation.left: pageStack.get(0)
onCurrentRoomChanged: {
if (!timelineViewLoader.item) {
return
}
applicationWindow().hoverLinkIndicator.text = "";
timelineViewLoader.item.positionViewAtBeginning();
hasScrolledUpBefore = false;
if (!Kirigami.Settings.isMobile && chatBoxLoader.item) {
chatBoxLoader.item.forceActiveFocus();
}
@@ -66,7 +58,7 @@ Kirigami.Page {
Loader {
id: timelineViewLoader
anchors.fill: parent
active: currentRoom && !currentRoom.isInvite && !root.loading
active: root.currentRoom && !root.currentRoom.isInvite && !root.loading
sourceComponent: TimelineView {
id: timelineView
currentRoom: root.currentRoom
@@ -80,7 +72,7 @@ Kirigami.Page {
Loader {
id: invitationLoader
active: currentRoom && currentRoom.isInvite
active: root.currentRoom && root.currentRoom.isInvite
anchors.centerIn: parent
sourceComponent: InvitationView {
currentRoom: root.currentRoom
@@ -106,7 +98,7 @@ Kirigami.Page {
sourceComponent: ChatBox {
id: chatBox
width: parent.width
currentRoom: currentRoom
currentRoom: root.currentRoom
onMessageSent: {
if (!timelineViewLoader.item.atYEnd) {
timelineViewLoader.item.goToLastMessage();
@@ -140,9 +132,9 @@ Kirigami.Page {
Shortcut {
sequence: StandardKey.Cancel
onActivated: {
if (!timelineViewLoader.item.atYEnd || currentRoom.hasUnreadMessages) {
if (!timelineViewLoader.item.atYEnd || root.currentRoom.hasUnreadMessages) {
goToLastMessage();
currentRoom.markAllMessagesAsRead();
root.currentRoom.markAllMessagesAsRead();
} else {
applicationWindow().pageStack.get(0).forceActiveFocus();
}

View File

@@ -3,17 +3,18 @@
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import org.kde.kirigamiaddons.settings 1.0 as KirigamiSettings
import QtQuick.Layouts 1.15
import org.kde.neochat 1.0
Kirigami.CategorizedSettings {
KirigamiSettings.CategorizedSettings {
id: root
property NeoChatRoom room
objectName: "settingsPage"
actions: [
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "general"
text: i18n("General")
icon.name: "settings-configure"
@@ -24,7 +25,7 @@ Kirigami.CategorizedSettings {
}
}
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "security"
text: i18n("Security")
icon.name: "security-low"
@@ -35,7 +36,7 @@ Kirigami.CategorizedSettings {
}
}
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "permissions"
text: i18n("Permissions")
icon.name: "visibility"
@@ -46,7 +47,7 @@ Kirigami.CategorizedSettings {
}
}
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "notifications"
text: i18n("Notifications")
icon.name: "notifications"

View File

@@ -27,7 +27,6 @@ Kirigami.ScrollablePage {
title: i18nc("@option:check", "Encryption")
}
MobileForm.FormCard {
visible: Controller.encryptionSupported
Layout.fillWidth: true
contentItem: ColumnLayout {
spacing: 0

View File

@@ -15,7 +15,7 @@ MobileForm.AbstractFormDelegate {
id: deviceDelegate
required property string id
required property int lastTimestamp
required property string timestamp
required property string displayName
property bool editDeviceName: false
@@ -49,7 +49,7 @@ MobileForm.AbstractFormDelegate {
QQC2.Label {
Layout.fillWidth: true
text: deviceDelegate.id + ", Last activity: " + (new Date(deviceDelegate.lastTimestamp)).toLocaleString(Qt.locale(), Locale.ShortFormat)
text: i18nc("@label", "%1, Last activity: %2", deviceDelegate.id, deviceDelegate.timestamp)
color: Kirigami.Theme.disabledTextColor
font: Kirigami.Theme.smallFont
elide: Text.ElideRight
@@ -100,7 +100,7 @@ MobileForm.AbstractFormDelegate {
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
visible: Controller.encryptionSupported && deviceDelegate.showVerifyButton
visible: deviceDelegate.showVerifyButton
action: Kirigami.Action {
id: verifyDeviceAction
text: i18n("Verify device")
@@ -131,4 +131,4 @@ MobileForm.AbstractFormDelegate {
}
}
}
}
}

View File

@@ -3,60 +3,61 @@
import QtQuick 2.15
import org.kde.kirigami 2.18 as Kirigami
import org.kde.kirigamiaddons.settings 1.0 as KirigamiSettings
import QtQuick.Layouts 1.15
Kirigami.CategorizedSettings {
KirigamiSettings.CategorizedSettings {
id: settingsPage
required property var connection
objectName: "settingsPage"
actions: [
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "general"
text: i18n("General")
icon.name: "org.kde.neochat"
page: Qt.resolvedUrl("GeneralSettingsPage.qml")
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "appearance"
text: i18n("Appearance")
icon.name: "preferences-desktop-theme-global"
page: Qt.resolvedUrl("AppearanceSettingsPage.qml")
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "notifications"
text: i18n("Notifications")
icon.name: "preferences-desktop-notification"
page: Qt.resolvedUrl("GlobalNotificationsPage.qml")
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "accounts"
text: i18n("Accounts")
icon.name: "preferences-system-users"
page: Qt.resolvedUrl("AccountsPage.qml")
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "emoticons"
text: i18n("Stickers & Emojis")
icon.name: "preferences-desktop-emoticons"
page: Qt.resolvedUrl("EmoticonsPage.qml")
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "spellChecking"
text: i18n("Spell Checking")
icon.name: "tools-check-spelling"
page: Qt.resolvedUrl("SonnetConfigPage.qml")
visible: Qt.platform.os !== "android"
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "networkProxy"
text: i18n("Network Proxy")
icon.name: "network-connect"
page: Qt.resolvedUrl("NetworkProxyPage.qml")
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "devices"
text: i18n("Devices")
icon.name: "computer"
@@ -67,13 +68,13 @@ Kirigami.CategorizedSettings {
}
}
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "aboutNeochat"
text: i18n("About NeoChat")
icon.name: "help-about"
page: Qt.resolvedUrl("About.qml")
},
Kirigami.SettingAction {
KirigamiSettings.SettingAction {
actionName: "aboutKDE"
text: i18n("About KDE")
icon.name: "kde"

View File

@@ -242,7 +242,6 @@ Kirigami.ApplicationWindow {
function onInitiated() {
if (Controller.accountCount === 0) {
console.warn("9")
pageStack.replace("qrc:/WelcomePage.qml", {});
} else if (!roomListLoaded) {
pageStack.replace(roomListComponent, {
@@ -288,8 +287,6 @@ Kirigami.ApplicationWindow {
Connections {
target: Controller.activeConnection
//TODO Remove this when the E2EE flag in libQuotient goes away
ignoreUnknownSignals: true
function onDirectChatAvailable(directChat) {
RoomManager.enterRoom(Controller.activeConnection.room(directChat.id));
}

View File

@@ -27,7 +27,7 @@
<file alias="PushNotification.qml">qml/RoomSettings/PushNotification.qml</file>
<file alias="Categories.qml">qml/RoomSettings/Categories.qml</file>
<file alias="Permissions.qml">qml/RoomSettings/Permissions.qml</file>
<file alias="NeochatMaximizeComponent.qml">@CMAKE_CURRENT_BINARY_DIR@/qml/Component/NeochatMaximizeComponent.qml</file>
<file alias="NeochatMaximizeComponent.qml">qml/Component/NeochatMaximizeComponent.qml</file>
<file alias="FancyEffectsContainer.qml">qml/Component/FancyEffectsContainer.qml</file>
<file alias="TypingPane.qml">qml/Component/TypingPane.qml</file>
<file alias="ShimmerGradient.qml">qml/Component/ShimmerGradient.qml</file>
@@ -74,7 +74,7 @@
<file alias="RoomDrawer.qml">qml/Panel/RoomDrawer.qml</file>
<file alias="DirectChatDrawerHeader.qml">qml/Panel/DirectChatDrawerHeader.qml</file>
<file alias="GroupChatDrawerHeader.qml">qml/Panel/GroupChatDrawerHeader.qml</file>
<file alias="UserDetailDialog.qml">@CMAKE_CURRENT_BINARY_DIR@/qml/Dialog/UserDetailDialog.qml</file>
<file alias="UserDetailDialog.qml">qml/Dialog/UserDetailDialog.qml</file>
<file alias="CreateRoomDialog.qml">qml/Dialog/CreateRoomDialog.qml</file>
<file alias="CreateSpaceDialog.qml">qml/Dialog/CreateSpaceDialog.qml</file>
<file alias="EmojiDialog.qml">qml/Dialog/EmojiDialog.qml</file>

View File

@@ -4,8 +4,9 @@
#include "texthandler.h"
#include <QDebug>
#include <QUrl>
#include <QGuiApplication>
#include <QStringLiteral>
#include <QUrl>
#include <Quotient/events/roommessageevent.h>
#include <Quotient/util.h>
@@ -15,6 +16,8 @@
#include <Kirigami/PlatformTheme>
#include "utils.h"
static const QStringList allowedTags = {
QStringLiteral("font"), QStringLiteral("del"), QStringLiteral("h1"), QStringLiteral("h2"), QStringLiteral("h3"), QStringLiteral("h4"),
QStringLiteral("h5"), QStringLiteral("h6"), QStringLiteral("blockquote"), QStringLiteral("p"), QStringLiteral("a"), QStringLiteral("ul"),
@@ -135,9 +138,9 @@ QString TextHandler::handleRecieveRichText(Qt::TextFormat inputFormat, const Neo
if (event != nullptr) {
auto e = eventCast<const Quotient::RoomMessageEvent>(event);
if (e->msgtype() == Quotient::MessageEventType::Emote) {
auto author = static_cast<NeoChatUser *>(room->user(e->senderId()));
auto author = room->user(e->senderId());
QString emoteString = QStringLiteral("* <a href=\"https://matrix.to/#/") + e->senderId() + QStringLiteral("\" style=\"color:")
+ author->color().name() + QStringLiteral("\">") + author->displayname(room) + QStringLiteral("</a> ");
+ Utils::getUserColor(author->hueF()).name() + QStringLiteral("\">") + author->displayname(room) + QStringLiteral("</a> ");
if (outputString.startsWith(QStringLiteral("<p>"))) {
outputString.insert(3, emoteString);
} else {

23
src/utils.h Normal file
View File

@@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QColor>
#include <QGuiApplication>
#include <QPalette>
namespace Utils
{
/**
* @brief Get a color for a user from a hueF value.
*
* The lightness of the color will be modified depending on the current palette in
* order to maintain contrast.
*/
inline QColor getUserColor(qreal hueF)
{
const auto lightness = static_cast<QGuiApplication *>(QGuiApplication::instance())->palette().color(QPalette::Active, QPalette::Window).lightnessF();
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
}
}