Compare commits

..

1 Commits

Author SHA1 Message Date
Tobias Fella
bd5dd1dd54 Revamped user info 2024-07-15 22:16:51 +02:00
106 changed files with 9052 additions and 15601 deletions

View File

@@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "07")
set(RELEASE_SERVICE_VERSION_MICRO "90")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})

View File

@@ -36,6 +36,8 @@ private Q_SLOTS:
void eventId();
void nullEventId();
void author();
void nullAuthor();
void authorDisplayName();
void nullAuthorDisplayName();
void singleLineSidplayName();
@@ -73,6 +75,8 @@ private Q_SLOTS:
void nullThread();
void location();
void nullLocation();
void readMarkers();
void nullReadMarkers();
};
void EventHandlerTest::initTestCase()
@@ -94,6 +98,32 @@ void EventHandlerTest::nullEventId()
QCOMPARE(noEventHandler.getId(), QString());
}
void EventHandlerTest::author()
{
auto event = room->messageEvents().at(0).get();
auto author = room->member(event->senderId());
EventHandler eventHandler(room, event);
auto eventHandlerAuthor = eventHandler.getAuthor();
QCOMPARE(eventHandlerAuthor.isLocalMember(), author.id() == room->localMember().id());
QCOMPARE(eventHandlerAuthor.id(), author.id());
QCOMPARE(eventHandlerAuthor.displayName(), author.displayName());
QCOMPARE(eventHandlerAuthor.avatarUrl(), author.avatarUrl());
QCOMPARE(eventHandlerAuthor.avatarMediaId(), author.avatarMediaId());
QCOMPARE(eventHandlerAuthor.color(), author.color());
}
void EventHandlerTest::nullAuthor()
{
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getAuthor(), RoomMember());
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getAuthor(), RoomMember());
}
void EventHandlerTest::authorDisplayName()
{
EventHandler eventHandler(room, room->messageEvents().at(1).get());
@@ -163,7 +193,6 @@ void EventHandlerTest::timeString()
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
QCOMPARE(eventHandler.getTimeString(true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
QCOMPARE(eventHandler.getTimeString(QStringLiteral("hh:mm")), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
}
void EventHandlerTest::nullTimeString()
@@ -492,5 +521,59 @@ void EventHandlerTest::nullLocation()
QCOMPARE(emptyHandler.getLocationAssetType(), QString());
}
void EventHandlerTest::readMarkers()
{
EventHandler eventHandler(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandler.hasReadMarkers(), true);
auto readMarkers = eventHandler.getReadMarkers();
QCOMPARE(readMarkers.size(), 1);
QCOMPARE(readMarkers[0].id(), QStringLiteral("@alice:example.org"));
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QString());
QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: Alice Margatroid"));
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
QCOMPARE(eventHandler2.hasReadMarkers(), true);
readMarkers = eventHandler2.getReadMarkers();
QCOMPARE(readMarkers.size(), 5);
QCOMPARE(eventHandler2.getNumberExcessReadMarkers(), QStringLiteral("+ 1"));
// There are no guarantees on the order of the users it will be different every time so don't match the whole string.
QCOMPARE(eventHandler2.getReadMarkersString().startsWith(QStringLiteral("6 users:")), true);
}
void EventHandlerTest::nullReadMarkers()
{
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReadMarkers(), QList<Quotient::RoomMember>());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReadMarkersString(), QString());
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReadMarkers(), QList<Quotient::RoomMember>());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getNumberExcessReadMarkers(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReadMarkersString(), QString());
}
QTEST_MAIN(EventHandlerTest)
#include "eventhandlertest.moc"

View File

@@ -95,7 +95,6 @@
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
@@ -329,7 +328,6 @@
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
<caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</caption>

View File

@@ -18,7 +18,6 @@ Name[eu]=NeoChat
Name[fi]=NeoChat
Name[fr]=NeoChat
Name[gl]=NeoChat
Name[he]=NeoChat
Name[hu]=NeoChat
Name[ia]=Neochat
Name[id]=NeoChat
@@ -60,7 +59,6 @@ GenericName[eu]=Matrix bezeroa
GenericName[fi]=Matrix-asiakas
GenericName[fr]=Client « Matrix »
GenericName[gl]=Cliente de Matrix
GenericName[he]=לקוח Matrix
GenericName[hu]=Matrix kliens
GenericName[ia]=Cliente de Matrice
GenericName[id]=Klien Matrix
@@ -101,7 +99,6 @@ 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[he]=לקוח לפרוטוקול 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

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

@@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: neochat\n"
"Report-Msgid-Bugs-To: https://bugs.kde.org\n"
"POT-Creation-Date: 2024-07-30 02:34+0000\n"
"PO-Revision-Date: 2024-08-06 05:24+0900\n"
"POT-Creation-Date: 2024-07-15 00:38+0000\n"
"PO-Revision-Date: 2024-07-08 15:59+0900\n"
"Last-Translator: Kisaragi Hiu <mail@kisaragi-hiu.com>\n"
"Language-Team: Traditional Chinese <zh-l10n@lists.slat.org>\n"
"Language: zh_TW\n"
@@ -365,7 +365,7 @@ msgid "Custom"
msgstr "自訂"
#: src/enums/powerlevel.cpp:88 src/models/permissionsmodel.cpp:162
#: src/models/userlistmodel.cpp:114
#: src/models/userlistmodel.cpp:113
#, kde-format
msgctxt ""
"%1 is the name of the power level, e.g. admin and %2 is the value that "
@@ -373,349 +373,367 @@ msgctxt ""
msgid "%1 (%2)"
msgstr "%1 (%2)"
#: src/eventhandler.cpp:297 src/eventhandler.cpp:505
#: src/models/messagecontentmodel.cpp:230 src/models/messageeventmodel.cpp:438
#: src/eventhandler.cpp:304 src/eventhandler.cpp:509
#: src/models/messagecontentmodel.cpp:223 src/models/messageeventmodel.cpp:459
#, kde-format
msgid "<i>[This message was deleted]</i>"
msgstr "<i>[此訊息已被刪除]</i>"
#: src/eventhandler.cpp:297 src/models/messagecontentmodel.cpp:231
#: src/models/messageeventmodel.cpp:439
#: src/eventhandler.cpp:304 src/models/messagecontentmodel.cpp:224
#: src/models/messageeventmodel.cpp:460
#, kde-format
msgid "<i>[This message was deleted: %1]</i>"
msgstr "<i>[此訊息已被刪除:%1]</i>"
#: src/eventhandler.cpp:331
#: src/eventhandler.cpp:335
#, kde-format
msgid "reinvited %1 to the room"
msgstr "已重新邀請 %1 到聊天室裡"
#: src/eventhandler.cpp:333
#: src/eventhandler.cpp:337
#, kde-format
msgctxt "Optional reason for an invitation"
msgid ": %1"
msgstr "%1"
#: src/eventhandler.cpp:342 src/eventhandler.cpp:529
#: src/eventhandler.cpp:346 src/eventhandler.cpp:533
#, kde-format
msgid "joined the room (repeated)"
msgstr "已加入聊天室(重複)"
#: src/eventhandler.cpp:344
#: src/eventhandler.cpp:348
#, kde-format
msgid "invited %1 to the room"
msgstr "已邀請 %1 到聊天室裡"
#: src/eventhandler.cpp:344 src/eventhandler.cpp:531
#: src/eventhandler.cpp:348 src/eventhandler.cpp:535
#, kde-format
msgid "joined the room"
msgstr "已加入聊天室"
#: src/eventhandler.cpp:348
#: src/eventhandler.cpp:352
#, kde-format
msgid ": %1"
msgstr "%1"
#: src/eventhandler.cpp:355 src/eventhandler.cpp:539
#: src/eventhandler.cpp:359 src/eventhandler.cpp:543
#, kde-format
msgctxt "their refers to a singular user"
msgid "cleared their display name"
msgstr "將其顯示名稱清除了"
#: src/eventhandler.cpp:358
#: src/eventhandler.cpp:362
#, kde-format
msgctxt "their refers to a singular user"
msgid "changed their display name to %1"
msgstr "將其顯示名稱變更為 %1"
#: src/eventhandler.cpp:364 src/eventhandler.cpp:546
#: src/eventhandler.cpp:368 src/eventhandler.cpp:550
#, kde-format
msgid " and "
msgstr " 和 "
#: src/eventhandler.cpp:367 src/eventhandler.cpp:549
#: src/eventhandler.cpp:371 src/eventhandler.cpp:553
#, kde-format
msgctxt "their refers to a singular user"
msgid "cleared their avatar"
msgstr "將其頭貼清除了"
#: src/eventhandler.cpp:369 src/eventhandler.cpp:551
#: src/eventhandler.cpp:373 src/eventhandler.cpp:555
#, kde-format
msgid "set an avatar"
msgstr "設定了頭貼"
#: src/eventhandler.cpp:371 src/eventhandler.cpp:553
#: src/eventhandler.cpp:375 src/eventhandler.cpp:557
#, kde-format
msgctxt "their refers to a singular user"
msgid "updated their avatar"
msgstr "更新了他的頭貼"
#: src/eventhandler.cpp:375 src/eventhandler.cpp:557
#: src/eventhandler.cpp:379 src/eventhandler.cpp:561
#, kde-format
msgctxt "<user> changed nothing"
msgid "changed nothing"
msgstr "什麼都沒改"
#: src/eventhandler.cpp:381
#: src/eventhandler.cpp:385
#, kde-format
msgid "withdrew %1's invitation"
msgstr "撤回了 %1 的邀請"
#: src/eventhandler.cpp:381 src/eventhandler.cpp:563
#: src/eventhandler.cpp:385 src/eventhandler.cpp:567
#, kde-format
msgid "rejected the invitation"
msgstr "拒絕了邀請"
#: src/eventhandler.cpp:385
#: src/eventhandler.cpp:389
#, kde-format
msgid "unbanned %1"
msgstr "解除了 %1 的封鎖"
#: src/eventhandler.cpp:385 src/eventhandler.cpp:567
#: src/eventhandler.cpp:389 src/eventhandler.cpp:571
#, kde-format
msgid "self-unbanned"
msgstr "已自我解除封鎖"
#: src/eventhandler.cpp:388
#: src/eventhandler.cpp:392
#, kde-format
msgid "has put %1 out of the room: %2"
msgstr "已將 %1 移出聊天室:%2"
#: src/eventhandler.cpp:389 src/eventhandler.cpp:569
#: src/eventhandler.cpp:393 src/eventhandler.cpp:573
#, kde-format
msgid "left the room"
msgstr "已離開聊天室"
#: src/eventhandler.cpp:393
#: src/eventhandler.cpp:397
#, kde-format
msgid "banned %1 from the room"
msgstr "已從聊天室封鎖 %1"
#: src/eventhandler.cpp:395
#: src/eventhandler.cpp:399
#, kde-format
msgid "banned %1 from the room: %2"
msgstr "已從聊天室封鎖 %1%2"
#: src/eventhandler.cpp:398 src/eventhandler.cpp:574
#: src/eventhandler.cpp:402 src/eventhandler.cpp:578
#, kde-format
msgid "self-banned from the room"
msgstr "已從聊天室自行封鎖"
#: src/eventhandler.cpp:402 src/eventhandler.cpp:577
#: src/eventhandler.cpp:406 src/eventhandler.cpp:581
#, kde-format
msgid "requested an invite"
msgstr "請求了邀請"
#: src/eventhandler.cpp:402
#: src/eventhandler.cpp:406
#, kde-format
msgid "requested an invite with reason: %1"
msgstr "請求了邀請,理由:%1"
#: src/eventhandler.cpp:406 src/eventhandler.cpp:581
#: src/eventhandler.cpp:410 src/eventhandler.cpp:585
#, kde-format
msgid "made something unknown"
msgstr "做了不明的東西"
#: src/eventhandler.cpp:409 src/eventhandler.cpp:584
#: src/eventhandler.cpp:413 src/eventhandler.cpp:588
#, kde-format
msgid "cleared the room main alias"
msgstr "清除了聊天室的主別名"
#: src/eventhandler.cpp:409
#: src/eventhandler.cpp:413
#, kde-format
msgid "set the room main alias to: %1"
msgstr "設定聊天室的主別名為:%1"
#: src/eventhandler.cpp:412 src/eventhandler.cpp:587
#: src/eventhandler.cpp:416 src/eventhandler.cpp:591
#, kde-format
msgid "cleared the room name"
msgstr "清除了聊天室名稱"
#: src/eventhandler.cpp:412
#: src/eventhandler.cpp:416
#, kde-format
msgid "set the room name to: %1"
msgstr "設定聊天室名稱為:%1"
#: src/eventhandler.cpp:415 src/eventhandler.cpp:590
#: src/eventhandler.cpp:419 src/eventhandler.cpp:594
#, kde-format
msgid "cleared the topic"
msgstr "清除了主題"
#: src/eventhandler.cpp:416
#: src/eventhandler.cpp:420
#, kde-format
msgid "set the topic to: %1"
msgstr "設定主題為:%1"
#: src/eventhandler.cpp:422 src/eventhandler.cpp:593
#: src/eventhandler.cpp:426 src/eventhandler.cpp:597
#, kde-format
msgid "changed the room avatar"
msgstr "變更了聊天室頭貼"
#: src/eventhandler.cpp:425 src/eventhandler.cpp:596
#: src/eventhandler.cpp:429 src/eventhandler.cpp:600
#, kde-format
msgid "activated End-to-End Encryption"
msgstr "啟用了端對端加密"
#: src/eventhandler.cpp:429
#: src/eventhandler.cpp:433
#, kde-format
msgid "upgraded the room to version %1"
msgstr "更新了聊天室到版本 %1"
#: src/eventhandler.cpp:430
#: src/eventhandler.cpp:434
#, kde-format
msgid "created the room, version %1"
msgstr "建立了聊天室,版本 %1"
#: src/eventhandler.cpp:433 src/eventhandler.cpp:602
#: src/eventhandler.cpp:437 src/eventhandler.cpp:606
#, kde-format
msgctxt "'power level' means permission level"
msgid "changed the power levels for this room"
msgstr "變更了這個聊天室的能力等級"
#: src/eventhandler.cpp:439 src/eventhandler.cpp:608
#: src/eventhandler.cpp:443 src/eventhandler.cpp:612
#, kde-format
msgid "changed the server access control lists for this room"
msgstr "變更了這個聊天室的伺服器存取控制清單 (ACL)"
#: src/eventhandler.cpp:443
#: src/eventhandler.cpp:447
#, kde-format
msgctxt "[User] added <name> widget"
msgid "added %1 widget"
msgstr "新增了 %1 元件"
#: src/eventhandler.cpp:446
#: src/eventhandler.cpp:450
#, kde-format
msgctxt "[User] removed <name> widget"
msgid "removed %1 widget"
msgstr "移除了 %1 元件"
#: src/eventhandler.cpp:448
#: src/eventhandler.cpp:452
#, kde-format
msgctxt "[User] configured <name> widget"
msgid "configured %1 widget"
msgstr "設定了 %1 元件"
#: src/eventhandler.cpp:451
#: src/eventhandler.cpp:455
#, kde-format
msgid "updated %1 state"
msgstr "更新了 %1 狀態"
#: src/eventhandler.cpp:452
#: src/eventhandler.cpp:456
#, kde-format
msgid "updated %1 state for %2"
msgstr "為 %2 更新了 %1 狀態"
#: src/eventhandler.cpp:457 src/eventhandler.cpp:626
#: src/eventhandler.cpp:461 src/eventhandler.cpp:630
#, kde-format
msgid "Unknown event"
msgstr "未知事件"
#: src/eventhandler.cpp:472
#: src/eventhandler.cpp:476
#, kde-format
msgid "a file"
msgstr "一個檔案"
#: src/eventhandler.cpp:512
#: src/eventhandler.cpp:516
#, kde-format
msgid "sent a message"
msgstr "傳送了訊息"
#: src/eventhandler.cpp:516
#: src/eventhandler.cpp:520
#, kde-format
msgid "sent a sticker"
msgstr "傳送了貼圖"
#: src/eventhandler.cpp:522
#: src/eventhandler.cpp:526
#, kde-format
msgid "reinvited someone to the room"
msgstr "已重新邀請某人到聊天室裡"
#: src/eventhandler.cpp:531
#: src/eventhandler.cpp:535
#, kde-format
msgid "invited someone to the room"
msgstr "已邀請某人到聊天室裡"
#: src/eventhandler.cpp:541
#: src/eventhandler.cpp:545
#, kde-format
msgctxt "their refers to a singular user"
msgid "changed their display name"
msgstr "變更了他的顯示名稱"
#: src/eventhandler.cpp:563
#: src/eventhandler.cpp:567
#, kde-format
msgid "withdrew a user's invitation"
msgstr "撤回了一個使用者的邀請"
#: src/eventhandler.cpp:567
#: src/eventhandler.cpp:571
#, kde-format
msgid "unbanned a user"
msgstr "已解除封鎖一個使用者"
#: src/eventhandler.cpp:569
#: src/eventhandler.cpp:573
#, kde-format
msgid "put a user out of the room"
msgstr "已將一個使用者移出聊天室"
#: src/eventhandler.cpp:572
#: src/eventhandler.cpp:576
#, kde-format
msgid "banned a user from the room"
msgstr "已從聊天室封鎖一個使用者"
#: src/eventhandler.cpp:584
#: src/eventhandler.cpp:588
#, kde-format
msgid "set the room main alias"
msgstr "設定了聊天室的主別名"
#: src/eventhandler.cpp:587
#: src/eventhandler.cpp:591
#, kde-format
msgid "set the room name"
msgstr "設定了聊天室名稱"
#: src/eventhandler.cpp:590
#: src/eventhandler.cpp:594
#, kde-format
msgid "set the topic"
msgstr "設定了主題"
#: src/eventhandler.cpp:599
#: src/eventhandler.cpp:603
#, kde-format
msgid "upgraded the room version"
msgstr "更新了聊天室版本"
#: src/eventhandler.cpp:599
#: src/eventhandler.cpp:603
#, kde-format
msgid "created the room"
msgstr "建立了聊天室"
#: src/eventhandler.cpp:605
#: src/eventhandler.cpp:609
#, kde-format
msgid "sent a live location beacon"
msgstr "傳送即時位置信標"
#: src/eventhandler.cpp:612
#: src/eventhandler.cpp:616
#, kde-format
msgid "added a widget"
msgstr "新增了一個元件"
#: src/eventhandler.cpp:615
#: src/eventhandler.cpp:619
#, kde-format
msgid "removed a widget"
msgstr "移除了一個元件"
#: src/eventhandler.cpp:617
#: src/eventhandler.cpp:621
#, kde-format
msgid "configured a widget"
msgstr "設定了一個元件"
#: src/eventhandler.cpp:620
#: src/eventhandler.cpp:624
#, kde-format
msgid "updated the state"
msgstr "更新了狀態"
#: src/eventhandler.cpp:624
#: src/eventhandler.cpp:628
#, kde-format
msgid "started a poll"
msgstr "開始了投票"
#: src/eventhandler.cpp:1035
#, kde-format
msgid "1 user: "
msgid_plural "%1 users: "
msgstr[0] "%1 名使用者:"
#: src/eventhandler.cpp:1040
#, kde-format
msgctxt "A member who is not in the room has been requested."
msgid "unknown member"
msgstr "未知成員"
#: src/eventhandler.cpp:1044
#, kde-format
msgctxt "list separator"
msgid ", "
msgstr ", "
#: src/filetransferpseudojob.cpp:48
#, kde-format
msgctxt "Job heading, like 'Copying'"
@@ -1382,67 +1400,67 @@ msgstr "%1 已被踢出這個聊天室。"
msgid "Removes the user from the room"
msgstr "從聊天室移除使用者"
#: src/models/emojimodel.cpp:155 src/models/emojimodel.cpp:213
#: src/models/emojimodel.cpp:153 src/models/emojimodel.cpp:211
#, kde-format
msgctxt "Previously used emojis"
msgid "History"
msgstr "歷史"
#: src/models/emojimodel.cpp:160
#: src/models/emojimodel.cpp:158
#, kde-format
msgctxt "'Smileys' is a category of emoji"
msgid "Smileys"
msgstr "微笑"
#: src/models/emojimodel.cpp:165
#: src/models/emojimodel.cpp:163
#, kde-format
msgctxt "'People' is a category of emoji"
msgid "People"
msgstr "人們"
#: src/models/emojimodel.cpp:170
#: src/models/emojimodel.cpp:168
#, kde-format
msgctxt "'Nature' is a category of emoji"
msgid "Nature"
msgstr "自然"
#: src/models/emojimodel.cpp:175
#: src/models/emojimodel.cpp:173
#, kde-format
msgctxt "'Food' is a category of emoji"
msgid "Food"
msgstr "食物"
#: src/models/emojimodel.cpp:180
#: src/models/emojimodel.cpp:178
#, kde-format
msgctxt "'Activities' is a category of emoji"
msgid "Activities"
msgstr "活動"
#: src/models/emojimodel.cpp:185
#: src/models/emojimodel.cpp:183
#, kde-format
msgctxt "'Travel' is a category of emoji"
msgid "Travel"
msgstr "旅行"
#: src/models/emojimodel.cpp:190
#: src/models/emojimodel.cpp:188
#, kde-format
msgctxt "'Objects' is a category of emoji"
msgid "Objects"
msgstr "物體"
#: src/models/emojimodel.cpp:195
#: src/models/emojimodel.cpp:193
#, kde-format
msgctxt "'Symbols' is a category of emoji"
msgid "Symbols"
msgstr "符號"
#: src/models/emojimodel.cpp:200
#: src/models/emojimodel.cpp:198
#, kde-format
msgctxt "'Flags' is a category of emoji"
msgid "Flags"
msgstr "旗幟"
#: src/models/emojimodel.cpp:219
#: src/models/emojimodel.cpp:217
#, kde-format
msgctxt "'Custom' is a category of emoji"
msgid "Custom"
@@ -1460,45 +1478,45 @@ msgctxt "As in 'The user's own emojis"
msgid "Own Emojis"
msgstr "自己的表情符號"
#: src/models/messagecontentmodel.cpp:223
#: src/models/messagecontentmodel.cpp:216
#: src/timeline/LinkPreviewLoadComponent.qml:66
#, kde-format
msgid "Loading reply"
msgstr "載入回覆中"
#: src/models/messagefiltermodel.cpp:164
#: src/models/messagefiltermodel.cpp:162
#, kde-format
msgctxt "%1: What's being done; %2: How often it is done."
msgid " %1"
msgid_plural " %1 %2 times"
msgstr[0] " %1 %2 次"
#: src/models/messagefiltermodel.cpp:168
#: src/models/messagefiltermodel.cpp:166
#, kde-format
msgctxt "n users"
msgid " %1 user "
msgid_plural " %1 users "
msgstr[0] " %1 名使用者 "
#: src/models/messagefiltermodel.cpp:177
#: src/models/messagefiltermodel.cpp:175
#, kde-format
msgctxt "[action 1], [action 2 and/or action 3]"
msgid ", "
msgstr ""
#: src/models/messagefiltermodel.cpp:181
#: src/models/messagefiltermodel.cpp:179
#, kde-format
msgctxt "[action 1, action 2] or [action 3]"
msgid " or "
msgstr " 或 "
#: src/models/messagefiltermodel.cpp:181
#: src/models/messagefiltermodel.cpp:179
#, kde-format
msgctxt "[action 1, action 2] and [action 3]"
msgid " and "
msgstr " 和 "
#: src/models/messagefiltermodel.cpp:187
#: src/models/messagefiltermodel.cpp:185
#, kde-format
msgctxt ""
"userText (%1) is either a Matrix username if a single user sent all the "
@@ -1713,121 +1731,103 @@ msgid "%2 reacted with %3"
msgid_plural "%2 reacted with %3"
msgstr[0] "%2 用 %3 反應"
#: src/models/readmarkermodel.cpp:110
#, kde-format
msgid "1 user: "
msgid_plural "%1 users: "
msgstr[0] "%1 名使用者:"
#: src/models/readmarkermodel.cpp:115
#, kde-format
msgctxt "A member who is not in the room has been requested."
msgid "unknown member"
msgstr "未知成員"
#: src/models/readmarkermodel.cpp:117
#, kde-format
msgctxt "list separator"
msgid ", "
msgstr ", "
#: src/neochatconnection.cpp:86
#: src/neochatconnection.cpp:85
#, kde-format
msgid "File too large to download."
msgstr "要下載的檔案太大了。"
#: src/neochatconnection.cpp:86
#: src/neochatconnection.cpp:85
#, kde-format
msgid "Contact your matrix server administrator for support."
msgstr "請聯絡您的 matrix 伺服器管理員以求支援。"
#: src/neochatconnection.cpp:317
#: src/neochatconnection.cpp:296
#, kde-format
msgctxt "@info"
msgid "No identity server configured"
msgstr "未設定身份伺服器"
#: src/neochatconnection.cpp:348
#: src/neochatconnection.cpp:327
#, kde-format
msgid "Room creation failed: %1"
msgstr "聊天室建立失敗:%1"
#: src/neochatconnection.cpp:385
#: src/neochatconnection.cpp:364
#, kde-format
msgid "Space creation failed: %1"
msgstr "聊天空間建立失敗:%1"
#: src/neochatroom.cpp:1403
#: src/neochatroom.cpp:1352
#, kde-format
msgid "Report sent successfully."
msgstr "已成功傳送檢舉"
#: src/neochatroom.cpp:1724 src/neochatroom.cpp:1732
#: src/neochatroom.cpp:1622 src/neochatroom.cpp:1630
#, kde-format
msgctxt "'Lat' and 'Lon' as in Latitude and Longitude"
msgid "Lat: %1, Lon: %2"
msgstr "緯度:%2經度%1"
#: src/notificationsmanager.cpp:126 src/notificationsmanager.cpp:330
#: src/notificationsmanager.cpp:120 src/notificationsmanager.cpp:325
#, kde-format
msgid "Encrypted Message"
msgstr "已加密訊息"
#: src/notificationsmanager.cpp:210 src/qml/Main.qml:279
#: src/notificationsmanager.cpp:204 src/qml/Main.qml:279
#, kde-format
msgid "%1: %2"
msgstr "%1: %2"
#: src/notificationsmanager.cpp:216
#: src/notificationsmanager.cpp:210
#, kde-format
msgid "Open NeoChat in this room"
msgstr "在這個聊天室開啟 NeoChat"
#: src/notificationsmanager.cpp:229 src/qml/DelegateContextMenu.qml:98
#: src/notificationsmanager.cpp:223 src/qml/DelegateContextMenu.qml:98
#: src/qml/HoverActions.qml:111
#, kde-format
msgid "Reply"
msgstr "回覆"
#: src/notificationsmanager.cpp:230
#: src/notificationsmanager.cpp:224
#, kde-format
msgid "Reply..."
msgstr "回覆..."
#: src/notificationsmanager.cpp:249
#: src/notificationsmanager.cpp:243
#, kde-format
msgid "%1 invited you to a room"
msgstr "%1 邀請了您到聊天室"
#: src/notificationsmanager.cpp:252
#: src/notificationsmanager.cpp:247
#, kde-format
msgid "Open this invitation in NeoChat"
msgstr "在 NeoChat 開啟這個邀請"
#: src/notificationsmanager.cpp:262
#: src/notificationsmanager.cpp:257
#, kde-format
msgctxt "@action:button The thing being accepted is an invitation to chat"
msgid "Accept"
msgstr "接受"
#: src/notificationsmanager.cpp:263
#: src/notificationsmanager.cpp:258
#, kde-format
msgctxt "@action:button The thing being rejected is an invitation to chat"
msgid "Reject"
msgstr "拒絕"
#: src/notificationsmanager.cpp:264
#: src/notificationsmanager.cpp:259
#, kde-format
msgctxt "@action:button The thing being rejected is an invitation to chat"
msgid "Reject and Ignore User"
msgstr "拒絕並忽略使用者"
#: src/notificationsmanager.cpp:323
#: src/notificationsmanager.cpp:318
#, kde-format
msgid "%1 (%2)"
msgstr "%1 (%2)"
#: src/notificationsmanager.cpp:334
#: src/notificationsmanager.cpp:329
#, kde-format
msgid "Open NeoChat"
msgstr "開啟 NeoChat"
@@ -2751,23 +2751,18 @@ msgstr "鏡射"
msgid "Accept this invitation?"
msgstr "接收邀請嗎?"
#: src/qml/InvitationView.qml:18
#, kde-format
msgid "You can reject invitations from unknown users under Security settings."
msgstr "您可以在安全性設定底下設定回絕來自未知使用者的邀請。"
#: src/qml/InvitationView.qml:24
#: src/qml/InvitationView.qml:21
#, kde-format
msgctxt "@action:button The thing being rejected is an invitation to chat"
msgid "Reject and ignore user"
msgstr "拒絕並忽略使用者"
#: src/qml/InvitationView.qml:33
#: src/qml/InvitationView.qml:30
#, kde-format
msgid "Reject"
msgstr "拒絕"
#: src/qml/InvitationView.qml:40 src/qml/KeyVerificationDialog.qml:92
#: src/qml/InvitationView.qml:37 src/qml/KeyVerificationDialog.qml:92
#, kde-format
msgid "Accept"
msgstr "接受"
@@ -3519,27 +3514,27 @@ msgctxt "'Space' is a matrix space"
msgid "Leave Space"
msgstr "離開聊天空間"
#: src/qml/TimelineView.qml:196
#: src/qml/TimelineView.qml:195
#, kde-format
msgid "Jump to first unread message"
msgstr "跳到第一個未讀訊息"
#: src/qml/TimelineView.qml:196
#: src/qml/TimelineView.qml:195
#, kde-format
msgid "Jump to oldest loaded message"
msgstr "跳到已載入的最舊的訊息"
#: src/qml/TimelineView.qml:236
#: src/qml/TimelineView.qml:235
#, kde-format
msgid "Jump to latest message"
msgstr "跳到最新訊息"
#: src/qml/TimelineView.qml:261
#: src/qml/TimelineView.qml:260
#, kde-format
msgid "Drag items here to share them"
msgstr "拖曳項目至此來分享它"
#: src/qml/TimelineView.qml:268
#: src/qml/TimelineView.qml:267
#, kde-format
msgctxt "Message displayed when some users are typing"
msgid "%2 is typing"
@@ -3930,27 +3925,27 @@ msgstr "繼續"
msgid "Working"
msgstr "處理中"
#: src/roommanager.cpp:138
#: src/roommanager.cpp:125
#, kde-format
msgid "Malformed or empty Matrix id"
msgstr "不正確或空白的 Matrix ID"
#: src/roommanager.cpp:138
#: src/roommanager.cpp:125
#, kde-format
msgid "%1 is not a correct Matrix identifier"
msgstr "%1 不是一個正確的 Matrix ID"
#: src/roommanager.cpp:345
#: src/roommanager.cpp:337
#, kde-format
msgid "Failed to join room"
msgstr "加入聊天室失敗"
#: src/roommanager.cpp:377
#: src/roommanager.cpp:369
#, kde-format
msgid "You requested to join '%1'"
msgstr "您已請求加入 '%1'"
#: src/roommanager.cpp:381
#: src/roommanager.cpp:373
#, kde-format
msgid "Failed to request joining room"
msgstr "請求加入聊天室失敗"
@@ -4349,7 +4344,7 @@ msgid "Keyword…"
msgstr "關鍵字…"
#: src/settings/GlobalNotificationsPage.qml:116
#: src/settings/Permissions.qml:386 src/settings/PushNotification.qml:119
#: src/settings/Permissions.qml:391 src/settings/PushNotification.qml:119
#, kde-format
msgid "Add keyword"
msgstr "新增關鍵字"
@@ -4410,7 +4405,7 @@ msgid "Ignored Users"
msgstr "已忽略的使用者"
#: src/settings/IgnoredUsersDialog.qml:24
#: src/settings/NeoChatSecurityPage.qml:35
#: src/settings/NeoChatSecurityPage.qml:37
#, kde-format
msgctxt "@title:group"
msgid "Ignored Users"
@@ -4540,61 +4535,37 @@ msgstr "安全性"
#: src/settings/NeoChatSecurityPage.qml:20
#, kde-format
msgctxt "@title:group"
msgid "Invitations"
msgstr "邀請"
#: src/settings/NeoChatSecurityPage.qml:24
#, kde-format
msgctxt "@option:check"
msgid "Reject invitations from unknown users"
msgstr "回絕來自未知使用者的邀請"
msgctxt "@title"
msgid "Keys"
msgstr "金鑰"
#: src/settings/NeoChatSecurityPage.qml:25
#, kde-format
msgid ""
"If enabled, NeoChat will reject invitations from from users you don't share "
"a room with."
msgstr ""
msgid "Device key"
msgstr "裝置金鑰"
#: src/settings/NeoChatSecurityPage.qml:25
#: src/settings/NeoChatSecurityPage.qml:29
#, kde-format
msgid "Your server does not support this setting."
msgstr "您的伺服器不支援這個設定。"
msgid "Encryption key"
msgstr "加密金鑰"
#: src/settings/NeoChatSecurityPage.qml:39
#: src/settings/NeoChatSecurityPage.qml:33
#, kde-format
msgid "Device id"
msgstr "裝置 ID"
#: src/settings/NeoChatSecurityPage.qml:41
#, kde-format
msgctxt "@action:button"
msgid "Manage ignored users"
msgstr "管理已忽略的使用者"
#: src/settings/NeoChatSecurityPage.qml:41
#: src/settings/NeoChatSecurityPage.qml:43
#, kde-format
msgctxt "@title:window"
msgid "Ignored Users"
msgstr "已忽略的使用者"
#: src/settings/NeoChatSecurityPage.qml:46
#, kde-format
msgctxt "@title"
msgid "Keys"
msgstr "金鑰"
#: src/settings/NeoChatSecurityPage.qml:51
#, kde-format
msgid "Device key"
msgstr "裝置金鑰"
#: src/settings/NeoChatSecurityPage.qml:55
#, kde-format
msgid "Encryption key"
msgstr "加密金鑰"
#: src/settings/NeoChatSecurityPage.qml:59
#, kde-format
msgid "Device id"
msgstr "裝置 ID"
#: src/settings/NeoChatSettingsView.qml:22 src/settings/RoomGeneralPage.qml:22
#: src/settings/RoomSettingsView.qml:39
#, kde-format
@@ -4748,27 +4719,27 @@ msgctxt "@label:textbox"
msgid "Password:"
msgstr "密碼:"
#: src/settings/Permissions.qml:32
#: src/settings/Permissions.qml:37
#, kde-format
msgid "Privileged Users"
msgstr "有權力的使用者"
#: src/settings/Permissions.qml:233
#: src/settings/Permissions.qml:238
#, kde-format
msgid "Default permissions"
msgstr "預設權限"
#: src/settings/Permissions.qml:273
#: src/settings/Permissions.qml:278
#, kde-format
msgid "Basic permissions"
msgstr "基本權限"
#: src/settings/Permissions.qml:313
#: src/settings/Permissions.qml:318
#, kde-format
msgid "Event permissions"
msgstr "事件fm0vu0"
#: src/settings/Permissions.qml:360
#: src/settings/Permissions.qml:365
#, kde-format
msgid "Event Type…"
msgstr "事件類型…"
@@ -5236,14 +5207,14 @@ msgid ""
"device."
msgstr "這個訊息已加密而傳送者並未與本裝置分享金鑰。"
#: src/timeline/FileComponent.qml:103 src/timeline/FileComponent.qml:186
#: src/timeline/FileComponent.qml:107 src/timeline/FileComponent.qml:190
#, kde-format
msgctxt ""
"tooltip for a button on a message; offers ability to download its file"
msgid "Download"
msgstr "下載"
#: src/timeline/FileComponent.qml:119 src/timeline/FileComponent.qml:176
#: src/timeline/FileComponent.qml:123 src/timeline/FileComponent.qml:180
#, kde-format
msgctxt ""
"tooltip for a button on a message; offers ability to open its downloaded "
@@ -5251,13 +5222,13 @@ msgctxt ""
msgid "Open File"
msgstr "開啟檔案"
#: src/timeline/FileComponent.qml:134
#: src/timeline/FileComponent.qml:138
#, kde-format
msgctxt "file download progress"
msgid "%1 / %2"
msgstr "%1 / %2"
#: src/timeline/FileComponent.qml:139
#: src/timeline/FileComponent.qml:143
#, kde-format
msgctxt ""
"tooltip for a button on a message; stops downloading the message's file"
@@ -5343,7 +5314,7 @@ msgctxt "as in 'this vote has ended'"
msgid "(Ended)"
msgstr "(已結束)"
#: src/timeline/ReadMarkerDelegate.qml:42
#: src/timeline/ReadMarkerDelegate.qml:39
#, kde-format
msgctxt "Relative time since the room was last read"
msgid "Last read: %1"
@@ -5362,18 +5333,18 @@ msgstr "這是此聊天的開頭。沒有比這更早的歷史訊息。"
msgid "Pl. %1"
msgstr "第 %1 月台"
#: src/timeline/VideoComponent.qml:197
#: src/timeline/VideoComponent.qml:171
#, kde-format
msgid "Video"
msgstr "影片"
#: src/timeline/VideoComponent.qml:261
#: src/timeline/VideoComponent.qml:235
#, kde-format
msgctxt "@action:button"
msgid "Volume"
msgstr "音量"
#: src/timeline/VideoComponent.qml:345
#: src/timeline/VideoComponent.qml:319
#, kde-format
msgid "Maximize"
msgstr "最大化"

View File

@@ -134,8 +134,6 @@ add_library(neochat STATIC
jobs/neochatdeletedevicejob.h
jobs/neochatchangepasswordjob.cpp
jobs/neochatchangepasswordjob.h
jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp
mediasizehelper.h
eventhandler.cpp
@@ -188,10 +186,6 @@ add_library(neochat STATIC
models/permissionsmodel.h
threepidbindhelper.cpp
threepidbindhelper.h
models/readmarkermodel.cpp
models/readmarkermodel.h
neochatroommember.cpp
neochatroommember.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -288,6 +282,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
qml/AccountDialog.qml
DEPENDENCIES
QtCore
QtQuick

View File

@@ -61,6 +61,20 @@ MessageComponentType::Type EventHandler::messageComponentType() const
return MessageComponentType::typeForEvent(*m_event);
}
Quotient::RoomMember EventHandler::getAuthor(bool isPending) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user.";
return {};
}
return isPending ? m_room->localMember() : m_room->member(m_event->senderId());
}
QString EventHandler::getAuthorDisplayName(bool isPending) const
{
if (m_room == nullptr) {
@@ -143,11 +157,6 @@ QString EventHandler::getTimeString(bool relative, QLocale::FormatType format, b
return {};
}
QString EventHandler::getTimeString(const QString &format, bool isPending, const QDateTime &lastUpdated)
{
return getTime(isPending, lastUpdated).toLocalTime().toString(format);
}
bool EventHandler::isHighlighted()
{
if (m_room == nullptr) {
@@ -232,19 +241,17 @@ Qt::TextFormat EventHandler::messageBodyInputFormat(const Quotient::RoomMessageE
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
{
QString body;
if (event.hasFileContent()) {
// if filename is given or body is equal to filename,
// then body is a caption
QString filename = event.content()->fileInfo()->originalName;
QString body = event.plainBody();
if (filename.isEmpty() || filename == body) {
return QString();
auto fileCaption = event.content()->fileInfo()->originalName;
if (fileCaption.isEmpty()) {
fileCaption = event.plainBody();
} else if (event.content()->fileInfo()->originalName != event.plainBody()) {
fileCaption = event.plainBody() + " | "_ls + fileCaption;
}
return body;
return fileCaption;
}
QString body;
if (event.hasTextContent() && event.content()) {
body = static_cast<const EventContent::TextContent *>(event.content())->body;
} else {
@@ -653,30 +660,23 @@ QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event)
QString eventId = event->id();
// Get the file info for the event.
const EventContent::FileInfo *fileInfo;
bool isSticker = false;
if (event->is<RoomMessageEvent>()) {
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
if (!roomMessageEvent->hasFileContent()) {
return {};
}
const EventContent::FileInfo *fileInfo;
fileInfo = roomMessageEvent->content()->fileInfo();
QVariantMap mediaInfo = getMediaInfoFromFileInfo(fileInfo, eventId, false, false);
// if filename isn't specifically given, it is in body
// https://spec.matrix.org/latest/client-server-api/#mfile
mediaInfo["filename"_ls] = (fileInfo->originalName.isEmpty()) ? roomMessageEvent->plainBody() : fileInfo->originalName;
return mediaInfo;
} else if (event->is<StickerEvent>()) {
const EventContent::FileInfo *fileInfo;
auto stickerEvent = eventCast<const StickerEvent>(event);
fileInfo = &stickerEvent->image();
return getMediaInfoFromFileInfo(fileInfo, eventId, false, true);
isSticker = true;
} else {
return {};
}
return getMediaInfoFromFileInfo(fileInfo, eventId, false, isSticker);
}
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const
@@ -952,4 +952,102 @@ QString EventHandler::getLocationAssetType() const
return assetType;
}
bool EventHandler::hasReadMarkers() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "hasReadMarkers called with m_room set to nullptr.";
return false;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "hasReadMarkers called with m_event set to nullptr.";
return false;
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localMember().id());
return userIds.size() > 0;
}
QList<Quotient::RoomMember> EventHandler::getReadMarkers(int maxMarkers) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_event set to nullptr.";
return {};
}
auto userIds_temp = m_room->userIdsAtEvent(m_event->id());
userIds_temp.remove(m_room->localMember().id());
auto userIds = userIds_temp.values();
if (userIds.count() > maxMarkers) {
userIds = userIds.mid(0, maxMarkers);
}
QList<Quotient::RoomMember> users;
users.reserve(userIds.size());
for (const auto &userId : userIds) {
users += m_room->member(userId);
}
return users;
}
QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_event set to nullptr.";
return {};
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localMember().id());
if (userIds.count() > maxMarkers) {
return QStringLiteral("+ ") + QString::number(userIds.count() - maxMarkers);
} else {
return QString();
}
}
QString EventHandler::getReadMarkersString() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkersString called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReadMarkersString called with m_event set to nullptr.";
return {};
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localMember().id());
/**
* The string ends up in the form
* "x users: user1DisplayName, user2DisplayName, etc."
*/
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) {
auto member = m_room->member(userId);
QString displayName;
if (!m_room->memberIds().contains(userId)) {
displayName = i18nc("A member who is not in the room has been requested.", "unknown member");
} else {
displayName = member.displayName();
}
readMarkersString += displayName + i18nc("list separator", ", ");
}
readMarkersString.chop(2);
return readMarkersString;
}
#include "moc_eventhandler.cpp"

View File

@@ -53,10 +53,25 @@ public:
*/
MessageComponentType::Type messageComponentType() const;
/**
* @brief Get the author of the event in context of the room.
*
* An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
* the room or event initialised.
*
* @param isPending if the event is pending, i.e. has not been confirmed by
* the server.
*
* @return a Quotient::RoomMember object for the user.
*
* @sa Quotient::RoomMember
*/
Quotient::RoomMember getAuthor(bool isPending = false) const;
/**
* @brief Get the display name of the event author.
*
* This method is special in that it will return
* This method is separate from getAuthor() and special in that it will return
* the old display name of the author if the current event is one that caused it
* to change. This allows for scenarios where the UI wishes to notify that a
* user's display name has changed and what it changed from.
@@ -98,8 +113,6 @@ public:
*/
QString getTimeString(bool relative, QLocale::FormatType format = QLocale::ShortFormat, bool isPending = false, QDateTime lastUpdated = {}) const;
QString getTimeString(const QString &format, bool isPending = false, const QDateTime &lastUpdated = {});
/**
* @brief Whether the event should be highlighted in the timeline.
*
@@ -329,6 +342,43 @@ public:
*/
QString getLocationAssetType() const;
/**
* @brief Whether the event has any read marker for other users.
*/
bool hasReadMarkers() const;
/**
* @brief Returns a list of user read marker for the event.
*
* @param maxMarkers the maximum number of users to return. Usually the number
* of user read makers shown is limited to not clutter the UI.
* This needs to be the same as used in getNumberExcessReadMarkers
* so that the markers line up with the number displayed, i.e.
* the number of users shown plus the excess number will be
* the total number of other user read markers at an event.
*/
QList<Quotient::RoomMember> getReadMarkers(int maxMarkers = 5) const;
/**
* @brief Returns the number of excess user read markers for the event.
*
* This returns a string in the form "+ x" ready for use in the UI.
*
* @param maxMarkers the maximum number of markers shown in the UI. This needs to
* be the same as used in getReadMarkers so that the value lines
* up with the number displayed, i.e. the number of users shown
* plus the excess number will be the total number of other user
* read markers at an event.
*/
QString getNumberExcessReadMarkers(int maxMarkers = 5) const;
/**
* @brief Returns a string with the names of the read markers at the event.
*
* This is in the form "x users: name 1, name 2, ...".
*/
QString getReadMarkersString() const;
private:
const NeoChatRoom *m_room = nullptr;
const Quotient::RoomEvent *m_event = nullptr;

View File

@@ -51,3 +51,9 @@ struct ForeignSSSSHandler {
QML_FOREIGN(Quotient::SSSSHandler)
QML_NAMED_ELEMENT(SSSSHandler)
};
struct RoomMemberForeign {
Q_GADGET
QML_FOREIGN(Quotient::RoomMember)
QML_NAMED_ELEMENT(RoomMember)
};

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "neochatgetcommonroomsjob.h"
using namespace Quotient;
NeochatGetCommonRoomsJob::NeochatGetCommonRoomsJob(const QString &userId)
: BaseJob(HttpVerb::Get,
QStringLiteral("GetCommonRoomsJob"),
QStringLiteral("/_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms").toLatin1(),
QUrlQuery({{QStringLiteral("user_id"), userId}}))
{
}

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Quotient/jobs/basejob.h>
#include <Quotient/omittable.h>
// TODO: Upstream to libQuotient
class NeochatGetCommonRoomsJob : public Quotient::BaseJob
{
public:
explicit NeochatGetCommonRoomsJob(const QString &userId);
};

View File

@@ -9,16 +9,18 @@
#include "customemojimodel.h"
#include "emojimodel.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "userlistmodel.h"
CompletionModel::CompletionModel(QObject *parent)
: QAbstractListModel(parent)
, m_filterModel(new CompletionProxyModel())
, m_userListModel(RoomManager::instance().userListModel())
, m_userListModel(new UserListModel(this))
, m_emojiModel(new QConcatenateTablesProxyModel(this))
{
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
connect(this, &CompletionModel::roomChanged, this, [this]() {
m_userListModel->setRoom(m_room);
});
m_emojiModel->addSourceModel(&CustomEmojiModel::instance());
m_emojiModel->addSourceModel(&EmojiModel::instance());
}

View File

@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "customemojimodel.h"
#include <QRegularExpression>
class NeoChatConnection;
struct CustomEmoji {
QString name; // with :semicolons:
QString url; // mxc://
QRegularExpression regexp;
};
struct CustomEmojiModel::Private {
QPointer<NeoChatConnection> connection;
QList<CustomEmoji> emojies;
};

View File

@@ -26,7 +26,7 @@ int EmojiModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
int total = 0;
for (const auto &category : std::as_const(_emojis)) {
for (const auto &category : _emojis) {
total += category.count();
}
return total;
@@ -35,7 +35,7 @@ int EmojiModel::rowCount(const QModelIndex &parent) const
QVariant EmojiModel::data(const QModelIndex &index, int role) const
{
auto row = index.row();
for (const auto &category : std::as_const(_emojis)) {
for (const auto &category : _emojis) {
if (row >= category.count()) {
row -= category.count();
continue;
@@ -79,8 +79,7 @@ QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
{
QVariantList result;
const auto &values = _emojis.values();
for (const auto &e : values) {
for (const auto &e : _emojis.values()) {
for (const auto &variant : e) {
const auto &emoji = qvariant_cast<Emoji>(variant);
if (emoji.shortName.contains(filter, Qt::CaseInsensitive)) {
@@ -122,8 +121,7 @@ QVariantList EmojiModel::emojis(Category category) const
}
if (category == HistoryNoCustom) {
QVariantList list;
const auto &history = emojiHistory();
for (const auto &e : history) {
for (const auto &e : emojiHistory()) {
auto emoji = qvariant_cast<Emoji>(e);
if (!emoji.isCustom) {
list.append(e);
@@ -226,9 +224,8 @@ QVariantList EmojiModel::categoriesWithCustom() const
QVariantList EmojiModel::emojiHistory() const
{
QVariantList list;
const auto &lastUsed = lastUsedEmojis();
for (const auto &historicEmoji : lastUsed) {
for (const auto &emojiCategory : std::as_const(_emojis)) {
for (const auto &historicEmoji : lastUsedEmojis()) {
for (const auto &emojiCategory : _emojis) {
for (const auto &emoji : emojiCategory) {
if (qvariant_cast<Emoji>(emoji).shortName == historicEmoji) {
list.append(emoji);

View File

@@ -30,6 +30,40 @@ bool MediaMessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex
QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
{
if (role == SourceRole) {
if (mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("image"))) {
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("source")].toUrl();
} else if (mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("video"))) {
auto progressInfo = mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>();
if (progressInfo.completed()) {
return mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>().localPath;
} else {
return QUrl();
}
} else {
return QUrl();
}
}
if (role == TempSourceRole) {
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("tempInfo")].toMap()[QStringLiteral("source")].toUrl();
}
if (role == TypeRole) {
if (mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("image"))) {
return MediaType::Image;
} else {
return MediaType::Video;
}
}
if (role == CaptionRole) {
return mapToSource(index).data(Qt::DisplayRole);
}
if (role == SourceWidthRole) {
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("width")].toFloat();
}
if (role == SourceHeightRole) {
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("height")].toFloat();
}
// We need to catch this one and return true if the next media object was
// on a different day.
if (role == MessageEventModel::ShowSectionRole) {
@@ -46,37 +80,6 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue<MessageContentModel *>(model);
}
QVariantMap mediaInfo = mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap();
if (role == TempSourceRole) {
return mediaInfo[QStringLiteral("tempInfo")].toMap()[QStringLiteral("source")].toUrl();
}
if (role == CaptionRole) {
return mapToSource(index).data(Qt::DisplayRole);
}
if (role == SourceWidthRole) {
return mediaInfo[QStringLiteral("width")].toFloat();
}
if (role == SourceHeightRole) {
return mediaInfo[QStringLiteral("height")].toFloat();
}
bool isVideo = mediaInfo[QStringLiteral("mimeType")].toString().contains(QStringLiteral("video"));
if (role == TypeRole) {
return (isVideo) ? MediaType::Video : MediaType::Image;
}
if (role == SourceRole) {
if (isVideo) {
auto progressInfo = mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>();
if (progressInfo.completed()) {
return mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>().localPath;
}
} else {
return mediaInfo[QStringLiteral("source")].toUrl();
}
}
return sourceModel()->data(mapToSource(index), role);
}

View File

@@ -3,7 +3,6 @@
#include "messagecontentmodel.h"
#include "neochatconfig.h"
#include "neochatroommember.h"
#include <QImageReader>
@@ -36,10 +35,10 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::Room
, m_room(room)
, m_eventId(event != nullptr ? event->id() : QString())
, m_eventSenderId(event != nullptr ? event->senderId() : QString())
, m_event(loadEvent<RoomEvent>(event->fullJson()))
, m_isPending(isPending)
, m_isReply(isReply)
{
intiializeEvent(event);
initializeModel();
}
@@ -82,7 +81,8 @@ void MessageContentModel::initializeModel()
if (m_eventId == serverEvent->id()) {
beginResetModel();
m_isPending = false;
intiializeEvent(serverEvent);
m_event = loadEvent<RoomEvent>(serverEvent->fullJson());
Q_EMIT eventUpdated();
endResetModel();
}
}
@@ -91,7 +91,8 @@ void MessageContentModel::initializeModel()
if (m_room != nullptr && m_event != nullptr) {
if (m_eventId == newEvent->id()) {
beginResetModel();
intiializeEvent(newEvent);
m_event = loadEvent<RoomEvent>(newEvent->fullJson());
Q_EMIT eventUpdated();
endResetModel();
}
}
@@ -107,9 +108,24 @@ void MessageContentModel::initializeModel()
}
});
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
if (m_room != nullptr && m_event != nullptr && eventId == m_eventId) {
if (m_event != nullptr && eventId == m_eventId) {
resetContent();
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
QString mxcUrl;
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
if (event->hasFileContent()) {
mxcUrl = event->content()->fileInfo()->url().toString();
}
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
mxcUrl = event->image().fileInfo()->url().toString();
}
if (mxcUrl.isEmpty()) {
return;
}
auto localPath = m_room->fileTransferInfo(m_eventId).localPath.toLocalFile();
auto config = KSharedConfig::openStateConfig(QStringLiteral("neochatdownloads"))->group(QStringLiteral("downloads"));
config.writePathEntry(mxcUrl.mid(6), localPath);
}
});
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
@@ -153,29 +169,6 @@ void MessageContentModel::initializeModel()
resetModel();
}
void MessageContentModel::intiializeEvent(const QString &eventId)
{
const auto newEvent = m_room->getEvent(eventId);
if (newEvent != nullptr) {
intiializeEvent(newEvent);
}
}
void MessageContentModel::intiializeEvent(const Quotient::RoomEvent *event)
{
m_event = loadEvent<RoomEvent>(event->fullJson());
auto senderId = event->senderId();
// A pending event might not have a sender ID set yet but in that case it must
// be the local member.
if (senderId.isEmpty()) {
senderId = m_room->localMember().id();
}
if (m_eventSenderObject == nullptr) {
m_eventSenderObject = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, senderId));
}
Q_EMIT eventUpdated();
}
bool MessageContentModel::showAuthor() const
{
return m_showAuthor;
@@ -258,16 +251,16 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
});
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
return eventHandler.getTimeString(QStringLiteral("hh:mm"), m_isPending, lastUpdated);
return eventHandler.getTimeString(false, QLocale::ShortFormat, m_isPending, lastUpdated);
}
if (role == AuthorRole) {
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
return QVariant::fromValue(eventHandler.getAuthor(m_isPending));
}
if (role == MediaInfoRole) {
return eventHandler.getMediaInfo();
}
if (role == FileTransferInfoRole) {
return QVariant::fromValue(m_room->cachedFileTransferInfo(m_event.get()));
return QVariant::fromValue(fileInfo());
}
if (role == ItineraryModelRole) {
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
@@ -444,37 +437,27 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
QList<MessageComponent> components;
components += MessageComponent{MessageComponentType::File, QString(), {}};
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
auto body = EventHandler::rawMessageBody(*event);
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
if (m_emptyItinerary) {
if (!m_isReply) {
auto fileTransferInfo = m_room->cachedFileTransferInfo(m_event.get());
auto fileTransferInfo = fileInfo();
#ifndef Q_OS_ANDROID
Q_ASSERT(event->content() != nullptr && event->content()->fileInfo() != nullptr);
const QMimeType mimeType = event->content()->fileInfo()->mimeType;
if (mimeType.name() == QStringLiteral("text/plain") || mimeType.parentMimeTypes().contains(QStringLiteral("text/plain"))) {
QString originalName = event->content()->fileInfo()->originalName;
if (originalName.isEmpty()) {
originalName = event->plainBody();
}
KSyntaxHighlighting::Repository repository;
KSyntaxHighlighting::Definition definitionForFile = repository.definitionForFileName(originalName);
if (!definitionForFile.isValid()) {
definitionForFile = repository.definitionForMimeType(mimeType.name());
}
QFile file(fileTransferInfo.localPath.path());
file.open(QIODevice::ReadOnly);
components += MessageComponent{MessageComponentType::Code,
QString::fromStdString(file.readAll().toStdString()),
{{QStringLiteral("class"), definitionForFile.name()}}};
}
KSyntaxHighlighting::Repository repository;
const auto definitionForFile = repository.definitionForFileName(fileTransferInfo.localPath.toString());
if (definitionForFile.isValid() || QFileInfo(fileTransferInfo.localPath.path()).suffix() == QStringLiteral("txt")) {
QFile file(fileTransferInfo.localPath.path());
file.open(QIODevice::ReadOnly);
components += MessageComponent{MessageComponentType::Code,
QString::fromStdString(file.readAll().toStdString()),
{{QStringLiteral("class"), definitionForFile.name()}}};
}
#endif
if (FileType::instance().fileHasImage(fileTransferInfo.localPath)) {
QImageReader reader(fileTransferInfo.localPath.path());
components += MessageComponent{MessageComponentType::Pdf, QString(), {{QStringLiteral("size"), reader.size()}}};
}
if (FileType::instance().fileHasImage(fileTransferInfo.localPath)) {
QImageReader reader(fileTransferInfo.localPath.path());
components += MessageComponent{MessageComponentType::Pdf, QString(), {{QStringLiteral("size"), reader.size()}}};
}
} else if (m_itineraryModel != nullptr) {
components += MessageComponent{MessageComponentType::Itinerary, QString(), {}};
@@ -484,12 +467,9 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
} else {
updateItineraryModel();
}
auto body = EventHandler::rawMessageBody(*event);
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
return components;
}
case MessageComponentType::Image:
case MessageComponentType::Audio:
case MessageComponentType::Video: {
if (!m_event->is<StickerEvent>()) {
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
@@ -572,7 +552,7 @@ void MessageContentModel::updateItineraryModel()
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
if (event->hasFileContent()) {
auto filePath = m_room->cachedFileTransferInfo(m_event.get()).localPath;
auto filePath = fileInfo().localPath;
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
delete m_itineraryModel;
m_itineraryModel = nullptr;
@@ -600,4 +580,42 @@ void MessageContentModel::updateItineraryModel()
}
}
FileTransferInfo MessageContentModel::fileInfo() const
{
if (m_room == nullptr || m_event == nullptr) {
return {};
}
QString mxcUrl;
int total;
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
if (event->hasFileContent()) {
mxcUrl = event->content()->fileInfo()->url().toString();
total = event->content()->fileInfo()->payloadSize;
}
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
mxcUrl = event->image().fileInfo()->url().toString();
total = event->image().fileInfo()->payloadSize;
}
auto config = KSharedConfig::openStateConfig(QStringLiteral("neochatdownloads"))->group(QStringLiteral("downloads"));
if (!config.hasKey(mxcUrl.mid(6))) {
return m_room->fileTransferInfo(m_eventId);
}
const auto path = config.readPathEntry(mxcUrl.mid(6), QString());
QFileInfo info(path);
if (!info.isFile()) {
config.deleteEntry(mxcUrl);
return m_room->fileTransferInfo(m_eventId);
}
// TODO: we could check the hash here
return FileTransferInfo{
.status = FileTransferInfo::Completed,
.isUpload = false,
.progress = total,
.total = total,
.localDir = QUrl(info.dir().path()),
.localPath = QUrl::fromLocalFile(path),
};
}
#include "moc_messagecontentmodel.cpp"

View File

@@ -6,13 +6,11 @@
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/roomevent.h>
#include <Quotient/room.h>
#include "enums/messagecomponenttype.h"
#include "eventhandler.h"
#include "itinerarymodel.h"
#include "neochatroommember.h"
struct MessageComponent {
MessageComponentType::Type type = MessageComponentType::Other;
@@ -117,7 +115,6 @@ private:
QPointer<NeoChatRoom> m_room;
QString m_eventId;
QString m_eventSenderId;
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
Quotient::RoomEventPtr m_event;
bool m_isPending;
@@ -125,8 +122,6 @@ private:
bool m_isReply;
void initializeModel();
void intiializeEvent(const QString &eventId);
void intiializeEvent(const Quotient::RoomEvent *event);
QList<MessageComponent> m_components;
void resetModel();
@@ -146,4 +141,6 @@ private:
void updateItineraryModel();
bool m_emptyItinerary = false;
Quotient::FileTransferInfo fileInfo() const;
};

View File

@@ -26,9 +26,6 @@
#include "messagecontentmodel.h"
#include "models/messagefiltermodel.h"
#include "models/reactionmodel.h"
#include "neochatroom.h"
#include "neochatroommember.h"
#include "readmarkermodel.h"
#include "texthandler.h"
using namespace Quotient;
@@ -48,6 +45,8 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[ThreadRootRole] = "threadRoot";
roles[ShowSectionRole] = "showSection";
roles[ReadMarkersRole] = "readMarkers";
roles[ExcessReadMarkersRole] = "excessReadMarkers";
roles[ReadMarkersStringRole] = "readMarkersString";
roles[ShowReadMarkersRole] = "showReadMarkers";
roles[ReactionRole] = "reaction";
roles[ShowReactionsRole] = "showReactions";
@@ -88,14 +87,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
// HACK: Reset the model to a null room first to make sure QML dismantles
// last room's objects before the room is actually changed
beginResetModel();
m_readMarkerModels.clear();
m_currentRoom->disconnect(this);
m_currentRoom = nullptr;
endResetModel();
// Don't clear the member objects until the model has been fully reset and all
// refs cleared.
m_memberObjects.clear();
}
beginResetModel();
@@ -106,7 +100,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
room->setDisplayed();
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
createEventObjects(&*event->viewAs<RoomEvent>());
if (const auto &roomMessageEvent = &*event->viewAs<RoomMessageEvent>()) {
createEventObjects(roomMessageEvent);
}
if (event->event()->is<PollStartEvent>()) {
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event->event()));
}
@@ -119,7 +115,11 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
for (auto &&event : events) {
createEventObjects(event.get());
const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
if (message != nullptr) {
createEventObjects(message);
}
if (event->is<PollStartEvent>()) {
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
}
@@ -129,7 +129,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
});
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
for (auto &event : events) {
createEventObjects(event.get());
if (const auto &roomMessageEvent = dynamic_cast<RoomMessageEvent *>(event.get())) {
createEventObjects(roomMessageEvent);
}
if (event->is<PollStartEvent>()) {
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
}
@@ -156,9 +158,8 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
refreshLastUserEvents(i);
}
});
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] {
m_initialized = true;
createEventObjects(event);
beginInsertRows({}, 0, 0);
});
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
@@ -194,7 +195,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
moveReadMarker(toEventId);
});
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
createEventObjects(newEvent);
const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent);
if (message != nullptr) {
createEventObjects(message);
}
});
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
if (eventId.isEmpty()) { // How did we get here?
@@ -202,7 +206,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
}
const auto eventIt = m_currentRoom->findInTimeline(eventId);
if (eventIt != m_currentRoom->historyEdge()) {
createEventObjects(eventIt->event());
if (const auto &event = dynamic_cast<const RoomMessageEvent *>(&**eventIt)) {
createEventObjects(event);
}
if (eventIt->event()->is<PollStartEvent>()) {
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(eventIt->event()));
}
@@ -210,10 +216,11 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
refreshEventRoles(eventId, {Qt::DisplayRole});
});
connect(m_currentRoom, &Room::changed, this, [this](Room::Changes changes) {
if (changes.testFlag(Quotient::Room::Change::Other)) {
if (changes & (Room::Change::PartiallyReadStats | Room::Change::UnreadStats | Room::Change::Other | Room::Change::Members)) {
// this is slow
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
createEventObjects(it->event());
auto event = it->event();
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole, ExcessReadMarkersRole});
}
}
});
@@ -221,6 +228,22 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
beginResetModel();
endResetModel();
});
connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) {
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
auto event = it->event();
if (event->senderId() == member.id()) {
refreshEventRoles(event->id(), {AuthorRole});
}
}
});
connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
auto event = it->event();
if (event->senderId() == member.id()) {
refreshEventRoles(event->id(), {AuthorRole});
}
}
});
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localMember().id();
} else {
@@ -388,8 +411,6 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
}
}
static NeochatRoomMember *emptyNeochatRoomMember = new NeochatRoomMember;
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
{
if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
@@ -462,18 +483,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == AuthorRole) {
QString mId;
if (isPending) {
mId = m_currentRoom->localMember().id();
} else {
mId = evt.senderId();
}
if (!m_memberObjects.contains(mId)) {
return QVariant::fromValue<NeochatRoomMember *>(emptyNeochatRoomMember);
}
return QVariant::fromValue<NeochatRoomMember *>(m_memberObjects.at(mId).get());
return QVariant::fromValue(eventHandler.getAuthor(isPending));
}
if (role == HighlightRole) {
@@ -504,11 +514,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
if (role == ProgressInfoRole) {
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
if (e->hasFileContent()) {
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
}
}
if (eventCast<const StickerEvent>(&evt)) {
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
if (auto e = eventCast<const StickerEvent>(&evt)) {
return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
}
}
@@ -547,15 +557,19 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == ReadMarkersRole) {
if (m_readMarkerModels.contains(evt.id())) {
return QVariant::fromValue<ReadMarkerModel *>(m_readMarkerModels[evt.id()].get());
} else {
return QVariantList();
}
return QVariant::fromValue(eventHandler.getReadMarkers());
}
if (role == ExcessReadMarkersRole) {
return eventHandler.getNumberExcessReadMarkers();
}
if (role == ReadMarkersStringRole) {
return eventHandler.getReadMarkersString();
}
if (role == ShowReadMarkersRole) {
return m_readMarkerModels.contains(evt.id());
return eventHandler.hasReadMarkers();
}
if (role == ReactionRole) {
@@ -616,71 +630,30 @@ int MessageEventModel::eventIdToRow(const QString &eventID) const
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
}
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *event)
{
if (event == nullptr) {
return;
}
auto eventId = event->id();
auto senderId = event->senderId();
// A pending event might not have a sender ID set yet but in that case it must
// be the local member.
if (senderId.isEmpty()) {
senderId = m_currentRoom->localMember().id();
}
if (!m_memberObjects.contains(senderId)) {
m_memberObjects[senderId] = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_currentRoom, senderId));
}
// ReadMarkerModel handles updates to add and remove markers, we only need to
// ReactionModel handles updates to add and remove reactions, we only need to
// handle adding and removing whole models here.
if (m_readMarkerModels.contains(eventId)) {
if (m_reactionModels.contains(eventId)) {
// If a model already exists but now has no reactions remove it
if (m_readMarkerModels[eventId]->rowCount() <= 0) {
m_readMarkerModels.remove(eventId);
if (m_reactionModels[eventId]->rowCount() <= 0) {
m_reactionModels.remove(eventId);
if (!resetting) {
refreshEventRoles(eventId, {ReadMarkersRole, ShowReadMarkersRole});
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
}
}
} else {
auto memberIds = m_currentRoom->userIdsAtEvent(eventId);
memberIds.remove(m_currentRoom->localMember().id());
if (memberIds.size() > 0) {
if (m_currentRoom->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
// If a model doesn't exist and there are reactions add it.
auto newModel = QSharedPointer<ReadMarkerModel>(new ReadMarkerModel(eventId, m_currentRoom));
if (newModel->rowCount() > 0) {
m_readMarkerModels[eventId] = newModel;
if (!resetting) {
refreshEventRoles(eventId, {ReadMarkersRole, ShowReadMarkersRole});
}
}
}
}
if (const auto roomEvent = eventCast<const RoomMessageEvent>(event)) {
// ReactionModel handles updates to add and remove reactions, we only need to
// handle adding and removing whole models here.
if (m_reactionModels.contains(eventId)) {
// If a model already exists but now has no reactions remove it
if (m_reactionModels[eventId]->rowCount() <= 0) {
m_reactionModels.remove(eventId);
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(event, m_currentRoom));
if (reactionModel->rowCount() > 0) {
m_reactionModels[eventId] = reactionModel;
if (!resetting) {
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
}
}
} else {
if (m_currentRoom->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
// If a model doesn't exist and there are reactions add it.
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(roomEvent, m_currentRoom));
if (reactionModel->rowCount() > 0) {
m_reactionModels[eventId] = reactionModel;
if (!resetting) {
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
}
}
}
}
}
}

View File

@@ -9,9 +9,7 @@
#include "linkpreviewer.h"
#include "neochatroom.h"
#include "neochatroommember.h"
#include "pollhandler.h"
#include "readmarkermodel.h"
class ReactionModel;
@@ -60,6 +58,8 @@ public:
ShowSectionRole, /**< Whether the section header should be shown. */
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
ExcessReadMarkersRole, /**< The number of other users at the event after the first 5. */
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
ReactionRole, /**< List model for this event. */
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
@@ -116,8 +116,6 @@ private:
bool movingEvent = false;
KFormat m_format;
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
[[nodiscard]] int timelineBaseIndex() const;
@@ -132,7 +130,7 @@ private:
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
void moveReadMarker(const QString &toEventId);
void createEventObjects(const Quotient::RoomEvent *event);
void createEventObjects(const Quotient::RoomMessageEvent *event);
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
bool m_initialized = false;

View File

@@ -4,13 +4,11 @@
#include "messagefiltermodel.h"
#include <KLocalizedString>
#include <QVariant>
#include "enums/delegatetype.h"
#include "messagecontentmodel.h"
#include "messageeventmodel.h"
#include "neochatconfig.h"
#include "neochatroommember.h"
#include "timelinemodel.h"
using namespace Quotient;

View File

@@ -99,7 +99,7 @@ void PushRuleModel::setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind
for (const auto &rule : rules) {
QString roomId;
if (rule.conditions.size() > 0) {
for (const auto &condition : std::as_const(rule.conditions)) {
for (const auto &condition : rule.conditions) {
if (condition.key == QStringLiteral("room_id")) {
roomId = condition.pattern;
}
@@ -163,7 +163,7 @@ PushRuleSection::Section PushRuleModel::getSection(Quotient::PushRule rule)
}
// If the rule has push conditions and one is a room ID it is a room only keyword.
if (!rule.conditions.isEmpty()) {
for (const auto &condition : std::as_const(rule.conditions)) {
for (auto condition : rule.conditions) {
if (condition.key == QStringLiteral("room_id")) {
return PushRuleSection::RoomKeywords;
}

View File

@@ -1,136 +0,0 @@
// 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 "readmarkermodel.h"
#include <KLocalizedString>
#include <Quotient/roommember.h>
#define MAXMARKERS 5
ReadMarkerModel::ReadMarkerModel(const QString &eventId, NeoChatRoom *room)
: QAbstractListModel(nullptr)
, m_room(room)
, m_eventId(eventId)
{
Q_ASSERT(!m_eventId.isEmpty());
Q_ASSERT(m_room != nullptr);
connect(m_room, &NeoChatRoom::changed, this, [this](Quotient::Room::Changes changes) {
if (m_room != nullptr && changes.testFlag(Quotient::Room::Change::Other)) {
auto memberIds = m_room->userIdsAtEvent(m_eventId).values();
if (memberIds == m_markerIds) {
return;
}
beginResetModel();
m_markerIds.clear();
endResetModel();
beginResetModel();
memberIds.removeAll(m_room->localMember().id());
m_markerIds = memberIds;
endResetModel();
Q_EMIT reactionUpdated();
}
});
connect(m_room, &NeoChatRoom::memberNameUpdated, this, [this](Quotient::RoomMember member) {
if (m_markerIds.contains(member.id())) {
const auto memberIndex = index(m_markerIds.indexOf(member.id()));
Q_EMIT dataChanged(memberIndex, memberIndex);
}
});
connect(m_room, &NeoChatRoom::memberAvatarUpdated, this, [this](Quotient::RoomMember member) {
if (m_markerIds.contains(member.id())) {
const auto memberIndex = index(m_markerIds.indexOf(member.id()));
Q_EMIT dataChanged(memberIndex, memberIndex);
}
});
beginResetModel();
auto userIds = m_room->userIdsAtEvent(m_eventId);
userIds.remove(m_room->localMember().id());
m_markerIds = userIds.values();
endResetModel();
Q_EMIT reactionUpdated();
}
QVariant ReadMarkerModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
if (index.row() >= rowCount()) {
qDebug() << "ReactionModel, something's wrong: index.row() >= rowCount()";
return {};
}
const auto member = m_room->member(m_markerIds.value(index.row()));
if (role == DisplayNameRole) {
return member.htmlSafeDisplayName();
}
if (role == AvatarUrlRole) {
return member.avatarUrl();
}
if (role == ColorRole) {
return member.color();
}
return {};
}
int ReadMarkerModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return std::min(int(m_markerIds.size()), MAXMARKERS);
}
QHash<int, QByteArray> ReadMarkerModel::roleNames() const
{
return {
{DisplayNameRole, "displayName"},
{AvatarUrlRole, "avatarUrl"},
{ColorRole, "memberColor"},
};
}
QString ReadMarkerModel::readMarkersString()
{
/**
* The string ends up in the form
* "x users: user1DisplayName, user2DisplayName, etc."
*/
QString readMarkersString = i18np("1 user: ", "%1 users: ", m_markerIds.size());
for (const auto &memberId : m_markerIds) {
auto member = m_room->member(memberId);
QString displayName = member.htmlSafeDisambiguatedName();
if (displayName.isEmpty()) {
displayName = i18nc("A member who is not in the room has been requested.", "unknown member");
}
readMarkersString += displayName + i18nc("list separator", ", ");
}
readMarkersString.chop(2);
return readMarkersString;
}
QString ReadMarkerModel::excessReadMarkersString()
{
if (m_room == nullptr) {
return {};
}
if (m_markerIds.size() > MAXMARKERS) {
return QStringLiteral("+ ") + QString::number(m_markerIds.size() - MAXMARKERS);
} else {
return QString();
}
}
#include "moc_readmarkermodel.cpp"

View File

@@ -1,79 +0,0 @@
// 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 <QAbstractListModel>
#include <qtmetamacros.h>
#include "neochatroom.h"
/**
* @class ReadMarkerModel
*
* This class defines the model for visualising a list of reactions to an event.
*/
class ReadMarkerModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
/**
* @brief Returns a string with the names of the read markers at the event.
*
* This is in the form "x users: name 1, name 2, ...".
*/
Q_PROPERTY(QString readMarkersString READ readMarkersString NOTIFY reactionUpdated)
/**
* @brief Returns the number of excess user read markers for the event.
*
* This returns a string in the form "+ x" ready for use in the UI.
*/
Q_PROPERTY(QString excessReadMarkersString READ excessReadMarkersString NOTIFY reactionUpdated)
public:
/**
* @brief Defines the model roles.
*/
enum Roles {
DisplayNameRole = Qt::DisplayRole, /**< The display name of the member in the room. */
AvatarUrlRole, /**< The avatar for the member in the room. */
ColorRole, /**< The color for the member. */
};
explicit ReadMarkerModel(const QString &eventId, NeoChatRoom *room);
QString readMarkersString();
QString excessReadMarkersString();
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a mapping from Role enum values to role names.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void reactionUpdated();
private:
QPointer<NeoChatRoom> m_room;
QString m_eventId;
QList<QString> m_markerIds;
};

View File

@@ -7,7 +7,6 @@
#include "eventhandler.h"
#include "models/messagecontentmodel.h"
#include "neochatroom.h"
#include "neochatroommember.h"
#include <QGuiApplication>
@@ -67,17 +66,7 @@ void SearchModel::search()
m_job = job;
connect(job, &BaseJob::finished, this, [this, job] {
beginResetModel();
m_memberObjects.clear();
m_result = job->searchCategories().roomEvents;
if (m_result.has_value()) {
for (const auto &result : m_result.value().results) {
if (!m_memberObjects.contains(result.result->senderId())) {
m_memberObjects[result.result->senderId()] = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, result.result->senderId()));
}
}
}
endResetModel();
setSearching(false);
m_job = nullptr;
@@ -94,7 +83,7 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
switch (role) {
case AuthorRole:
return QVariant::fromValue<NeochatRoomMember *>(m_memberObjects.at(event.senderId()).get());
return QVariant::fromValue(eventHandler.getAuthor());
case ShowSectionRole:
if (row == 0) {
return true;

View File

@@ -9,8 +9,6 @@
#include <Quotient/csapi/search.h>
#include "neochatroommember.h"
namespace Quotient
{
class Connection;
@@ -125,6 +123,4 @@ private:
std::optional<Quotient::SearchJob::ResultRoomEvents> m_result = std::nullopt;
Quotient::SearchJob *m_job = nullptr;
bool m_searching = false;
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
};

View File

@@ -33,7 +33,6 @@ void UserListModel::setRoom(NeoChatRoom *room)
m_currentRoom->disconnect(this);
m_currentRoom->connection()->disconnect(this);
m_currentRoom = nullptr;
m_members.clear();
endResetModel();
}
@@ -57,7 +56,7 @@ void UserListModel::setRoom(NeoChatRoom *room)
});
}
m_active = false;
refreshAllMembers();
Q_EMIT roomChanged();
}
@@ -170,6 +169,7 @@ void UserListModel::refreshMember(const Quotient::RoomMember &member, const QLis
void UserListModel::refreshAllMembers()
{
beginResetModel();
m_members.clear();
if (m_currentRoom != nullptr) {
m_members = m_currentRoom->joinedMemberIds();
@@ -179,17 +179,8 @@ void UserListModel::refreshAllMembers()
MemberSorter sorter(m_currentRoom);
#endif
std::sort(m_members.begin(), m_members.end(), [&sorter, this](const auto &left, const auto &right) {
const auto leftPl = m_currentRoom->getUserPowerLevel(left);
const auto rightPl = m_currentRoom->getUserPowerLevel(right);
if (leftPl > rightPl) {
return true;
} else if (rightPl > leftPl) {
return false;
}
return sorter(m_currentRoom->member(left), m_currentRoom->member(right));
});
}
endResetModel();
Q_EMIT usersRefreshed();
@@ -225,14 +216,4 @@ QHash<int, QByteArray> UserListModel::roleNames() const
return roles;
}
void UserListModel::activate()
{
if (m_active) {
return;
}
m_active = true;
refreshAllMembers();
}
#include "moc_userlistmodel.cpp"

View File

@@ -77,8 +77,6 @@ public:
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
void activate();
Q_SIGNALS:
void roomChanged();
void usersRefreshed();
@@ -96,8 +94,6 @@ private:
QPointer<NeoChatRoom> m_currentRoom;
QList<QString> m_members;
bool m_active = false;
int findUserPos(const Quotient::RoomMember &member) const;
[[nodiscard]] int findUserPos(const QString &username) const;
};

View File

@@ -16,7 +16,6 @@ Name[eu]=NeoChat
Name[fi]=NeoChat
Name[fr]=NeoChat
Name[gl]=NeoChat
Name[he]=NeoChat
Name[hu]=NeoChat
Name[ia]=Neochat
Name[id]=NeoChat
@@ -59,7 +58,6 @@ 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[he]=לקוח ל־matrix, פרוטוקול התקשורת המבוזר
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
@@ -104,7 +102,6 @@ Name[eu]=Mezu berria
Name[fi]=Uusi viesti
Name[fr]=Nouveau message
Name[gl]=Nova mensaxe
Name[he]=הודעה חדשה
Name[hu]=Új üzenet
Name[ia]=Nove message
Name[id]=Pesan baru
@@ -145,7 +142,6 @@ 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[he]=יש הודעה חדשה
Comment[hu]=Új üzenet érkezett
Comment[ia]=Il ha un nove message
Comment[id]=Ada pesan baru
@@ -190,7 +186,6 @@ Name[eu]=Gonbidapen berria
Name[fi]=Uusi kutsu
Name[fr]=Nouvelle invitation
Name[gl]=Novo convite
Name[he]=הזמנה חדשה
Name[hu]=Új meghívó
Name[ia]=Nove invitation
Name[id]=Undangan Baru
@@ -230,7 +225,6 @@ 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[he]=יש הזמנה חדשה לחדר
Comment[hu]=Új meghívó érkezett egy szobába
Comment[ia]=Il ha un nove invitation a un sala
Comment[id]=Ada undangan baru ke sebuah ruangan
@@ -261,7 +255,7 @@ Action=Popup
Name=Share
Name[ar]=شارك
Name[ca]=Compartició
Name[ca@valencia]=Compartiu
Name[ca@valencia]=Compartició
Name[cs]=Sdílet
Name[en_GB]=Share
Name[eo]=Kundividi
@@ -269,7 +263,6 @@ Name[es]=Compartir
Name[eu]=Partekatu
Name[fi]=Jaa
Name[fr]=Partager
Name[he]=שיתוף
Name[hu]=Megosztás
Name[ia]=Comparti
Name[it]=Condivisione
@@ -278,7 +271,6 @@ Name[lv]=Kopīgot
Name[nl]=Gedeelde
Name[nn]=Del
Name[pl]=Udostępnij
Name[pt_BR]=Compartilhar
Name[sl]=Deli
Name[sv]=Dela
Name[ta]=பகிர்
@@ -296,7 +288,6 @@ Comment[es]=El resultado de compartir una parte de contenido
Comment[eu]=Eduki pieza bat partekatzearen emaitza
Comment[fi]=Tulos yhden sisältöosasen jakamisesta
Comment[fr]=Le résultat du partage d'une partie de contenu.
Comment[he]=תוצאת שיתוף פיסת תוכן
Comment[hu]=Tartalom megosztásának eredménye
Comment[ia]=Le exito de compartir un pecietta de contento
Comment[it]=Il risultato della condivisione di un contenuto
@@ -305,7 +296,6 @@ Comment[lv]=Satura kopīgošanas rezultāts
Comment[nl]=Het resultaat van het delen van een stukje inhoud
Comment[nn]=Resultatet av deling av innhald
Comment[pl]=Wynik udostępniania kawałka treści
Comment[pt_BR]=O resultado de compartilhar um conteúdo
Comment[sl]=Rezultat deljenega kosa vsebine
Comment[sv]=Resultatet av att dela innehåll
Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு

View File

@@ -187,11 +187,5 @@
<default>false</default>
</entry>
</group>
<group name="Security">
<entry name="RejectUnknownInvites" type="bool">
<label>Reject unknown invites</label>
<default>false</default>
</entry>
</group>
</kcfg>

View File

@@ -25,7 +25,6 @@
#include <Quotient/csapi/content-repo.h>
#include <Quotient/csapi/profile.h>
#include <Quotient/csapi/versions.h>
#include <Quotient/database.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
@@ -133,21 +132,6 @@ void NeoChatConnection::connectSignals()
Q_EMIT homeNotificationsChanged();
Q_EMIT homeHaveHighlightNotificationsChanged();
});
// Fetch unstable features
// TODO: Expose unstableFeatures() in libQuotient
connect(
this,
&Connection::connected,
this,
[this] {
auto job = callApi<GetVersionsJob>(BackgroundRequest);
connect(job, &GetVersionsJob::success, this, [this, job] {
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_ls);
Q_EMIT canCheckMutualRoomsChanged();
});
},
Qt::SingleShotConnection);
}
int NeoChatConnection::badgeNotificationCount() const
@@ -216,11 +200,6 @@ QVariantList NeoChatConnection::getSupportedRoomVersions() const
return supportedRoomVersions;
}
bool NeoChatConnection::canCheckMutualRooms() const
{
return m_canCheckMutualRooms;
}
void NeoChatConnection::changePassword(const QString &currentPassword, const QString &newPassword)
{
auto job = callApi<NeochatChangePasswordJob>(newPassword, false);

View File

@@ -79,11 +79,6 @@ class NeoChatConnection : public Quotient::Connection
*/
Q_PROPERTY(bool isOnline READ isOnline WRITE setIsOnline NOTIFY isOnlineChanged)
/**
* @brief Whether the server supports querying a user's mutual rooms.
*/
Q_PROPERTY(bool canCheckMutualRooms READ canCheckMutualRooms NOTIFY canCheckMutualRoomsChanged)
public:
/**
* @brief Defines the status after an attempt to change the password on an account.
@@ -100,7 +95,6 @@ public:
Q_INVOKABLE void logout(bool serverSideLogout);
Q_INVOKABLE QVariantList getSupportedRoomVersions() const;
bool canCheckMutualRooms() const;
/**
* @brief Change the password for an account.
@@ -202,7 +196,6 @@ Q_SIGNALS:
void passwordStatus(NeoChatConnection::PasswordStatus status);
void userConsentRequired(QUrl url);
void badgeNotificationCountChanged(NeoChatConnection *connection, int count);
void canCheckMutualRoomsChanged();
private:
bool m_isOnline = true;
@@ -215,6 +208,4 @@ private:
int m_badgeNotificationCount = 0;
QHash<QUrl, LinkPreviewer *> m_linkPreviewers;
bool m_canCheckMutualRooms = false;
};

View File

@@ -41,7 +41,6 @@
#include "events/joinrulesevent.h"
#include "events/pollevent.h"
#include "filetransferpseudojob.h"
#include "jobs/neochatgetcommonroomsjob.h"
#include "neochatconfig.h"
#include "notificationsmanager.h"
#include "roomlastmessageprovider.h"
@@ -70,26 +69,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
setFileUploadingProgress(0);
setHasFileUploading(false);
});
connect(this, &Room::fileTransferCompleted, this, [this](QString eventId) {
const auto evtIt = findInTimeline(eventId);
if (evtIt != messageEvents().rend()) {
const auto m_event = evtIt->viewAs<RoomEvent>();
QString mxcUrl;
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
if (event->hasFileContent()) {
mxcUrl = event->content()->fileInfo()->url().toString();
}
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
mxcUrl = event->image().fileInfo()->url().toString();
}
if (mxcUrl.isEmpty()) {
return;
}
auto localPath = this->fileTransferInfo(eventId).localPath.toLocalFile();
auto config = KSharedConfig::openStateConfig(QStringLiteral("neochatdownloads"))->group(QStringLiteral("downloads"));
config.writePathEntry(mxcUrl.mid(6), localPath);
}
});
connect(this, &Room::addedMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
connect(this, &Room::aboutToAddHistoricalMessages, this, &NeoChatRoom::cleanupExtraEventRange);
@@ -130,38 +109,14 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
return;
}
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localMember().id());
auto showNotification = [this, roomMemberEvent] {
QImage avatar_image;
if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {});
} else {
qWarning() << "using this room's avatar";
avatar_image = avatar(128);
}
NotificationsManager::instance().postInviteNotification(this,
displayName(),
member(roomMemberEvent->senderId()).htmlSafeDisplayName(),
avatar_image);
};
if (NeoChatConfig::rejectUnknownInvites()) {
auto job = this->connection()->callApi<NeochatGetCommonRoomsJob>(roomMemberEvent->senderId());
connect(job, &BaseJob::result, this, [this, job, roomMemberEvent, showNotification] {
QJsonObject replyData = job->jsonData();
if (replyData.contains(QStringLiteral("joined"))) {
const bool inAnyOfOurRooms = !replyData[QStringLiteral("joined")].toArray().isEmpty();
if (inAnyOfOurRooms) {
showNotification();
} else {
leaveRoom();
}
}
});
QImage avatar_image;
if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {});
} else {
showNotification();
qWarning() << "using this room's avatar";
avatar_image = avatar(128);
}
NotificationsManager::instance().postInviteNotification(this, displayName(), member(roomMemberEvent->senderId()).htmlSafeDisplayName(), avatar_image);
},
Qt::SingleShotConnection);
connect(this, &Room::changed, this, [this] {
@@ -891,18 +846,11 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
int NeoChatRoom::getUserPowerLevel(const QString &userId) const
{
if (!successorId().isEmpty()) {
return 0; // No one can upgrade a room that's already upgraded
auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
if (!powerLevelEvent) {
return 0;
}
const auto &mId = userId.isEmpty() ? connection()->userId() : userId;
if (const auto *plEvent = currentState().get<RoomPowerLevelsEvent>()) {
return plEvent->powerLevelForUser(mId);
}
if (const auto *createEvent = creation()) {
return createEvent->senderId() == mId ? 100 : 0;
}
return 0; // That's rather weird but may happen, according to rvdh
return powerLevelEvent->powerLevelForUser(userId);
}
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
@@ -1338,6 +1286,7 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
m_currentPushNotificationState = state;
Q_EMIT pushNotificationStateChanged(m_currentPushNotificationState);
}
void NeoChatRoom::updatePushNotificationState(QString type)
@@ -1421,7 +1370,7 @@ void NeoChatRoom::openEventMediaExternally(const QString &eventId)
if (evtIt != messageEvents().rend() && is<RoomMessageEvent>(**evtIt)) {
const auto event = evtIt->viewAs<RoomMessageEvent>();
if (event->hasFileContent()) {
const auto transferInfo = cachedFileTransferInfo(event);
const auto transferInfo = fileTransferInfo(eventId);
if (transferInfo.completed()) {
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
@@ -1429,20 +1378,15 @@ void NeoChatRoom::openEventMediaExternally(const QString &eventId)
downloadFile(eventId,
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
+ event->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
connect(
this,
&Room::fileTransferCompleted,
this,
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
}
},
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
connect(this, &Room::fileTransferCompleted, this, [this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
UrlHelper helper;
helper.openUrl(transferInfo.localPath);
}
});
}
}
}
@@ -1462,66 +1406,20 @@ void NeoChatRoom::copyEventMedia(const QString &eventId)
downloadFile(eventId,
QUrl(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + u'/'
+ event->id().replace(u':', u'_').replace(u'/', u'_').replace(u'+', u'_') + fileNameToDownload(eventId)));
connect(
this,
&Room::fileTransferCompleted,
this,
[this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
Clipboard clipboard;
clipboard.setImage(transferInfo.localPath);
}
},
static_cast<Qt::ConnectionType>(Qt::SingleShotConnection));
connect(this, &Room::fileTransferCompleted, this, [this, eventId](QString id, QUrl localFile, FileSourceInfo fileMetadata) {
Q_UNUSED(localFile);
Q_UNUSED(fileMetadata);
if (id == eventId) {
auto transferInfo = fileTransferInfo(eventId);
Clipboard clipboard;
clipboard.setImage(transferInfo.localPath);
}
});
}
}
}
}
FileTransferInfo NeoChatRoom::cachedFileTransferInfo(const Quotient::RoomEvent *event) const
{
QString mxcUrl;
int total = 0;
if (auto evt = eventCast<const Quotient::RoomMessageEvent>(event)) {
if (evt->hasFileContent()) {
mxcUrl = evt->content()->fileInfo()->url().toString();
total = evt->content()->fileInfo()->payloadSize;
}
} else if (auto evt = eventCast<const Quotient::StickerEvent>(event)) {
mxcUrl = evt->image().fileInfo()->url().toString();
total = evt->image().fileInfo()->payloadSize;
}
FileTransferInfo transferInfo = fileTransferInfo(event->id());
if (transferInfo.active()) {
return transferInfo;
}
auto config = KSharedConfig::openStateConfig(QStringLiteral("neochatdownloads"))->group(QStringLiteral("downloads"));
if (!config.hasKey(mxcUrl.mid(6))) {
return transferInfo;
}
const auto path = config.readPathEntry(mxcUrl.mid(6), QString());
QFileInfo info(path);
if (!info.isFile()) {
config.deleteEntry(mxcUrl);
return transferInfo;
}
// TODO: we could check the hash here
return FileTransferInfo{
.status = FileTransferInfo::Completed,
.isUpload = false,
.progress = total,
.total = total,
.localDir = QUrl(info.dir().path()),
.localPath = QUrl::fromLocalFile(path),
};
}
ChatBarCache *NeoChatRoom::mainCache() const
{
return m_mainCache;
@@ -1767,11 +1665,6 @@ const RoomEvent *NeoChatRoom::getEvent(const QString &eventId) const
return timelineIt->get();
}
const auto pendingIt = findPendingEvent(eventId);
if (pendingIt != pendingEvents().end()) {
return pendingIt->event();
}
auto extraIt = std::find_if(m_extraEvents.begin(), m_extraEvents.end(), [eventId](const Quotient::event_ptr_tt<Quotient::RoomEvent> &event) {
return event->id() == eventId;
});

View File

@@ -579,14 +579,6 @@ public:
*/
Q_INVOKABLE QString invitingUserId() const;
/**
* @brief Return the cached file transfer information for the event.
*
* If we downloaded the file previously, return a struct with Completed status
* and the local file path stored in KSharedCOnfig
*/
Quotient::FileTransferInfo cachedFileTransferInfo(const Quotient::RoomEvent *event) const;
private:
QSet<const Quotient::RoomEvent *> highlights;

View File

@@ -1,166 +0,0 @@
// 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 "neochatroommember.h"
#include "neochatroom.h"
NeochatRoomMember::NeochatRoomMember(NeoChatRoom *room, const QString &memberId)
: m_room(room)
, m_memberId(memberId)
{
Q_ASSERT(!m_memberId.isEmpty());
if (m_room != nullptr) {
connect(m_room, &NeoChatRoom::memberNameUpdated, this, [this](Quotient::RoomMember member) {
if (member.id() == m_memberId) {
Q_EMIT displayNameUpdated();
}
});
connect(m_room, &NeoChatRoom::memberAvatarUpdated, this, [this](Quotient::RoomMember member) {
if (member.id() == m_memberId) {
Q_EMIT avatarUpdated();
}
});
}
}
QString NeochatRoomMember::id() const
{
return m_memberId;
}
Quotient::Uri NeochatRoomMember::uri() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return {};
}
return m_room->member(m_memberId).uri();
}
bool NeochatRoomMember::isLocalMember() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return false;
}
return m_room->member(m_memberId).isLocalMember();
}
Quotient::Membership NeochatRoomMember::membershipState() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return Quotient::Membership::Leave;
}
return m_room->member(m_memberId).membershipState();
}
QString NeochatRoomMember::name() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return id();
}
return m_room->member(m_memberId).name();
}
QString NeochatRoomMember::displayName() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return id();
}
return m_room->member(m_memberId).displayName();
}
QString NeochatRoomMember::htmlSafeDisplayName() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return id();
}
return m_room->member(m_memberId).htmlSafeDisplayName();
}
QString NeochatRoomMember::fullName() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return id();
}
return m_room->member(m_memberId).fullName();
}
QString NeochatRoomMember::htmlSafeFullName() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return id();
}
return m_room->member(m_memberId).htmlSafeFullName();
}
QString NeochatRoomMember::disambiguatedName() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return id();
}
return m_room->member(m_memberId).disambiguatedName();
}
QString NeochatRoomMember::htmlSafeDisambiguatedName() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return id();
}
return m_room->member(m_memberId).htmlSafeDisambiguatedName();
}
int NeochatRoomMember::hue() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return 0;
}
return m_room->member(m_memberId).hue();
}
qreal NeochatRoomMember::hueF() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return 0.0;
}
return m_room->member(m_memberId).hueF();
}
QColor NeochatRoomMember::color() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return {};
}
return m_room->member(m_memberId).color();
}
QString NeochatRoomMember::avatarMediaId() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return {};
}
return m_room->member(m_memberId).avatarMediaId();
}
QUrl NeochatRoomMember::avatarUrl() const
{
if (m_room == nullptr || m_memberId.isEmpty()) {
return {};
}
return m_room->member(m_memberId).avatarUrl();
}

View File

@@ -1,83 +0,0 @@
// 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 <QObject>
#include <QPointer>
#include <qqmlintegration.h>
#include <Quotient/roommember.h>
#include <Quotient/uri.h>
class NeoChatRoom;
/**
* @class NeochatRoomMember
*
* This class is a shim around RoomMember that can be safety passed to QML.
*
* Because RoomMember has an internal pointer to a RoomMemberEvent it is
* designed to be created used then quickly discarded as the stste event is changed
* every time the member updates. Passing these to QML which will hold onto them
* can lead to accessing an already deleted Quotient::RoomMemberEvent relatively easily.
*
* This class instead holds a member ID and can therefore always safely create and
* access Quotient::RoomMember objects while being used as long as needed by QML.
*
* @note This is only needed to pass to QML if only accessing in CPP RoomMmeber can
* be used safely.
*
* @note The interface is the same as Quotient::RoomMember.
*
* @sa Quotient::RoomMember
*/
class NeochatRoomMember : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Q_PROPERTY(QString id READ id CONSTANT)
Q_PROPERTY(Quotient::Uri uri READ uri CONSTANT)
Q_PROPERTY(bool isLocalMember READ isLocalMember CONSTANT)
Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameUpdated)
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameUpdated)
Q_PROPERTY(QString fullName READ fullName NOTIFY displayNameUpdated)
Q_PROPERTY(QString htmlSafeFullName READ htmlSafeFullName NOTIFY displayNameUpdated)
Q_PROPERTY(QString disambiguatedName READ disambiguatedName NOTIFY displayNameUpdated)
Q_PROPERTY(QString htmlSafeDisambiguatedName READ htmlSafeDisambiguatedName NOTIFY displayNameUpdated)
Q_PROPERTY(int hue READ hue CONSTANT)
Q_PROPERTY(qreal hueF READ hueF CONSTANT)
Q_PROPERTY(QColor color READ color CONSTANT)
Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarUpdated)
public:
NeochatRoomMember() = default;
explicit NeochatRoomMember(NeoChatRoom *room, const QString &memberId);
QString id() const;
Quotient::Uri uri() const;
bool isLocalMember() const;
Quotient::Membership membershipState() const;
QString name() const;
QString displayName() const;
QString htmlSafeDisplayName() const;
QString fullName() const;
QString htmlSafeFullName() const;
QString disambiguatedName() const;
QString htmlSafeDisambiguatedName() const;
int hue() const;
qreal hueF() const;
QColor color() const;
QString avatarMediaId() const;
QUrl avatarUrl() const;
Q_SIGNALS:
void displayNameUpdated();
void avatarUpdated();
private:
QPointer<NeoChatRoom> m_room;
const QString m_memberId = QString();
};

View File

@@ -110,12 +110,6 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
}
auto sender = room->member(notification["event"_ls]["sender"_ls].toString());
// Don't display notifications for events in invited rooms
// This should prevent empty notifications from appearing when they shouldn't
if (room->joinState() == JoinState::Invite) {
continue;
}
QString body;
if (notification["event"_ls]["type"_ls].toString() == "org.matrix.msc3381.poll.start"_ls) {
body = notification["event"_ls]["content"_ls]["org.matrix.msc3381.poll.start"_ls]["question"_ls]["body"_ls].toString();
@@ -249,6 +243,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS
notification->setText(i18n("%1 invited you to a room", sender));
notification->setTitle(title);
notification->setPixmap(createNotificationImage(icon, nullptr));
notification->setFlags(KNotification::Persistent);
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
if (!room) {

View File

@@ -17,7 +17,6 @@ Name[eu]=NeoChat
Name[fi]=NeoChat
Name[fr]=NeoChat
Name[gl]=NeoChat
Name[he]=NeoChat
Name[hu]=NeoChat
Name[ia]=Neochat
Name[id]=NeoChat
@@ -58,7 +57,6 @@ 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[he]=איתור חדרים ב־NeoChat
Comment[hu]=Szobák keresése a NeoChatben
Comment[ia]=Trova salas in NeoChat
Comment[id]=Cari ruangan di NeoChat

View File

@@ -28,7 +28,6 @@
"Description[nl]": "Delen via NeoChat",
"Description[nn]": "Del via NeoChat",
"Description[pl]": "Udostępnij przez NeoChat",
"Description[pt_BR]": "Compartilhar via NeoChat",
"Description[ru]": "Опубликовать в NeoChat",
"Description[sl]": "Deli prek NeoChat",
"Description[sv]": "Dela via NeoChat",

84
src/qml/AccountDialog.qml Normal file
View File

@@ -0,0 +1,84 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.prison
import org.kde.neochat
Kirigami.Dialog {
id: root
property NeoChatConnection connection
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
contentItem: ColumnLayout {
spacing: 0
KirigamiComponents.Avatar {
id: avatar
readonly property string mediaId: root.connection.localUser.avatarMediaId
Layout.preferredWidth: Kirigami.Units.iconSizes.huge * 2
Layout.preferredHeight: Kirigami.Units.iconSizes.huge * 2
Layout.alignment: Qt.AlignHCenter
name: root.room ? root.room.member(root.user.id).displayName : root.user.displayName
source: mediaId ? root.connection.makeMediaUrl("mxc://" + mediaId) : ""
color: root.room ? root.room.member(root.user.id).color : QmlUtils.getUserColor(root.user.hueF)
}
QQC2.Label {
Layout.fillWidth: true
font.bold: false
font.weight: Font.DemiBold
font.pixelSize: 32
elide: Text.ElideRight
wrapMode: Text.NoWrap
text: root.connection.localUser.displayName
textFormat: Text.PlainText
horizontalAlignment: Text.AlignHCenter
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
Layout.bottomMargin: Kirigami.Units.largeSpacing
font.pixelSize: 16
textFormat: TextEdit.PlainText
text: root.connection.localUser.id
horizontalAlignment: Text.AlignHCenter
}
FormCard.FormDelegateSeparator {}
FormCard.FormButtonDelegate {
text: i18n("Edit Account")
}
FormCard.FormButtonDelegate {
text: i18n("Switch Account")
}
FormCard.FormButtonDelegate {
text: i18n("Logout")
}
}
Component {
id: qrMaximizeComponent
QrCodeMaximizeComponent {}
}
}

View File

@@ -17,7 +17,7 @@ Components.AbstractMaximizeComponent {
/**
* @brief The message author.
*/
property NeochatRoomMember author
property RoomMember author
/**
* @brief The timestamp of the message.

View File

@@ -15,10 +15,7 @@ Kirigami.PlaceholderMessage {
required property NeoChatRoom currentRoom
text: i18n("Accept this invitation?")
explanation: root.currentRoom.connection.canCheckMutualRooms ? i18n("You can reject invitations from unknown users under Security settings.") : ""
RowLayout {
Layout.alignment: Qt.AlignHCenter
QQC2.Button {
Layout.alignment: Qt.AlignHCenter
text: i18nc("@action:button The thing being rejected is an invitation to chat", "Reject and ignore user")

View File

@@ -192,7 +192,20 @@ QQC2.ScrollView {
}
}
model: root.room.isDirectChat() ? 0 : RoomManager.userListModel
KSortFilterProxyModel {
id: sortedMessageEventModel
sourceModel: UserListModel {
room: root.room
}
sortRoleName: "powerLevel"
sortOrder: Qt.DescendingOrder
filterRoleName: "name"
filterCaseSensitivity: Qt.CaseInsensitive
}
model: root.room.isDirectChat() ? 0 : sortedMessageEventModel
clip: true
focus: true

View File

@@ -227,11 +227,6 @@ Kirigami.Page {
}
}
footer: Loader {
width: parent.width
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
}
MouseArea {
anchors.top: parent.top
anchors.bottom: parent.bottom

View File

@@ -47,6 +47,22 @@ QQC2.Control {
width: scrollView.width
spacing: 0
AvatarTabButton {
readonly property string mediaId: root.connection.localUser.avatarMediaId
Layout.fillWidth: true
Layout.preferredHeight: width - Kirigami.Units.smallSpacing
Layout.maximumHeight: width - Kirigami.Units.smallSpacing
Layout.topMargin: Kirigami.Units.smallSpacing / 2
Layout.bottomMargin: Kirigami.Units.smallSpacing / 2
text: root.connection.localUser.displayName
source: mediaId ? root.connection.makeMediaUrl("mxc://" + mediaId) : ""
onClicked: Qt.createComponent("org.kde.neochat", "AccountDialog").createObject(root, {
connection: root.connection
}).open()
}
AvatarTabButton {
id: notificationsButton

View File

@@ -108,7 +108,6 @@ QQC2.ScrollView {
onTriggered: {
root.roomChanging = false;
markReadIfVisibleTimer.reset();
RoomManager.activateUserModel();
}
}
onAtYEndChanged: if (!root.roomChanging) {
@@ -191,7 +190,7 @@ QQC2.ScrollView {
implicitHeight: Kirigami.Units.gridUnit * 2
z: 2
visible: (root.currentRoom?.hasUnreadMessages ?? false)
visible: root.currentRoom && root.currentRoom.hasUnreadMessages
text: root.currentRoom.readMarkerLoaded ? i18n("Jump to first unread message") : i18n("Jump to oldest loaded message")
action: Kirigami.Action {

View File

@@ -11,7 +11,6 @@
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "neochatroommember.h"
#include "spacehierarchycache.h"
#include "urlhelper.h"
@@ -40,7 +39,6 @@ RoomManager::RoomManager(QObject *parent)
, m_timelineModel(new TimelineModel(this))
, m_messageFilterModel(new MessageFilterModel(this, m_timelineModel))
, m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
, m_userListModel(new UserListModel(this))
{
m_lastRoomConfig = m_config->group(QStringLiteral("LastOpenRoom"));
m_lastSpaceConfig = m_config->group(QStringLiteral("LastOpenSpace"));
@@ -48,7 +46,6 @@ RoomManager::RoomManager(QObject *parent)
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_timelineModel->setRoom(m_currentRoom);
m_userListModel->setRoom(m_currentRoom);
});
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
@@ -116,16 +113,6 @@ MediaMessageFilterModel *RoomManager::mediaMessageFilterModel() const
return m_mediaMessageFilterModel;
}
UserListModel *RoomManager::userListModel() const
{
return m_userListModel;
}
void RoomManager::activateUserModel()
{
m_userListModel->activate();
}
UriResolveResult RoomManager::resolveResource(const Uri &uri)
{
return UriResolverBase::visitResource(m_connection, uri);
@@ -185,7 +172,7 @@ void RoomManager::maximizeMedia(int index)
Q_EMIT showMaximizedMedia(index);
}
void RoomManager::maximizeCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language)
void RoomManager::maximizeCode(const RoomMember &author, const QDateTime &time, const QString &codeText, const QString &language)
{
if (codeText.isEmpty()) {
return;
@@ -203,14 +190,14 @@ 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, const QString &selectedText)
{
const auto &event = **room->findInTimeline(eventId);
const auto eventHandler = EventHandler(room, &event);
if (eventHandler.getMediaInfo().contains("mimeType"_ls)) {
Q_EMIT showFileMenu(eventId,
sender,
eventHandler.getAuthor(),
eventHandler.messageComponentType(),
eventHandler.getPlainBody(),
eventHandler.getMediaInfo()["mimeType"_ls].toString(),
@@ -218,7 +205,12 @@ void RoomManager::viewEventMenu(const QString &eventId, NeoChatRoom *room, Neoch
return;
}
Q_EMIT showMessageMenu(eventId, sender, eventHandler.messageComponentType(), eventHandler.getPlainBody(), eventHandler.getRichBody(), selectedText);
Q_EMIT showMessageMenu(eventId,
eventHandler.getAuthor(),
eventHandler.messageComponentType(),
eventHandler.getPlainBody(),
eventHandler.getRichBody(),
selectedText);
}
bool RoomManager::hasOpenRoom() const

View File

@@ -22,8 +22,6 @@
#include "models/sortfilterroomtreemodel.h"
#include "models/sortfilterspacelistmodel.h"
#include "models/timelinemodel.h"
#include "models/userlistmodel.h"
#include "neochatroommember.h"
class NeoChatRoom;
class NeoChatConnection;
@@ -122,14 +120,6 @@ class RoomManager : public QObject, public UriResolverBase
*/
Q_PROPERTY(MediaMessageFilterModel *mediaMessageFilterModel READ mediaMessageFilterModel CONSTANT)
/**
* @brief The UserListModel that should be used for room member visualisation.
*
* @note Available here so that the room page and drawer both have access to the
* same model.
*/
Q_PROPERTY(UserListModel *userListModel READ userListModel CONSTANT)
/**
* @brief Whether a room is currently open in NeoChat.
*
@@ -165,9 +155,6 @@ public:
MessageFilterModel *messageFilterModel() const;
MediaMessageFilterModel *mediaMessageFilterModel() const;
UserListModel *userListModel() const;
Q_INVOKABLE void activateUserModel();
/**
* @brief Resolve the given URI resource.
*
@@ -217,7 +204,7 @@ public:
*/
Q_INVOKABLE void maximizeMedia(int index);
Q_INVOKABLE void maximizeCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language);
Q_INVOKABLE void maximizeCode(const RoomMember &author, const QDateTime &time, const QString &codeText, const QString &language);
/**
* @brief Request that any full screen overlay currently open closes.
@@ -232,7 +219,7 @@ 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, const QString &selectedText = {});
ChatDocumentHandler *chatDocumentHandler() const;
void setChatDocumentHandler(ChatDocumentHandler *handler);
@@ -288,7 +275,7 @@ Q_SIGNALS:
/**
* @brief Request a block of code is shown maximized.
*/
void showMaximizedCode(NeochatRoomMember *author, const QDateTime &time, const QString &codeText, const QString &language);
void showMaximizedCode(const RoomMember &author, const QDateTime &time, const QString &codeText, const QString &language);
/**
* @brief Request that any full screen overlay closes.
@@ -304,7 +291,7 @@ Q_SIGNALS:
* @brief Request to show a menu for the given event.
*/
void showMessageMenu(const QString &eventId,
const NeochatRoomMember *author,
const Quotient::RoomMember &author,
MessageComponentType::Type messageComponentType,
const QString &plainText,
const QString &htmlText,
@@ -314,7 +301,7 @@ Q_SIGNALS:
* @brief Request to show a menu for the given media event.
*/
void showFileMenu(const QString &eventId,
const NeochatRoomMember *author,
const Quotient::RoomMember &author,
MessageComponentType::Type messageComponentType,
const QString &plainText,
const QString &mimeType,
@@ -372,9 +359,6 @@ private:
TimelineModel *m_timelineModel;
MessageFilterModel *m_messageFilterModel;
MediaMessageFilterModel *m_mediaMessageFilterModel;
UserListModel *m_userListModel;
QPointer<NeoChatConnection> m_connection;
void setCurrentRoom(const QString &roomId);

View File

@@ -41,7 +41,7 @@ FormCard.FormCard {
width: stickerFlow.width / 4
height: width
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(emoticonEditorPage, {
onClicked: pageStack.pushDialogLayer(emoticonEditorPage, {
description: model.body ?? "",
index: model.index,
url: model.url,
@@ -90,7 +90,7 @@ FormCard.FormCard {
width: stickerFlow.width / 4
height: width
onClicked: root.QQC2.ApplicationWindow.window.pageStack.pushDialogLayer(emoticonEditorPage, {
onClicked: pageStack.pushDialogLayer(emoticonEditorPage, {
description: "",
index: -1,
url: "",

View File

@@ -50,7 +50,7 @@ FormCard.FormCardPage {
QQC2.ToolButton {
text: i18nc("@action:button", "Unignore this user")
icon.name: "list-remove-symbolic"
onClicked: root.connection.removeFromIgnoredUsers(modelData)
onClicked: root.connection.removeFromIgnoredUsers(root.connection.user(modelData))
display: QQC2.Button.IconOnly
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered

View File

@@ -16,32 +16,6 @@ FormCard.FormCardPage {
title: i18nc("@title", "Security")
FormCard.FormHeader {
title: i18nc("@title:group", "Invitations")
}
FormCard.FormCard {
FormCard.FormCheckDelegate {
text: i18nc("@option:check", "Reject invitations from unknown users")
description: connection.canCheckMutualRooms ? i18n("If enabled, NeoChat will reject invitations from from users you don't share a room with.") : i18n("Your server does not support this setting.")
checked: Config.rejectUnknownInvites
enabled: !Config.isRejectUnknownInvitesImmutable && connection.canCheckMutualRooms
onToggled: {
Config.rejectUnknownInvites = checked;
Config.save();
}
}
}
FormCard.FormHeader {
title: i18nc("@title:group", "Ignored Users")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Manage ignored users")
onClicked: root.ApplicationWindow.window.pageStack.push(ignoredUsersDialogComponent, {}, {
title: i18nc("@title:window", "Ignored Users")
});
}
}
FormCard.FormHeader {
title: i18nc("@title", "Keys")
}
@@ -59,6 +33,17 @@ FormCard.FormCardPage {
description: i18n("Device id")
}
}
FormCard.FormHeader {
title: i18nc("@title:group", "Ignored Users")
}
FormCard.FormCard {
FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Manage ignored users")
onClicked: pageStack.pushDialogLayer(ignoredUsersDialogComponent, {}, {
title: i18nc("@title:window", "Ignored Users")
});
}
}
Component {
id: ignoredUsersDialogComponent

View File

@@ -20,6 +20,11 @@ FormCard.FormCardPage {
title: i18nc('@title:window', 'Permissions')
property UserListModel userListModel: UserListModel {
id: userListModel
room: root.room
}
readonly property PowerLevelModel powerLevelModel: PowerLevelModel {
showMute: false
}
@@ -34,7 +39,7 @@ FormCard.FormCardPage {
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: RoomManager.userListModel
sourceModel: userListModel
sortRoleName: "powerLevel"
sortOrder: Qt.DescendingOrder
filterRowCallback: function (source_row, source_parent) {
@@ -153,7 +158,7 @@ FormCard.FormCardPage {
model: UserFilterModel {
id: userListFilterModel
sourceModel: RoomManager.userListModel
sourceModel: userListModel
filterText: userListSearchField.text
onFilterTextChanged: {

View File

@@ -28,6 +28,11 @@ ColumnLayout {
*/
required property string eventId
/**
* @brief The display text of the message.
*/
required property string display
/**
* @brief The media info for the event.
*
@@ -39,7 +44,6 @@ ColumnLayout {
* - width - The width in pixels of the audio media.
* - height - The height in pixels of the audio media.
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
* - filename - original filename of the media
*/
required property var mediaInfo
@@ -134,7 +138,7 @@ ColumnLayout {
id: playButton
}
QQC2.Label {
text: root.mediaInfo.filename
text: root.display
wrapMode: Text.Wrap
Layout.fillWidth: true
}
@@ -155,7 +159,7 @@ ColumnLayout {
from: 0
to: audio.duration
value: audio.position
onMoved: audio.setPosition(value)
onMoved: audio.seek(value)
}
QQC2.Label {

View File

@@ -60,7 +60,7 @@ RowLayout {
horizontalAlignment: Text.AlignRight
color: Kirigami.Theme.disabledTextColor
QQC2.ToolTip.visible: timeHoverHandler.hovered
QQC2.ToolTip.text: root.time.toLocaleString(Qt.locale(), Locale.ShortFormat)
QQC2.ToolTip.text: root.time.toLocaleString(Qt.locale(), Locale.LongFormat)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler {

View File

@@ -13,21 +13,20 @@ Flow {
property var avatarSize: Kirigami.Units.iconSizes.small
property alias model: avatarFlowRepeater.model
property string toolTipText
property alias excessAvatars: excessAvatarsLabel.text
spacing: -avatarSize / 2
Repeater {
id: avatarFlowRepeater
delegate: KirigamiComponents.Avatar {
required property string displayName
required property url avatarUrl
required property color memberColor
required property var modelData
implicitWidth: root.avatarSize
implicitHeight: root.avatarSize
name: displayName
source: avatarUrl
color: memberColor
name: modelData.displayName
source: modelData.avatarUrl
color: modelData.color
}
}
QQC2.Label {
@@ -35,9 +34,6 @@ Flow {
visible: text !== ""
color: Kirigami.Theme.textColor
horizontalAlignment: Text.AlignHCenter
text: root.model?.excessReadMarkersString ?? ""
background: Kirigami.ShadowedRectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.inherit: false
@@ -58,7 +54,7 @@ Flow {
}
}
QQC2.ToolTip.text: root.model?.readMarkersString ?? ""
QQC2.ToolTip.text: toolTipText
QQC2.ToolTip.visible: hoverHandler.hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay

View File

@@ -20,7 +20,7 @@ QQC2.Control {
*
* @sa Quotient::RoomMember
*/
required property NeochatRoomMember author
required property RoomMember author
/**
* @brief The timestamp of the message.

View File

@@ -29,6 +29,11 @@ ColumnLayout {
*/
required property string eventId
/**
* @brief The display text of the message.
*/
required property string display
/**
* @brief The media info for the event.
*
@@ -40,7 +45,6 @@ ColumnLayout {
* - width - The width in pixels of the audio media.
* - height - The height in pixels of the audio media.
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
* - filename - original filename of the media
*/
required property var mediaInfo
@@ -151,7 +155,7 @@ ColumnLayout {
spacing: 0
QQC2.Label {
Layout.fillWidth: true
text: root.mediaInfo.filename
text: root.display
wrapMode: Text.Wrap
elide: Text.ElideRight
}

View File

@@ -26,7 +26,7 @@ TimelineDelegate {
/**
* @brief The message author.
*/
required property NeochatRoomMember author
required property RoomMember author
width: parent?.width
rightPadding: Config.compactLayout && root.ListView.view.width >= Kirigami.Units.gridUnit * 20 ? Kirigami.Units.gridUnit * 2 + Kirigami.Units.largeSpacing : Kirigami.Units.largeSpacing
@@ -88,7 +88,7 @@ TimelineDelegate {
QtObject {
id: _private
function showMessageMenu() {
RoomManager.viewEventMenu(root.eventId, root.room, root.author, "");
RoomManager.viewEventMenu(root.eventId, root.room, "");
}
}
}

View File

@@ -50,7 +50,7 @@ TimelineDelegate {
*
* @sa Quotient::RoomMember
*/
required property NeochatRoomMember author
required property var author
/**
* @brief The model to visualise the content of the message.
@@ -82,6 +82,16 @@ TimelineDelegate {
*/
required property var readMarkers
/**
* @brief String with the display name and matrix ID of the other user read markers.
*/
required property string readMarkersString
/**
* @brief The number of other users at the event after the first 5.
*/
required property var excessReadMarkers
/**
* @brief Whether the other user read marker component should be shown.
*/
@@ -235,7 +245,7 @@ TimelineDelegate {
topMargin: Kirigami.Units.smallSpacing
}
visible: (root.contentModel?.showAuthor ?? false) && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight)
visible: root.contentModel?.showAuthor && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight)
name: root.author.displayName
source: root.author.avatarUrl
color: root.author.color
@@ -332,6 +342,8 @@ TimelineDelegate {
Layout.rightMargin: Kirigami.Units.largeSpacing
visible: root.showReadMarkers
model: root.readMarkers
toolTipText: root.readMarkersString
excessAvatars: root.excessReadMarkers
}
DelegateSizeHelper {
@@ -365,7 +377,7 @@ TimelineDelegate {
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalMember && !Config.compactLayout && !root.alwaysFillWidth
function showMessageMenu() {
RoomManager.viewEventMenu(root.eventId, root.room, root.author, root.selectedText);
RoomManager.viewEventMenu(root.eventId, root.room, root.selectedText);
}
}
}

View File

@@ -5,7 +5,6 @@ import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.coreaddons
import org.kde.kirigami as Kirigami
/**
@@ -14,14 +13,13 @@ import org.kde.kirigami as Kirigami
RowLayout {
property alias mimeIconSource: icon.source
property alias label: nameLabel.text
property string subLabel: ""
property int size: 0
property int duration: 0
property alias subLabel: subLabel.text
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
id: icon
fallback: "unknown"
}
ColumnLayout {
@@ -34,21 +32,15 @@ RowLayout {
id: nameLabel
Layout.fillWidth: true
Layout.alignment: caption.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter
Layout.alignment: subLabel.visible ? Qt.AlignLeft | Qt.AlignBottom : Qt.AlignLeft | Qt.AlignVCenter
elide: Text.ElideRight
}
QQC2.Label {
id: caption
id: subLabel
Layout.fillWidth: true
text: (subLabel || size || duration || '') && [
subLabel,
size && Format.formatByteSize(size),
duration > 0 && Format.formatDuration(duration),
].filter(Boolean).join(" | ")
elide: Text.ElideRight
visible: text.length > 0
opacity: 0.7

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