Compare commits
65 Commits
work/nvrwh
...
work/tobia
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60ce292b58 | ||
|
|
ec906813fc | ||
|
|
36543d0872 | ||
|
|
fc6ea0b779 | ||
|
|
dae23ccd4b | ||
|
|
666cd3c81f | ||
|
|
dc5366e924 | ||
|
|
f21b3bb0e4 | ||
|
|
4ebcc36fb3 | ||
|
|
292cfb0d3d | ||
|
|
1b59917f16 | ||
|
|
fcf64a7e1b | ||
|
|
b598584aea | ||
|
|
0ebcacce69 | ||
|
|
33c38288a3 | ||
|
|
f00e3ded45 | ||
|
|
250483fbe7 | ||
|
|
755a060e12 | ||
|
|
6d3839dd42 | ||
|
|
15084459bb | ||
|
|
7150445f8e | ||
|
|
b02bdd22dd | ||
|
|
8755cd9d61 | ||
|
|
2c4c07dc81 | ||
|
|
645771c03d | ||
|
|
b2af69fd92 | ||
|
|
ca57732871 | ||
|
|
cd9e98f24b | ||
|
|
136da65381 | ||
|
|
b909cb2db8 | ||
|
|
3a4b531edf | ||
|
|
413453dd85 | ||
|
|
fe52d26f05 | ||
|
|
6029c0d0b3 | ||
|
|
12bfe9d6ca | ||
|
|
dd910f2856 | ||
|
|
b4e0d9e996 | ||
|
|
d3f691548c | ||
|
|
367131d64c | ||
|
|
2895b7bc21 | ||
|
|
f9cdd55a4d | ||
|
|
1854373dd6 | ||
|
|
00a621a5f7 | ||
|
|
f047bd797e | ||
|
|
777f8b4276 | ||
|
|
0992b7fc93 | ||
|
|
b7d6208869 | ||
|
|
da4ce27168 | ||
|
|
4746401bec | ||
|
|
79cf399bf5 | ||
|
|
563323f241 | ||
|
|
e2048dfa1c | ||
|
|
c986c63b84 | ||
|
|
0b3426a9b2 | ||
|
|
173e507ebf | ||
|
|
fb3b1490a9 | ||
|
|
48502480df | ||
|
|
efb3b8f38c | ||
|
|
f185b1773c | ||
|
|
95fff4c9f7 | ||
|
|
27662f9a4a | ||
|
|
f9f678a801 | ||
|
|
8e07e7553a | ||
|
|
bb566e3c7b | ||
|
|
35c68a6de1 |
@@ -12,7 +12,6 @@
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatroom.h"
|
||||
@@ -29,19 +28,14 @@ class EventHandlerTest : public QObject
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
EventHandler eventHandler;
|
||||
EventHandler emptyHandler;
|
||||
EventHandler noEventHandler;
|
||||
|
||||
EventHandler emptyHandler = EventHandler(nullptr, nullptr);
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void nullSetEvent();
|
||||
void eventId();
|
||||
void nullEventId();
|
||||
void delegateType_data();
|
||||
void delegateType();
|
||||
void nullDelegateType();
|
||||
void author();
|
||||
void nullAuthor();
|
||||
void authorDisplayName();
|
||||
@@ -69,8 +63,6 @@ private Q_SLOTS:
|
||||
void nullHasReply();
|
||||
void replyId();
|
||||
void nullReplyId();
|
||||
void replyDelegateType();
|
||||
void nullReplyDelegateType();
|
||||
void replyAuthor();
|
||||
void nullReplyAuthor();
|
||||
void replyBody();
|
||||
@@ -83,72 +75,32 @@ private Q_SLOTS:
|
||||
void nullLocation();
|
||||
void readMarkers();
|
||||
void nullReadMarkers();
|
||||
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
void EventHandlerTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
|
||||
|
||||
eventHandler.setRoom(room);
|
||||
noEventHandler.setRoom(room);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullSetEvent()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "cannot setEvent when m_room is set to nullptr.");
|
||||
emptyHandler.setEvent(room->messageEvents().at(0).get());
|
||||
}
|
||||
|
||||
void EventHandlerTest::eventId()
|
||||
{
|
||||
eventHandler.setEvent(room->messageEvents().at(0).get());
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandler.getId(), QStringLiteral("$153456789:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullEventId()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getId called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getId(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::delegateType_data()
|
||||
{
|
||||
QTest::addColumn<int>("eventNum");
|
||||
QTest::addColumn<DelegateType::Type>("delegateType");
|
||||
|
||||
QTest::newRow("message") << 0 << DelegateType::Message;
|
||||
QTest::newRow("state") << 1 << DelegateType::State;
|
||||
QTest::newRow("message 2") << 2 << DelegateType::Message;
|
||||
QTest::newRow("reaction") << 3 << DelegateType::Other;
|
||||
QTest::newRow("video") << 4 << DelegateType::Video;
|
||||
QTest::newRow("location") << 7 << DelegateType::Location;
|
||||
}
|
||||
|
||||
void EventHandlerTest::delegateType()
|
||||
{
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(DelegateType::Type, delegateType);
|
||||
|
||||
eventHandler.setEvent(room->messageEvents().at(eventNum).get());
|
||||
|
||||
QCOMPARE(eventHandler.getDelegateType(), delegateType);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullDelegateType()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getDelegateType called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getDelegateType(), DelegateType::Other);
|
||||
}
|
||||
|
||||
void EventHandlerTest::author()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
auto author = room->user(event->senderId());
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, event);
|
||||
|
||||
auto eventHandlerAuthor = eventHandler.getAuthor();
|
||||
|
||||
@@ -166,15 +118,14 @@ void EventHandlerTest::nullAuthor()
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getAuthor(), QVariantMap());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user.");
|
||||
QCOMPARE(noEventHandler.getAuthor(), room->getUser(nullptr));
|
||||
}
|
||||
|
||||
void EventHandlerTest::authorDisplayName()
|
||||
{
|
||||
auto event = room->messageEvents().at(1).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(1).get());
|
||||
QCOMPARE(eventHandler.getAuthorDisplayName(), QStringLiteral("before"));
|
||||
}
|
||||
|
||||
@@ -183,15 +134,14 @@ void EventHandlerTest::nullAuthorDisplayName()
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getAuthorDisplayName(), QString());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getAuthorDisplayName(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::singleLineSidplayName()
|
||||
{
|
||||
auto event = room->messageEvents().at(11).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(11).get());
|
||||
QCOMPARE(eventHandler.singleLineAuthorDisplayname(), QStringLiteral("Look at me I put newlines in my display name"));
|
||||
}
|
||||
|
||||
@@ -200,14 +150,14 @@ void EventHandlerTest::nullSingleLineDisplayName()
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.singleLineAuthorDisplayname(), QString());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.singleLineAuthorDisplayname(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::time()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
QCOMPARE(eventHandler.getTime(), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||
QCOMPARE(eventHandler.getTime(true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||
@@ -215,18 +165,18 @@ void EventHandlerTest::time()
|
||||
|
||||
void EventHandlerTest::nullTime()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getTime called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getTime(), QDateTime());
|
||||
|
||||
eventHandler.setEvent(room->messageEvents().at(0).get());
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
|
||||
QCOMPARE(eventHandler.getTime(true), QDateTime());
|
||||
}
|
||||
|
||||
void EventHandlerTest::timeString()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
KFormat format;
|
||||
|
||||
@@ -246,25 +196,22 @@ void EventHandlerTest::timeString()
|
||||
|
||||
void EventHandlerTest::nullTimeString()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getTimeString called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getTimeString(false), QString());
|
||||
|
||||
eventHandler.setEvent(room->messageEvents().at(0).get());
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::highlighted()
|
||||
{
|
||||
auto event = room->messageEvents().at(2).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandlerHighlight(room, room->messageEvents().at(2).get());
|
||||
QCOMPARE(eventHandlerHighlight.isHighlighted(), true);
|
||||
|
||||
QCOMPARE(eventHandler.isHighlighted(), true);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isHighlighted(), false);
|
||||
EventHandler eventHandlerNoHighlight(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoHighlight.isHighlighted(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHighlighted()
|
||||
@@ -272,21 +219,18 @@ void EventHandlerTest::nullHighlighted()
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isHighlighted(), false);
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.isHighlighted(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::hidden()
|
||||
{
|
||||
auto event = room->messageEvents().at(3).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandlerHidden(room, room->messageEvents().at(3).get());
|
||||
QCOMPARE(eventHandlerHidden.isHidden(), true);
|
||||
|
||||
QCOMPARE(eventHandler.isHidden(), true);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isHidden(), false);
|
||||
EventHandler eventHandlerNoHidden(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoHidden.isHidden(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHidden()
|
||||
@@ -294,14 +238,14 @@ void EventHandlerTest::nullHidden()
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isHidden(), false);
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.isHidden(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::body()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
QCOMPARE(eventHandler.getRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(eventHandler.getRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
|
||||
@@ -311,6 +255,8 @@ void EventHandlerTest::body()
|
||||
|
||||
void EventHandlerTest::nullBody()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getRichBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getRichBody(), QString());
|
||||
|
||||
@@ -335,32 +281,30 @@ void EventHandlerTest::genericBody()
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(QString, output);
|
||||
|
||||
eventHandler.setEvent(room->messageEvents().at(eventNum).get());
|
||||
EventHandler eventHandler(room, room->messageEvents().at(eventNum).get());
|
||||
|
||||
QCOMPARE(eventHandler.getGenericBody(), output);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullGenericBody()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getGenericBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getGenericBody(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::subtitle()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is an example text message"));
|
||||
|
||||
event = room->messageEvents().at(2).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is a highlight @bob:kde.org and this is a link https://kde.org"));
|
||||
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
|
||||
QCOMPARE(eventHandler2.subtitleText(), QStringLiteral("after: This is a highlight @bob:kde.org and this is a link https://kde.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullSubtitle()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.subtitleText(), QString());
|
||||
}
|
||||
@@ -368,7 +312,7 @@ void EventHandlerTest::nullSubtitle()
|
||||
void EventHandlerTest::mediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(4).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, event);
|
||||
|
||||
auto mediaInfo = eventHandler.getMediaInfo();
|
||||
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
|
||||
@@ -393,76 +337,48 @@ void EventHandlerTest::nullMediaInfo()
|
||||
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getMediaInfo(), QVariantMap());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
|
||||
}
|
||||
|
||||
void EventHandlerTest::hasReply()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandlerReply(room, room->messageEvents().at(5).get());
|
||||
QCOMPARE(eventHandlerReply.hasReply(), true);
|
||||
|
||||
QCOMPARE(eventHandler.hasReply(), true);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.hasReply(), false);
|
||||
EventHandler eventHandlerNoReply(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoReply.hasReply(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHasReply()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "hasReply called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.hasReply(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyId()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandlerReply(room, room->messageEvents().at(5).get());
|
||||
QCOMPARE(eventHandlerReply.getReplyId(), QStringLiteral("$153456789:example.org"));
|
||||
|
||||
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$153456789:example.org"));
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyId(), QStringLiteral(""));
|
||||
EventHandler eventHandlerNoReply(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoReply.getReplyId(), QStringLiteral(""));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyId()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyId called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyId(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyDelegateType()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyDelegateType(), DelegateType::Message);
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyDelegateType(), DelegateType::Other);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyDelegateType()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyDelegateType called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getReplyDelegateType(), DelegateType::Other);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyDelegateType called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyDelegateType(), DelegateType::Other);
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyAuthor()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
auto replyEvent = room->messageEvents().at(0).get();
|
||||
auto replyAuthor = room->user(replyEvent->senderId());
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||
|
||||
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
|
||||
|
||||
@@ -474,10 +390,8 @@ void EventHandlerTest::replyAuthor()
|
||||
QCOMPARE(eventHandlerReplyAuthor["color"_ls], Utils::getUserColor(replyAuthor->hueF()));
|
||||
QCOMPARE(eventHandlerReplyAuthor["object"_ls], QVariant::fromValue(replyAuthor));
|
||||
|
||||
event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.getReplyAuthor(), room->getUser(nullptr));
|
||||
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), room->getUser(nullptr));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyAuthor()
|
||||
@@ -485,14 +399,14 @@ void EventHandlerTest::nullReplyAuthor()
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getReplyAuthor(), QVariantMap());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
|
||||
QCOMPARE(noEventHandler.getReplyAuthor(), room->getUser(nullptr));
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyBody()
|
||||
{
|
||||
auto event = room->messageEvents().at(5).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||
|
||||
QCOMPARE(eventHandler.getReplyRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(eventHandler.getReplyRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
|
||||
@@ -502,6 +416,8 @@ void EventHandlerTest::replyBody()
|
||||
|
||||
void EventHandlerTest::nullReplyBody()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyRichBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyRichBody(), QString());
|
||||
|
||||
@@ -513,7 +429,7 @@ void EventHandlerTest::replyMediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(6).get();
|
||||
auto replyEvent = room->messageEvents().at(4).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, event);
|
||||
|
||||
auto mediaInfo = eventHandler.getReplyMediaInfo();
|
||||
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
|
||||
@@ -538,31 +454,26 @@ void EventHandlerTest::nullReplyMediaInfo()
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getReplyMediaInfo(), QVariantMap());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyMediaInfo(), QVariantMap());
|
||||
}
|
||||
|
||||
void EventHandlerTest::thread()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandlerNoThread(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoThread.isThreaded(), false);
|
||||
QCOMPARE(eventHandlerNoThread.threadRoot(), QString());
|
||||
|
||||
QCOMPARE(eventHandler.isThreaded(), false);
|
||||
QCOMPARE(eventHandler.threadRoot(), QString());
|
||||
EventHandler eventHandlerThreadRoot(room, room->messageEvents().at(9).get());
|
||||
QCOMPARE(eventHandlerThreadRoot.isThreaded(), true);
|
||||
QCOMPARE(eventHandlerThreadRoot.threadRoot(), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(eventHandlerThreadRoot.getReplyId(), QStringLiteral("$threadroot:example.org"));
|
||||
|
||||
event = room->messageEvents().at(9).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isThreaded(), true);
|
||||
QCOMPARE(eventHandler.threadRoot(), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$threadroot:example.org"));
|
||||
|
||||
event = room->messageEvents().at(10).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.isThreaded(), true);
|
||||
QCOMPARE(eventHandler.threadRoot(), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$threadmessage1:example.org"));
|
||||
EventHandler eventHandlerThreadReply(room, room->messageEvents().at(10).get());
|
||||
QCOMPARE(eventHandlerThreadReply.isThreaded(), true);
|
||||
QCOMPARE(eventHandlerThreadReply.threadRoot(), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(eventHandlerThreadReply.getReplyId(), QStringLiteral("$threadmessage1:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullThread()
|
||||
@@ -570,14 +481,14 @@ void EventHandlerTest::nullThread()
|
||||
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isThreaded(), false);
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.threadRoot(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::location()
|
||||
{
|
||||
auto event = room->messageEvents().at(7).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler(room, room->messageEvents().at(7).get());
|
||||
|
||||
QCOMPARE(eventHandler.getLatitude(), QStringLiteral("51.7035").toFloat());
|
||||
QCOMPARE(eventHandler.getLongitude(), QStringLiteral("-1.14394").toFloat());
|
||||
@@ -598,9 +509,7 @@ void EventHandlerTest::nullLocation()
|
||||
|
||||
void EventHandlerTest::readMarkers()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandler.hasReadMarkers(), true);
|
||||
|
||||
auto readMarkers = eventHandler.getReadMarkers();
|
||||
@@ -611,18 +520,16 @@ void EventHandlerTest::readMarkers()
|
||||
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QString());
|
||||
QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: @alice:matrix.org"));
|
||||
|
||||
event = room->messageEvents().at(2).get();
|
||||
eventHandler.setEvent(event);
|
||||
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
|
||||
QCOMPARE(eventHandler2.hasReadMarkers(), true);
|
||||
|
||||
QCOMPARE(eventHandler.hasReadMarkers(), true);
|
||||
|
||||
readMarkers = eventHandler.getReadMarkers();
|
||||
readMarkers = eventHandler2.getReadMarkers();
|
||||
|
||||
QCOMPARE(readMarkers.size(), 5);
|
||||
|
||||
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QStringLiteral("+ 1"));
|
||||
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(eventHandler.getReadMarkersString().startsWith(QStringLiteral("6 users:")), true);
|
||||
QCOMPARE(eventHandler2.getReadMarkersString().startsWith(QStringLiteral("6 users:")), true);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReadMarkers()
|
||||
@@ -639,6 +546,8 @@ void EventHandlerTest::nullReadMarkers()
|
||||
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);
|
||||
|
||||
@@ -652,10 +561,5 @@ void EventHandlerTest::nullReadMarkers()
|
||||
QCOMPARE(noEventHandler.getReadMarkersString(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::cleanup()
|
||||
{
|
||||
eventHandler.setEvent(nullptr);
|
||||
}
|
||||
|
||||
QTEST_MAIN(EventHandlerTest)
|
||||
#include "eventhandlertest.moc"
|
||||
|
||||
@@ -103,7 +103,6 @@ void MessageEventModelTest::simpleTimeline()
|
||||
|
||||
QCOMPARE(model->data(model->index(1)), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(model->data(model->index(1), MessageEventModel::DelegateTypeRole), DelegateType::Message);
|
||||
QCOMPARE(model->data(model->index(1), MessageEventModel::PlainText), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(model->data(model->index(1), MessageEventModel::EventIdRole), QStringLiteral("$153456789:example.org"));
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "Index QModelIndex(-1,-1,0x0,QObject(0x0)) is not valid (expected valid)");
|
||||
|
||||
@@ -87,6 +87,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="fi">NeoChat on asiakassovellus Matrixille, hajautetulle pikaviestinyhteyskäytännölle. Sillä voi lähettää teksti-, video- ja ääniviestejä perheelle, tutuille ja ystäville. Se käyttää KDE-kehystä ja erityisesti Kirigamia tuottaakseen mukautuvan monialustaisen käyttökokemuksen.</p>
|
||||
<p xml:lang="fr">NeoChat est un client pour le protocole Matrix, un protocole décentralisé de communications pour messagerie instantané. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis. Il utilise les environnements de développement et plus précisément Kirigami pour fournir une expérience convergente sur plusieurs plate-formes. </p>
|
||||
<p xml:lang="gl">NeoChat é un cliente para Matrix, o protocolo de comunicación descentralizada para mensaxaría instantánea. Podes enviar mensaxes de texto, vídeos e ficheiros de son á túa familia, colegas e amizades. Usas infraestruturas de KDE e principalmente Kirigami para proporcionar unha experiencia de uso converxente para varias plataformas.</p>
|
||||
<p xml:lang="hu">A NeoChat egy kliens a Matrixhoz, az azonnali üzenetküldés decentralizált komunikációs protokolljához.. Szöveges üzeneteket, videókat és hangfájlokat küldhet családjának, kollégáinak és barátainak. A KDE keretrendszert használja, a Kirigaminak köszönhetően konvergens élményt nyújt több platformon is.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente per Matrix, le protocollo de communication decentralisate per messager instantanee. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante. Illo usa KDE frameworks e super toto Kirigamii forni un experientia convergente trans platteforme multiple.</p>
|
||||
<p xml:lang="it">NeoChat è un client per Matrix, il protocollo di comunicazione decentralizzato per la messaggistica istantanea. Ti consente di inviare messaggi di testo, video e file audio a familiari, colleghi e amici. Utilizza i framework KDE e in particolare Kirigami per fornire un'esperienza convergente su più piattaforme.</p>
|
||||
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით.</p>
|
||||
@@ -324,6 +325,24 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="24.02.0" date="2024-02-28">
|
||||
<url>https://kde.org/announcements/megarelease/6/#neochat</url>
|
||||
<description>
|
||||
<p>In the newest version, when launching the app, you will get a welcome page that lets you choose which account you want to use and lets you log in to other accounts. The welcome screen will also warn you when NeoChat cannot load an account.</p>
|
||||
<p>NeoChat will also let you register a new account directly from the app itself. Deactivating your Matrix account is also possible from within NeoChat.</p>
|
||||
<p>Spaces are a relatively new feature of Matrix that let you group chat channels together. This is used to improve the discoverability of rooms, manage large communities, or just tidy all the channels you are in. You can now do all this without leaving NeoChat.</p>
|
||||
<p>NeoChat won't let you miss any new notifications anymore. We added a new page that includes all your recent notifications and when NeoChat is closed, you will still be able to receive push notifications. The main timeline will let you know when more messages are loading and when you reach the end of it.</p>
|
||||
<p>More NeoChat Goodies</p>
|
||||
<ul>
|
||||
<li>QR Codes to share contacts</li>
|
||||
<li>Improved room upgrades</li>
|
||||
<li>Added button to reject invitation and ignore user</li>
|
||||
<li>Display device security details</li>
|
||||
<li>Added room security settings</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="23.08.5" date="2024-02-15"/>
|
||||
<release version="23.08.4" date="2023-12-07"/>
|
||||
<release version="23.08.3" date="2023-11-09"/>
|
||||
<release version="23.08.2" date="2023-10-12"/>
|
||||
|
||||
1040
po/ar/neochat.po
1040
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1020
po/ast/neochat.po
1020
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1052
po/az/neochat.po
1052
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1071
po/ca/neochat.po
1071
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1102
po/cs/neochat.po
1102
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1042
po/da/neochat.po
1042
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1044
po/de/neochat.po
1044
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1051
po/el/neochat.po
1051
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1044
po/en_GB/neochat.po
1044
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1030
po/eo/neochat.po
1030
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1041
po/es/neochat.po
1041
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1041
po/eu/neochat.po
1041
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1417
po/fi/neochat.po
1417
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1125
po/fr/neochat.po
1125
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1070
po/hu/neochat.po
1070
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1041
po/ia/neochat.po
1041
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1040
po/id/neochat.po
1040
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1049
po/ie/neochat.po
1049
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1096
po/it/neochat.po
1096
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1020
po/ja/neochat.po
1020
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1037
po/ka/neochat.po
1037
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1044
po/ko/neochat.po
1044
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1020
po/lt/neochat.po
1020
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1043
po/nl/neochat.po
1043
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1032
po/nn/neochat.po
1032
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1052
po/pa/neochat.po
1052
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1042
po/pl/neochat.po
1042
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1040
po/pt/neochat.po
1040
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1052
po/pt_BR/neochat.po
1052
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1188
po/ru/neochat.po
1188
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1052
po/sk/neochat.po
1052
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1043
po/sl/neochat.po
1043
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1044
po/sv/neochat.po
1044
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1131
po/ta/neochat.po
1131
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1042
po/tok/neochat.po
1042
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
||||
></term>
|
||||
<listitem>
|
||||
<para
|
||||
>Bir kullanıcı veya oda için matrix URI'si; örneğin, matrix:u/kullanıcı:örnek.org ve matrix:r/kök:örnek.org. Bu, NeoChat'in verilen odayı veya konuşmayı açmayı denemesini sağlar. </para>
|
||||
>Bir kullanıcı veya oda için matrix URI’si; örneğin, matrix:u/kullanıcı:örnek.org ve matrix:r/kök:örnek.org. Bu, NeoChat’in verilen odayı veya konuşmayı açmayı denemesini sağlar. </para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
1034
po/tr/neochat.po
1034
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1041
po/uk/neochat.po
1041
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1069
po/zh_CN/neochat.po
1069
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1083
po/zh_TW/neochat.po
1083
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,8 @@ add_library(neochat STATIC
|
||||
notificationsmanager.h
|
||||
models/sortfilterroomlistmodel.cpp
|
||||
models/sortfilterroomlistmodel.h
|
||||
models/roomtreemodel.cpp
|
||||
models/roomtreemodel.h
|
||||
chatdocumenthandler.cpp
|
||||
chatdocumenthandler.h
|
||||
models/devicesmodel.cpp
|
||||
@@ -148,6 +150,17 @@ add_library(neochat STATIC
|
||||
models/itinerarymodel.h
|
||||
proxycontroller.cpp
|
||||
proxycontroller.h
|
||||
models/linemodel.cpp
|
||||
models/linemodel.h
|
||||
events/locationbeaconevent.h
|
||||
events/serveraclevent.h
|
||||
events/widgetevent.h
|
||||
enums/messagecomponenttype.h
|
||||
models/messagecontentmodel.cpp
|
||||
models/messagecontentmodel.h
|
||||
enums/neochatroomtype.h
|
||||
models/sortfilterroomtreemodel.cpp
|
||||
models/sortfilterroomtreemodel.h
|
||||
)
|
||||
|
||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
@@ -193,21 +206,12 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/TimelineDelegate.qml
|
||||
qml/ReplyComponent.qml
|
||||
qml/StateDelegate.qml
|
||||
qml/RichLabel.qml
|
||||
qml/MessageDelegate.qml
|
||||
qml/Bubble.qml
|
||||
qml/SectionDelegate.qml
|
||||
qml/VideoDelegate.qml
|
||||
qml/ReactionDelegate.qml
|
||||
qml/LinkPreviewDelegate.qml
|
||||
qml/AudioDelegate.qml
|
||||
qml/FileDelegate.qml
|
||||
qml/ImageDelegate.qml
|
||||
qml/EncryptedDelegate.qml
|
||||
qml/EventDelegate.qml
|
||||
qml/TextDelegate.qml
|
||||
qml/ReadMarkerDelegate.qml
|
||||
qml/PollDelegate.qml
|
||||
qml/MimeComponent.qml
|
||||
qml/StateComponent.qml
|
||||
qml/MessageEditComponent.qml
|
||||
@@ -271,14 +275,12 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/EmojiDelegate.qml
|
||||
qml/EmojiGrid.qml
|
||||
qml/RoomSearchPage.qml
|
||||
qml/LocationDelegate.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/TimelineView.qml
|
||||
qml/InvitationView.qml
|
||||
qml/AvatarTabButton.qml
|
||||
qml/SpaceDrawer.qml
|
||||
qml/OsmLocationPlugin.qml
|
||||
qml/LiveLocationDelegate.qml
|
||||
qml/FullScreenMap.qml
|
||||
qml/LocationsPage.qml
|
||||
qml/LocationMapItem.qml
|
||||
@@ -304,6 +306,21 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/SearchPage.qml
|
||||
qml/ServerComboBox.qml
|
||||
qml/UserSearchPage.qml
|
||||
qml/ManualUserDialog.qml
|
||||
qml/MessageComponentChooser.qml
|
||||
qml/TextComponent.qml
|
||||
qml/ImageComponent.qml
|
||||
qml/VideoComponent.qml
|
||||
qml/AudioComponent.qml
|
||||
qml/EncryptedComponent.qml
|
||||
qml/FileComponent.qml
|
||||
qml/LocationComponent.qml
|
||||
qml/LiveLocationComponent.qml
|
||||
qml/PollComponent.qml
|
||||
qml/LinkPreviewComponent.qml
|
||||
qml/LoadComponent.qml
|
||||
qml/RecommendedSpaceDialog.qml
|
||||
qml/RoomTreeSection.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
|
||||
@@ -43,14 +43,14 @@ void ChatBarCache::setReplyId(const QString &replyId)
|
||||
if (m_relationType == Reply && m_relationId == replyId) {
|
||||
return;
|
||||
}
|
||||
m_relationId = replyId;
|
||||
const auto oldEventId = std::exchange(m_relationId, replyId);
|
||||
if (m_relationId.isEmpty()) {
|
||||
m_relationType = None;
|
||||
} else {
|
||||
m_relationType = Reply;
|
||||
}
|
||||
m_attachmentPath = QString();
|
||||
Q_EMIT relationIdChanged();
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
}
|
||||
|
||||
@@ -72,14 +72,14 @@ void ChatBarCache::setEditId(const QString &editId)
|
||||
if (m_relationType == Edit && m_relationId == editId) {
|
||||
return;
|
||||
}
|
||||
m_relationId = editId;
|
||||
const auto oldEventId = std::exchange(m_relationId, editId);
|
||||
if (m_relationId.isEmpty()) {
|
||||
m_relationType = None;
|
||||
} else {
|
||||
m_relationType = Edit;
|
||||
}
|
||||
m_attachmentPath = QString();
|
||||
Q_EMIT relationIdChanged();
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
}
|
||||
|
||||
@@ -114,10 +114,9 @@ QString ChatBarCache::relationMessage() const
|
||||
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
return {};
|
||||
}
|
||||
EventHandler eventhandler;
|
||||
eventhandler.setRoom(room);
|
||||
|
||||
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
|
||||
eventhandler.setEvent(&**event);
|
||||
EventHandler eventhandler(room, &**event);
|
||||
return eventhandler.getPlainBody();
|
||||
}
|
||||
return {};
|
||||
@@ -154,9 +153,9 @@ void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
|
||||
}
|
||||
m_attachmentPath = attachmentPath;
|
||||
m_relationType = None;
|
||||
m_relationId = QString();
|
||||
const auto oldEventId = std::exchange(m_relationId, QString());
|
||||
Q_EMIT attachmentPathChanged();
|
||||
Q_EMIT relationIdChanged();
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
}
|
||||
|
||||
QList<Mention> *ChatBarCache::mentions()
|
||||
|
||||
@@ -186,7 +186,7 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void textChanged();
|
||||
void relationIdChanged();
|
||||
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
|
||||
void threadIdChanged();
|
||||
void attachmentPathChanged();
|
||||
|
||||
|
||||
@@ -178,10 +178,12 @@ void Controller::invokeLogin()
|
||||
}
|
||||
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection] {
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connection->loadState();
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
});
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
@@ -330,3 +332,13 @@ void Controller::setTestMode(bool test)
|
||||
{
|
||||
testMode = test;
|
||||
}
|
||||
|
||||
void Controller::removeConnection(const QString &userId)
|
||||
{
|
||||
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
|
||||
auto connection = m_connectionsLoading[userId];
|
||||
m_accountsLoading.removeAll(userId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
SettingsGroup("Accounts"_ls).remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +96,8 @@ public:
|
||||
|
||||
static void setTestMode(bool testMode);
|
||||
|
||||
Q_INVOKABLE void removeConnection(const QString &userId);
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
@@ -109,6 +111,7 @@ private:
|
||||
|
||||
Quotient::AccountRegistry m_accountRegistry;
|
||||
QStringList m_accountsLoading;
|
||||
QMap<QString, QPointer<Quotient::Connection>> m_connectionsLoading;
|
||||
QString m_endpoint;
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
@@ -6,6 +6,13 @@
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/events/encryptedevent.h>
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
|
||||
#include "events/pollevent.h"
|
||||
|
||||
/**
|
||||
* @class DelegateType
|
||||
*
|
||||
@@ -26,23 +33,34 @@ public:
|
||||
* similar to the spec it is not the same.
|
||||
*/
|
||||
enum Type {
|
||||
Emote, /**< A message that begins with /me. */
|
||||
Notice, /**< A notice event. */
|
||||
Image, /**< A message that is an image. */
|
||||
Audio, /**< A message that is an audio recording. */
|
||||
Video, /**< A message that is a video. */
|
||||
File, /**< A message that is a file. */
|
||||
Message, /**< A text message. */
|
||||
Sticker, /**< A message that is a sticker. */
|
||||
State, /**< A state event in the room. */
|
||||
Encrypted, /**< An encrypted message that cannot be decrypted. */
|
||||
ReadMarker, /**< The local user read marker. */
|
||||
Poll, /**< The initial event for a poll. */
|
||||
Location, /**< A location event. */
|
||||
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
|
||||
Loading, /**< A delegate to tell the user more messages are being loaded. */
|
||||
TimelineEnd, /**< A delegate to inform that all messages are loaded. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
};
|
||||
Q_ENUM(Type);
|
||||
|
||||
/**
|
||||
* @brief Return the delegate type for the given event.
|
||||
*
|
||||
* @param event the event to return a type for.
|
||||
*
|
||||
* @sa Type
|
||||
*/
|
||||
static Type typeForEvent(const Quotient::RoomEvent &event)
|
||||
{
|
||||
if (event.is<Quotient::RoomMessageEvent>() || event.is<Quotient::StickerEvent>() || event.is<Quotient::EncryptedEvent>()
|
||||
|| event.is<Quotient::PollStartEvent>()) {
|
||||
return Message;
|
||||
}
|
||||
if (event.isStateEvent()) {
|
||||
if (event.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return Message;
|
||||
}
|
||||
return State;
|
||||
}
|
||||
return Other;
|
||||
}
|
||||
};
|
||||
|
||||
107
src/enums/messagecomponenttype.h
Normal file
107
src/enums/messagecomponenttype.h
Normal file
@@ -0,0 +1,107 @@
|
||||
// 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 <QQmlEngine>
|
||||
|
||||
#include <Quotient/events/encryptedevent.h>
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
|
||||
#include "events/pollevent.h"
|
||||
|
||||
/**
|
||||
* @class MessageComponentType
|
||||
*
|
||||
* This class is designed to define the MessageComponentType enumeration.
|
||||
*/
|
||||
class MessageComponentType : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief The type of component that is needed for an event.
|
||||
*
|
||||
* @note While similar this is not the matrix event or message type. This is
|
||||
* to tell a QML Bubble what component to use to visualise all or part of
|
||||
* a room message.
|
||||
*/
|
||||
enum Type {
|
||||
Text, /**< A text message. */
|
||||
Image, /**< A message that is an image. */
|
||||
Audio, /**< A message that is an audio recording. */
|
||||
Video, /**< A message that is a video. */
|
||||
File, /**< A message that is a file. */
|
||||
Poll, /**< The initial event for a poll. */
|
||||
Location, /**< A location event. */
|
||||
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
|
||||
Encrypted, /**< An encrypted message that cannot be decrypted. */
|
||||
Reply, /**< A component to show a replied-to message. */
|
||||
ReplyLoad, /**< A loading dialog for a reply. */
|
||||
LinkPreview, /**< A preview of a URL in the message. */
|
||||
LinkPreviewLoad, /**< A loading dialog for a link preview. */
|
||||
Edit, /**< A text edit for editing a message. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
};
|
||||
Q_ENUM(Type);
|
||||
|
||||
/**
|
||||
* @brief Return the delegate type for the given event.
|
||||
*
|
||||
* @param event the event to return a type for.
|
||||
*
|
||||
* @sa Type
|
||||
*/
|
||||
static Type typeForEvent(const Quotient::RoomEvent &event)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
if (const auto e = eventCast<const RoomMessageEvent>(&event)) {
|
||||
switch (e->msgtype()) {
|
||||
case MessageEventType::Emote:
|
||||
return MessageComponentType::Text;
|
||||
case MessageEventType::Notice:
|
||||
return MessageComponentType::Text;
|
||||
case MessageEventType::Image:
|
||||
return MessageComponentType::Image;
|
||||
case MessageEventType::Audio:
|
||||
return MessageComponentType::Audio;
|
||||
case MessageEventType::Video:
|
||||
return MessageComponentType::Video;
|
||||
case MessageEventType::Location:
|
||||
return MessageComponentType::Location;
|
||||
case MessageEventType::File:
|
||||
return MessageComponentType::File;
|
||||
default:
|
||||
return MessageComponentType::Text;
|
||||
}
|
||||
}
|
||||
if (is<const StickerEvent>(event)) {
|
||||
return MessageComponentType::Image;
|
||||
}
|
||||
if (event.isStateEvent()) {
|
||||
if (event.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return MessageComponentType::LiveLocation;
|
||||
}
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
if (is<const EncryptedEvent>(event)) {
|
||||
return MessageComponentType::Encrypted;
|
||||
}
|
||||
if (is<PollStartEvent>(event)) {
|
||||
const auto pollEvent = eventCast<const PollStartEvent>(&event);
|
||||
if (pollEvent->isRedacted()) {
|
||||
return MessageComponentType::Text;
|
||||
}
|
||||
return MessageComponentType::Poll;
|
||||
}
|
||||
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
};
|
||||
97
src/enums/neochatroomtype.h
Normal file
97
src/enums/neochatroomtype.h
Normal file
@@ -0,0 +1,97 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "neochatroom.h"
|
||||
#include <Quotient/quotient_common.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
class NeoChatRoomType : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the room list categories a room can be assigned.
|
||||
*/
|
||||
enum Types {
|
||||
Search = 0, /**< So we can show a search delegate if needed, e.g. collapsed mode. */
|
||||
Invited, /**< The user has been invited to the room. */
|
||||
Favorite, /**< The room is set as a favourite. */
|
||||
Direct, /**< The room is a direct chat. */
|
||||
Normal, /**< The default category for a joined room. */
|
||||
Deprioritized, /**< The room is set as low priority. */
|
||||
Space, /**< The room is a space. */
|
||||
AddDirect, /**< So we can show the add friend delegate. */
|
||||
};
|
||||
Q_ENUM(Types);
|
||||
|
||||
static NeoChatRoomType::Types typeForRoom(const NeoChatRoom *room)
|
||||
{
|
||||
if (room->isSpace()) {
|
||||
return NeoChatRoomType::Space;
|
||||
}
|
||||
if (room->joinState() == Quotient::JoinState::Invite) {
|
||||
return NeoChatRoomType::Invited;
|
||||
}
|
||||
if (room->isFavourite()) {
|
||||
return NeoChatRoomType::Favorite;
|
||||
}
|
||||
if (room->isLowPriority()) {
|
||||
return NeoChatRoomType::Deprioritized;
|
||||
}
|
||||
if (room->isDirectChat()) {
|
||||
return NeoChatRoomType::Direct;
|
||||
}
|
||||
return NeoChatRoomType::Normal;
|
||||
}
|
||||
|
||||
static QString typeName(int category)
|
||||
{
|
||||
switch (category) {
|
||||
case NeoChatRoomType::Invited:
|
||||
return i18n("Invited");
|
||||
case NeoChatRoomType::Favorite:
|
||||
return i18n("Favorite");
|
||||
case NeoChatRoomType::Direct:
|
||||
return i18n("Friends");
|
||||
case NeoChatRoomType::Normal:
|
||||
return i18n("Normal");
|
||||
case NeoChatRoomType::Deprioritized:
|
||||
return i18n("Low priority");
|
||||
case NeoChatRoomType::Space:
|
||||
return i18n("Spaces");
|
||||
case NeoChatRoomType::Search:
|
||||
return i18n("Search");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
static QString typeIconName(int category)
|
||||
{
|
||||
switch (category) {
|
||||
case NeoChatRoomType::Invited:
|
||||
return QStringLiteral("user-invisible");
|
||||
case NeoChatRoomType::Favorite:
|
||||
return QStringLiteral("favorite");
|
||||
case NeoChatRoomType::Direct:
|
||||
return QStringLiteral("dialog-messages");
|
||||
case NeoChatRoomType::Normal:
|
||||
return QStringLiteral("group");
|
||||
case NeoChatRoomType::Deprioritized:
|
||||
return QStringLiteral("object-order-lower");
|
||||
case NeoChatRoomType::Space:
|
||||
return QStringLiteral("group");
|
||||
case NeoChatRoomType::Search:
|
||||
return QStringLiteral("search");
|
||||
default:
|
||||
return QStringLiteral("tools-report-bug");
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -14,15 +14,19 @@
|
||||
#include <Quotient/events/roomavatarevent.h>
|
||||
#include <Quotient/events/roomcanonicalaliasevent.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
#include <Quotient/events/simplestateevents.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
|
||||
#include "delegatetype.h"
|
||||
#include "eventhandler_logging.h"
|
||||
#include "events/locationbeaconevent.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "events/serveraclevent.h"
|
||||
#include "events/widgetevent.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecomponenttype.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
@@ -31,34 +35,10 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
const NeoChatRoom *EventHandler::getRoom() const
|
||||
EventHandler::EventHandler(const NeoChatRoom *room, const RoomEvent *event)
|
||||
: m_room(room)
|
||||
, m_event(event)
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void EventHandler::setRoom(const NeoChatRoom *room)
|
||||
{
|
||||
if (room == m_room) {
|
||||
return;
|
||||
}
|
||||
m_room = room;
|
||||
}
|
||||
|
||||
const Quotient::Event *EventHandler::getEvent() const
|
||||
{
|
||||
return m_event;
|
||||
}
|
||||
|
||||
void EventHandler::setEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "cannot setEvent when m_room is set to nullptr.";
|
||||
return;
|
||||
}
|
||||
if (event == m_event) {
|
||||
return;
|
||||
}
|
||||
m_event = event;
|
||||
}
|
||||
|
||||
QString EventHandler::getId() const
|
||||
@@ -71,62 +51,14 @@ QString EventHandler::getId() const
|
||||
return !m_event->id().isEmpty() ? m_event->id() : m_event->transactionId();
|
||||
}
|
||||
|
||||
DelegateType::Type EventHandler::getDelegateTypeForEvent(const Quotient::RoomEvent *event) const
|
||||
{
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
switch (e->msgtype()) {
|
||||
case MessageEventType::Emote:
|
||||
return DelegateType::Emote;
|
||||
case MessageEventType::Notice:
|
||||
return DelegateType::Notice;
|
||||
case MessageEventType::Image:
|
||||
return DelegateType::Image;
|
||||
case MessageEventType::Audio:
|
||||
return DelegateType::Audio;
|
||||
case MessageEventType::Video:
|
||||
return DelegateType::Video;
|
||||
case MessageEventType::Location:
|
||||
return DelegateType::Location;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (e->hasFileContent()) {
|
||||
return DelegateType::File;
|
||||
}
|
||||
|
||||
return DelegateType::Message;
|
||||
}
|
||||
if (is<const StickerEvent>(*event)) {
|
||||
return DelegateType::Sticker;
|
||||
}
|
||||
if (event->isStateEvent()) {
|
||||
if (event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return DelegateType::LiveLocation;
|
||||
}
|
||||
return DelegateType::State;
|
||||
}
|
||||
if (is<const EncryptedEvent>(*event)) {
|
||||
return DelegateType::Encrypted;
|
||||
}
|
||||
if (is<PollStartEvent>(*event)) {
|
||||
const auto pollEvent = eventCast<const PollStartEvent>(event);
|
||||
if (pollEvent->isRedacted()) {
|
||||
return DelegateType::Message;
|
||||
}
|
||||
return DelegateType::Poll;
|
||||
}
|
||||
|
||||
return DelegateType::Other;
|
||||
}
|
||||
|
||||
DelegateType::Type EventHandler::getDelegateType() const
|
||||
MessageComponentType::Type EventHandler::messageComponentType() const
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getDelegateType called with m_event set to nullptr.";
|
||||
return DelegateType::Other;
|
||||
qCWarning(EventHandling) << "messageComponentType called with m_event set to nullptr.";
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
|
||||
return getDelegateTypeForEvent(m_event);
|
||||
return MessageComponentType::typeForEvent(*m_event);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getAuthor(bool isPending) const
|
||||
@@ -345,8 +277,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
||||
}
|
||||
|
||||
if (prettyPrint) {
|
||||
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
|
||||
.arg(e.userId(), Utils::getUserColor(m_room->user(e.userId())->hueF()).name(), subjectName);
|
||||
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%2</a>").arg(e.userId(), subjectName);
|
||||
}
|
||||
|
||||
// The below code assumes senderName output in AuthorRole
|
||||
@@ -457,22 +388,22 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
||||
[](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||
},
|
||||
[](const LocationBeaconEvent &e) {
|
||||
return e.contentJson()["description"_ls].toString();
|
||||
},
|
||||
[](const ServerAclEvent &) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
},
|
||||
[](const WidgetEvent &e) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
return i18nc("[User] added <name> widget", "added %1 widget", e.contentJson()["name"_ls].toString());
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18nc("[User] removed <name> widget", "removed %1 widget", e.fullJson()["unsigned"_ls]["prev_content"_ls]["name"_ls].toString());
|
||||
}
|
||||
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_ls].toString());
|
||||
},
|
||||
[prettyPrint](const StateEvent &e) {
|
||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
}
|
||||
if (e.matrixType() == QLatin1String("im.vector.modular.widgets")) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
return i18nc("[User] added <name> widget", "added %1 widget", e.contentJson()["name"_ls].toString());
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18nc("[User] removed <name> widget", "removed %1 widget", e.fullJson()["unsigned"_ls]["prev_content"_ls]["name"_ls].toString());
|
||||
}
|
||||
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_ls].toString());
|
||||
}
|
||||
if (e.matrixType() == "org.matrix.msc3672.beacon_info"_ls) {
|
||||
return e.contentJson()["description"_ls].toString();
|
||||
}
|
||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
|
||||
},
|
||||
@@ -626,19 +557,22 @@ QString EventHandler::getGenericBody() const
|
||||
[](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||
},
|
||||
[](const StateEvent &e) {
|
||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
[](const LocationBeaconEvent &) {
|
||||
return i18n("sent a live location beacon");
|
||||
},
|
||||
[](const ServerAclEvent &) {
|
||||
return i18n("changed the server access control lists for this room");
|
||||
},
|
||||
[](const WidgetEvent &e) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
return i18n("added a widget");
|
||||
}
|
||||
if (e.matrixType() == QLatin1String("im.vector.modular.widgets")) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
return i18n("added a widget");
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18n("removed a widget");
|
||||
}
|
||||
return i18n("configured a widget");
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18n("removed a widget");
|
||||
}
|
||||
return i18n("configured a widget");
|
||||
},
|
||||
[](const StateEvent &) {
|
||||
return i18n("updated the state");
|
||||
},
|
||||
[](const PollStartEvent &e) {
|
||||
@@ -795,22 +729,22 @@ QString EventHandler::getReplyId() const
|
||||
return m_event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
}
|
||||
|
||||
DelegateType::Type EventHandler::getReplyDelegateType() const
|
||||
MessageComponentType::Type EventHandler::replyMessageComponentType() const
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyDelegateType called with m_room set to nullptr.";
|
||||
return DelegateType::Other;
|
||||
qCWarning(EventHandling) << "replyMessageComponentType called with m_room set to nullptr.";
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyDelegateType called with m_event set to nullptr.";
|
||||
return DelegateType::Other;
|
||||
qCWarning(EventHandling) << "replyMessageComponentType called with m_event set to nullptr.";
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
|
||||
auto replyEvent = m_room->getReplyForEvent(*m_event);
|
||||
if (replyEvent == nullptr) {
|
||||
return DelegateType::Other;
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
return getDelegateTypeForEvent(replyEvent);
|
||||
return MessageComponentType::typeForEvent(*replyEvent);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getReplyAuthor() const
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "enums/messagecomponenttype.h"
|
||||
|
||||
class LinkPreviewer;
|
||||
class NeoChatRoom;
|
||||
@@ -31,30 +31,12 @@ class ReactionModel;
|
||||
* information. This is to minimize warnings from QML especially during startup
|
||||
* and room changes.
|
||||
*/
|
||||
class EventHandler : public QObject
|
||||
class EventHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Return the current room the EventHandler is using.
|
||||
*/
|
||||
const NeoChatRoom *getRoom() const;
|
||||
|
||||
/**
|
||||
* @brief Set the current room the EventHandler to using.
|
||||
*/
|
||||
void setRoom(const NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Return the current event the EventHandler is using.
|
||||
*/
|
||||
const Quotient::Event *getEvent() const;
|
||||
|
||||
/**
|
||||
* @brief Set the current event the EventHandler to using.
|
||||
*/
|
||||
void setEvent(const Quotient::RoomEvent *event);
|
||||
EventHandler(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the event.
|
||||
@@ -62,13 +44,9 @@ public:
|
||||
QString getId() const;
|
||||
|
||||
/**
|
||||
* @brief Return the DelegateType of the event.
|
||||
*
|
||||
* @note While similar this is not the matrix event or message type. This is
|
||||
* to tell a QML ListView what delegate to show for each event. So while
|
||||
* similar to the spec it is not the same.
|
||||
* @brief The MessageComponentType to use to visualise the main event content.
|
||||
*/
|
||||
DelegateType::Type getDelegateType() const;
|
||||
MessageComponentType::Type messageComponentType() const;
|
||||
|
||||
/**
|
||||
* @brief Get the author of the event in context of the room.
|
||||
@@ -242,13 +220,9 @@ public:
|
||||
QString getReplyId() const;
|
||||
|
||||
/**
|
||||
* @brief Return the DelegateType of the event replied to.
|
||||
*
|
||||
* @note While similar this is not the matrix event or message type. This is
|
||||
* to tell a QML ListView what delegate to show for each event. So while
|
||||
* similar to the spec it is not the same.
|
||||
* @brief The MessageComponentType to use to visualise the reply content.
|
||||
*/
|
||||
DelegateType::Type getReplyDelegateType() const;
|
||||
MessageComponentType::Type replyMessageComponentType() const;
|
||||
|
||||
/**
|
||||
* @brief Get the author of the event replied to in context of the room.
|
||||
@@ -404,8 +378,6 @@ private:
|
||||
|
||||
KFormat m_format;
|
||||
|
||||
DelegateType::Type getDelegateTypeForEvent(const Quotient::RoomEvent *event) const;
|
||||
|
||||
QString getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const;
|
||||
QString getMessageBody(const Quotient::RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines) const;
|
||||
|
||||
|
||||
14
src/events/locationbeaconevent.h
Normal file
14
src/events/locationbeaconevent.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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 <Quotient/events/simplestateevents.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
|
||||
// Defined so we can directly switch on type.
|
||||
DEFINE_SIMPLE_STATE_EVENT(LocationBeaconEvent, "org.matrix.msc3672.beacon_info", QString, body, "body")
|
||||
|
||||
} // namespace Quotient
|
||||
14
src/events/serveraclevent.h
Normal file
14
src/events/serveraclevent.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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 <Quotient/events/simplestateevents.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
|
||||
// Defined so we can directly switch on type.
|
||||
DEFINE_SIMPLE_STATE_EVENT(ServerAclEvent, "m.room.server_acl", bool, allow_ip_literals, "allow_ip_literals")
|
||||
|
||||
} // namespace Quotient
|
||||
14
src/events/widgetevent.h
Normal file
14
src/events/widgetevent.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// 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 <Quotient/events/simplestateevents.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
|
||||
// Defined so we can directly switch on type.
|
||||
DEFINE_SIMPLE_STATE_EVENT(WidgetEvent, "im.vector.modular.widgets", QString, name, "name")
|
||||
|
||||
} // namespace Quotient
|
||||
@@ -13,8 +13,8 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event)
|
||||
: QObject(nullptr)
|
||||
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_currentRoom(room)
|
||||
, m_event(event)
|
||||
, m_loaded(false)
|
||||
|
||||
@@ -60,7 +60,7 @@ class LinkPreviewer : public QObject
|
||||
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
|
||||
|
||||
public:
|
||||
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr);
|
||||
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr, QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] QUrl url() const;
|
||||
[[nodiscard]] bool loaded() const;
|
||||
|
||||
@@ -230,7 +230,7 @@ QList<ActionsModel::Action> actions{
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
if (targetRoom) {
|
||||
RoomManager::instance().enterRoom(dynamic_cast<NeoChatRoom *>(targetRoom));
|
||||
RoomManager::instance().resolveResource(targetRoom->id());
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
@@ -256,7 +256,7 @@ QList<ActionsModel::Action> actions{
|
||||
}
|
||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||
if (targetRoom) {
|
||||
RoomManager::instance().enterRoom(dynamic_cast<NeoChatRoom *>(targetRoom));
|
||||
RoomManager::instance().resolveResource(targetRoom->id());
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
|
||||
64
src/models/linemodel.cpp
Normal file
64
src/models/linemodel.cpp
Normal file
@@ -0,0 +1,64 @@
|
||||
// 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 "linemodel.h"
|
||||
|
||||
LineModel::LineModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QQuickTextDocument *LineModel::document() const
|
||||
{
|
||||
return m_document;
|
||||
}
|
||||
|
||||
void LineModel::setDocument(QQuickTextDocument *document)
|
||||
{
|
||||
if (document == m_document) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_document = document;
|
||||
Q_EMIT documentChanged();
|
||||
|
||||
resetModel();
|
||||
}
|
||||
|
||||
QVariant LineModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &row = index.row();
|
||||
if (row < 0 || row > rowCount()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == LineHeightRole) {
|
||||
auto textDoc = m_document->textDocument();
|
||||
return int(textDoc->documentLayout()->blockBoundingRect(textDoc->findBlockByNumber(row)).height());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int LineModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
if (m_document == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return m_document->textDocument()->blockCount();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> LineModel::roleNames() const
|
||||
{
|
||||
return {{LineHeightRole, "docLineHeight"}};
|
||||
}
|
||||
|
||||
void LineModel::resetModel()
|
||||
{
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
80
src/models/linemodel.h
Normal file
80
src/models/linemodel.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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 <QAbstractTextDocumentLayout>
|
||||
#include <QQmlEngine>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextBlock>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
/**
|
||||
* @class LineModel
|
||||
*
|
||||
* A model to provide line info for a QQuickTextDocument.
|
||||
*
|
||||
* @sa QQuickTextDocument
|
||||
*/
|
||||
class LineModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The QQuickTextDocument that is being handled.
|
||||
*/
|
||||
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
LineHeightRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit LineModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] QQuickTextDocument *document() const;
|
||||
void setDocument(QQuickTextDocument *document);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) 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;
|
||||
|
||||
/**
|
||||
* @brief Reset the model.
|
||||
*
|
||||
* This needs to be called when the QQuickTextDocument container changes width
|
||||
* or height as this may change line heights due to wrapping.
|
||||
*
|
||||
* @sa QQuickTextDocument
|
||||
*/
|
||||
Q_INVOKABLE void resetModel();
|
||||
|
||||
Q_SIGNALS:
|
||||
void documentChanged();
|
||||
|
||||
private:
|
||||
QPointer<QQuickTextDocument> m_document = nullptr;
|
||||
};
|
||||
@@ -3,9 +3,9 @@
|
||||
|
||||
#include "mediamessagefiltermodel.h"
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "messagefiltermodel.h"
|
||||
|
||||
@@ -20,8 +20,8 @@ bool MediaMessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex
|
||||
{
|
||||
const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
|
||||
if (index.data(MessageEventModel::DelegateTypeRole).toInt() == DelegateType::Image
|
||||
|| index.data(MessageEventModel::DelegateTypeRole).toInt() == DelegateType::Video) {
|
||||
if (index.data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("image"))
|
||||
|| index.data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("video"))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -30,9 +30,9 @@ bool MediaMessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex
|
||||
QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == SourceRole) {
|
||||
if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == DelegateType::Image) {
|
||||
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::DelegateTypeRole).toInt() == DelegateType::Video) {
|
||||
} 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()) {
|
||||
@@ -48,7 +48,7 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("tempInfo")].toMap()[QStringLiteral("source")].toUrl();
|
||||
}
|
||||
if (role == TypeRole) {
|
||||
if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == DelegateType::Image) {
|
||||
if (mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("image"))) {
|
||||
return MediaType::Image;
|
||||
} else {
|
||||
return MediaType::Video;
|
||||
|
||||
245
src/models/messagecontentmodel.cpp
Normal file
245
src/models/messagecontentmodel.cpp
Normal file
@@ -0,0 +1,245 @@
|
||||
// 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 "messagecontentmodel.h"
|
||||
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
MessageContentModel::MessageContentModel(const Quotient::RoomEvent *event, NeoChatRoom *room)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
, m_event(event)
|
||||
{
|
||||
if (m_room != nullptr) {
|
||||
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_event->id() == serverEvent->id()) {
|
||||
beginResetModel();
|
||||
m_event = serverEvent;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_event->id() == newEvent->id()) {
|
||||
beginResetModel();
|
||||
m_event = newEvent;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::replyLoaded, this, [this](const QString &eventId, const QString &replyId) {
|
||||
Q_UNUSED(eventId)
|
||||
if (m_event != nullptr && m_room != nullptr) {
|
||||
const auto eventHandler = EventHandler(m_room, m_event);
|
||||
if (replyId == eventHandler.getReplyId()) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
m_components[0] = MessageComponentType::Reply;
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_event->id()) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_event->id()) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_event->id()) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
|
||||
if (m_event != nullptr && eventId == m_event->id()) {
|
||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||
}
|
||||
});
|
||||
connect(m_room->editCache(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
|
||||
if (m_event != nullptr && (oldEventId == m_event->id() || newEventId == m_event->id())) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
if (LinkPreviewer::hasPreviewableLinks(event)) {
|
||||
m_linkPreviewer = new LinkPreviewer(m_room, event, this);
|
||||
|
||||
connect(m_linkPreviewer, &LinkPreviewer::loadedChanged, [this]() {
|
||||
if (m_linkPreviewer->loaded()) {
|
||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||
beginResetModel();
|
||||
m_components[m_components.size() - 1] = MessageComponentType::LinkPreview;
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateComponents();
|
||||
}
|
||||
|
||||
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
|
||||
|
||||
QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.row() >= rowCount()) {
|
||||
qDebug() << "MessageContentModel, something's wrong: index.row() >= rowCount()";
|
||||
return {};
|
||||
}
|
||||
|
||||
EventHandler eventHandler(m_room, m_event);
|
||||
|
||||
if (role == DisplayRole) {
|
||||
if (m_event->isRedacted()) {
|
||||
auto reason = m_event->redactedBecause()->reason();
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
||||
: i18n("<i>[This message was deleted: %1]</i>", m_event->redactedBecause()->reason());
|
||||
}
|
||||
return eventHandler.getRichBody();
|
||||
}
|
||||
if (role == ComponentTypeRole) {
|
||||
const auto component = m_components[index.row()];
|
||||
if (component == MessageComponentType::Text && !m_event->id().isEmpty() && m_room->editCache()->editId() == m_event->id()) {
|
||||
return MessageComponentType::Edit;
|
||||
}
|
||||
return component;
|
||||
}
|
||||
if (role == EventIdRole) {
|
||||
return eventHandler.getId();
|
||||
}
|
||||
if (role == AuthorRole) {
|
||||
return eventHandler.getAuthor(false);
|
||||
}
|
||||
if (role == MediaInfoRole) {
|
||||
return eventHandler.getMediaInfo();
|
||||
}
|
||||
if (role == FileTransferInfoRole) {
|
||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||
if (event->hasFileContent()) {
|
||||
return QVariant::fromValue(m_room->fileTransferInfo(event->id()));
|
||||
}
|
||||
}
|
||||
if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
|
||||
return QVariant::fromValue(m_room->fileTransferInfo(event->id()));
|
||||
}
|
||||
}
|
||||
if (role == LatitudeRole) {
|
||||
return eventHandler.getLatitude();
|
||||
}
|
||||
if (role == LongitudeRole) {
|
||||
return eventHandler.getLongitude();
|
||||
}
|
||||
if (role == AssetRole) {
|
||||
return eventHandler.getLocationAssetType();
|
||||
}
|
||||
if (role == PollHandlerRole) {
|
||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_event->id()));
|
||||
}
|
||||
if (role == IsReplyRole) {
|
||||
return eventHandler.hasReply();
|
||||
}
|
||||
if (role == ReplyComponentType) {
|
||||
return eventHandler.replyMessageComponentType();
|
||||
}
|
||||
if (role == ReplyEventIdRole) {
|
||||
return eventHandler.getReplyId();
|
||||
}
|
||||
if (role == ReplyAuthorRole) {
|
||||
return eventHandler.getReplyAuthor();
|
||||
}
|
||||
if (role == ReplyDisplayRole) {
|
||||
return eventHandler.getReplyRichBody();
|
||||
}
|
||||
if (role == ReplyMediaInfoRole) {
|
||||
return eventHandler.getReplyMediaInfo();
|
||||
}
|
||||
if (role == LinkPreviewerRole) {
|
||||
if (m_linkPreviewer != nullptr) {
|
||||
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewer);
|
||||
} else {
|
||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int MessageContentModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_components.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[DisplayRole] = "display";
|
||||
roles[ComponentTypeRole] = "componentType";
|
||||
roles[EventIdRole] = "eventId";
|
||||
roles[AuthorRole] = "author";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
roles[FileTransferInfoRole] = "fileTransferInfo";
|
||||
roles[LatitudeRole] = "latitude";
|
||||
roles[LongitudeRole] = "longitude";
|
||||
roles[AssetRole] = "asset";
|
||||
roles[PollHandlerRole] = "pollHandler";
|
||||
roles[IsReplyRole] = "isReply";
|
||||
roles[ReplyComponentType] = "replyComponentType";
|
||||
roles[ReplyEventIdRole] = "replyEventId";
|
||||
roles[ReplyAuthorRole] = "replyAuthor";
|
||||
roles[ReplyDisplayRole] = "replyDisplay";
|
||||
roles[ReplyMediaInfoRole] = "replyMediaInfo";
|
||||
roles[LinkPreviewerRole] = "linkPreviewer";
|
||||
return roles;
|
||||
}
|
||||
|
||||
void MessageContentModel::updateComponents()
|
||||
{
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
EventHandler eventHandler(m_room, m_event);
|
||||
if (eventHandler.hasReply()) {
|
||||
if (m_room->findInTimeline(eventHandler.getReplyId()) == m_room->historyEdge()) {
|
||||
m_components += MessageComponentType::ReplyLoad;
|
||||
m_room->loadReply(m_event->id(), eventHandler.getReplyId());
|
||||
} else {
|
||||
m_components += MessageComponentType::Reply;
|
||||
}
|
||||
}
|
||||
|
||||
m_components += eventHandler.messageComponentType();
|
||||
|
||||
if (m_linkPreviewer != nullptr) {
|
||||
if (m_linkPreviewer->loaded()) {
|
||||
m_components += MessageComponentType::LinkPreview;
|
||||
} else {
|
||||
m_components += MessageComponentType::LinkPreviewLoad;
|
||||
}
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
83
src/models/messagecontentmodel.h
Normal file
83
src/models/messagecontentmodel.h
Normal file
@@ -0,0 +1,83 @@
|
||||
// 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 <QQmlEngine>
|
||||
|
||||
#include "eventhandler.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecomponenttype.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
/**
|
||||
* @class MessageContentModel
|
||||
*
|
||||
* A model to visualise the components of a single RoomMessageEvent.
|
||||
*/
|
||||
class MessageContentModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
DisplayRole = Qt::DisplayRole, /**< The display text for the message. */
|
||||
ComponentTypeRole, /**< The type of component to visualise the message. */
|
||||
EventIdRole, /**< The matrix event ID of the event. */
|
||||
AuthorRole, /**< The author of the event. */
|
||||
MediaInfoRole, /**< The media info for the event. */
|
||||
FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */
|
||||
LatitudeRole, /**< Latitude for a location event. */
|
||||
LongitudeRole, /**< Longitude for a location event. */
|
||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||
PollHandlerRole, /**< The PollHandler for the event, if any. */
|
||||
|
||||
IsReplyRole, /**< Is the message a reply to another event. */
|
||||
ReplyComponentType, /**< The type of component to visualise the reply message. */
|
||||
ReplyEventIdRole, /**< The matrix ID of the message that was replied to. */
|
||||
ReplyAuthorRole, /**< The author of the event that was replied to. */
|
||||
ReplyDisplayRole, /**< The body of the message that was replied to. */
|
||||
ReplyMediaInfoRole, /**< The media info of the message that was replied to. */
|
||||
|
||||
LinkPreviewerRole, /**< The link preview details. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit MessageContentModel(const Quotient::RoomEvent *event, NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @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;
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
const Quotient::RoomEvent *m_event = nullptr;
|
||||
|
||||
QVector<MessageComponentType::Type> m_components;
|
||||
void updateComponents();
|
||||
|
||||
LinkPreviewer *m_linkPreviewer = nullptr;
|
||||
};
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "messageeventmodel_logging.h"
|
||||
|
||||
#include "neochatconfig.h"
|
||||
@@ -10,6 +9,7 @@
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/rooms.h>
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include "enums/delegatetype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecontentmodel.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
@@ -31,7 +33,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[DelegateTypeRole] = "delegateType";
|
||||
roles[PlainText] = "plainText";
|
||||
roles[EventIdRole] = "eventId";
|
||||
roles[TimeRole] = "time";
|
||||
roles[TimeStringRole] = "timeString";
|
||||
@@ -40,15 +41,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[HighlightRole] = "isHighlighted";
|
||||
roles[SpecialMarksRole] = "marks";
|
||||
roles[ProgressInfoRole] = "progressInfo";
|
||||
roles[ShowLinkPreviewRole] = "showLinkPreview";
|
||||
roles[LinkPreviewRole] = "linkPreview";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
roles[IsReplyRole] = "isReply";
|
||||
roles[ReplyAuthor] = "replyAuthor";
|
||||
roles[ReplyIdRole] = "replyId";
|
||||
roles[ReplyDelegateTypeRole] = "replyDelegateType";
|
||||
roles[ReplyDisplayRole] = "replyDisplay";
|
||||
roles[ReplyMediaInfoRole] = "replyMediaInfo";
|
||||
roles[IsThreadedRole] = "isThreaded";
|
||||
roles[ThreadRootRole] = "threadRoot";
|
||||
roles[ShowAuthorRole] = "showAuthor";
|
||||
@@ -64,10 +56,8 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[IsRedactedRole] = "isRedacted";
|
||||
roles[GenericDisplayRole] = "genericDisplay";
|
||||
roles[IsPendingRole] = "isPending";
|
||||
roles[LatitudeRole] = "latitude";
|
||||
roles[LongitudeRole] = "longitude";
|
||||
roles[AssetRole] = "asset";
|
||||
roles[PollHandlerRole] = "pollHandler";
|
||||
roles[ContentModelRole] = "contentModel";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -96,7 +86,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
beginResetModel();
|
||||
if (m_currentRoom) {
|
||||
m_currentRoom->disconnect(this);
|
||||
m_linkPreviewers.clear();
|
||||
m_reactionModels.clear();
|
||||
}
|
||||
|
||||
@@ -119,14 +108,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
room->getPreviousContent(50);
|
||||
}
|
||||
lastReadEventId = room->lastFullyReadEventId();
|
||||
connect(m_currentRoom, &NeoChatRoom::replyLoaded, this, [this](const auto &eventId, const auto &replyId) {
|
||||
Q_UNUSED(replyId);
|
||||
auto row = eventIdToRow(eventId);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
Q_EMIT dataChanged(index(row, 0), index(row, 0), {ReplyDelegateTypeRole, ReplyDisplayRole, ReplyMediaInfoRole, ReplyAuthor});
|
||||
});
|
||||
|
||||
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &&event : events) {
|
||||
@@ -238,7 +219,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
moveReadMarker(toEventId);
|
||||
});
|
||||
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
||||
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
|
||||
const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent);
|
||||
if (message != nullptr) {
|
||||
createEventObjects(message);
|
||||
@@ -265,10 +245,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole, ExcessReadMarkersRole});
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
|
||||
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [this] {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
@@ -439,8 +415,6 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
|
||||
}
|
||||
}
|
||||
|
||||
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
|
||||
|
||||
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
|
||||
@@ -481,9 +455,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex());
|
||||
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
||||
|
||||
EventHandler eventHandler;
|
||||
eventHandler.setRoom(m_currentRoom);
|
||||
eventHandler.setEvent(&evt);
|
||||
EventHandler eventHandler(m_currentRoom, &evt);
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (evt.isRedacted()) {
|
||||
@@ -494,16 +466,24 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return eventHandler.getRichBody();
|
||||
}
|
||||
|
||||
if (role == ContentModelRole) {
|
||||
if (!evt.isStateEvent()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(&evt, m_currentRoom));
|
||||
}
|
||||
if (evt.isStateEvent()) {
|
||||
if (evt.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(&evt, m_currentRoom));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == GenericDisplayRole) {
|
||||
return eventHandler.getGenericBody();
|
||||
}
|
||||
|
||||
if (role == PlainText) {
|
||||
return eventHandler.getPlainBody();
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
return eventHandler.getDelegateType();
|
||||
return DelegateType::typeForEvent(evt);
|
||||
}
|
||||
|
||||
if (role == AuthorRole) {
|
||||
@@ -561,46 +541,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated);
|
||||
}
|
||||
|
||||
if (role == ShowLinkPreviewRole) {
|
||||
return m_linkPreviewers.contains(evt.id());
|
||||
}
|
||||
|
||||
if (role == LinkPreviewRole) {
|
||||
if (m_linkPreviewers.contains(evt.id())) {
|
||||
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewers[evt.id()].data());
|
||||
} else {
|
||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||
}
|
||||
}
|
||||
|
||||
if (role == MediaInfoRole) {
|
||||
return eventHandler.getMediaInfo();
|
||||
}
|
||||
|
||||
if (role == IsReplyRole) {
|
||||
return eventHandler.hasReply();
|
||||
}
|
||||
|
||||
if (role == ReplyIdRole) {
|
||||
return eventHandler.getReplyId();
|
||||
}
|
||||
|
||||
if (role == ReplyDelegateTypeRole) {
|
||||
return eventHandler.getReplyDelegateType();
|
||||
}
|
||||
|
||||
if (role == ReplyAuthor) {
|
||||
return eventHandler.getReplyAuthor();
|
||||
}
|
||||
|
||||
if (role == ReplyDisplayRole) {
|
||||
return eventHandler.getReplyRichBody();
|
||||
}
|
||||
|
||||
if (role == ReplyMediaInfoRole) {
|
||||
return eventHandler.getReplyMediaInfo();
|
||||
}
|
||||
|
||||
if (role == IsThreadedRole) {
|
||||
return eventHandler.isThreaded();
|
||||
}
|
||||
@@ -644,18 +584,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == LatitudeRole) {
|
||||
return eventHandler.getLatitude();
|
||||
}
|
||||
|
||||
if (role == LongitudeRole) {
|
||||
return eventHandler.getLongitude();
|
||||
}
|
||||
|
||||
if (role == AssetRole) {
|
||||
return eventHandler.getLocationAssetType();
|
||||
}
|
||||
|
||||
if (role == ReadMarkersRole) {
|
||||
return eventHandler.getReadMarkers();
|
||||
}
|
||||
@@ -705,8 +633,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return row < static_cast<int>(m_currentRoom->pendingEvents().size());
|
||||
}
|
||||
|
||||
if (role == PollHandlerRole) {
|
||||
return QVariant::fromValue<PollHandler *>(m_currentRoom->poll(evt.id()));
|
||||
if (role == MediaInfoRole) {
|
||||
return eventHandler.getMediaInfo();
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -726,16 +654,6 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
|
||||
{
|
||||
auto eventId = event->id();
|
||||
|
||||
if (m_linkPreviewers.contains(eventId)) {
|
||||
if (!LinkPreviewer::hasPreviewableLinks(event)) {
|
||||
m_linkPreviewers.remove(eventId);
|
||||
}
|
||||
} else {
|
||||
if (LinkPreviewer::hasPreviewableLinks(event)) {
|
||||
m_linkPreviewers[eventId] = QSharedPointer<LinkPreviewer>(new LinkPreviewer(m_currentRoom, 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)) {
|
||||
@@ -763,7 +681,7 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
|
||||
bool MessageEventModel::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::ApplicationPaletteChange) {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyAuthor, ReadMarkersRole});
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReadMarkersRole});
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,6 @@ public:
|
||||
*/
|
||||
enum EventRoles {
|
||||
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||
PlainText, /**< Plain text representation of the message. */
|
||||
EventIdRole, /**< The matrix event ID of the event. */
|
||||
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
|
||||
TimeStringRole, /**< The timestamp for when the event was sent as a string (in QLocale::ShortFormat). */
|
||||
@@ -50,18 +49,9 @@ public:
|
||||
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||
ProgressInfoRole, /**< Progress info when downloading files. */
|
||||
GenericDisplayRole, /**< A generic string based upon the message type. */
|
||||
|
||||
ShowLinkPreviewRole, /**< Whether a link preview should be shown. */
|
||||
LinkPreviewRole, /**< The link preview details. */
|
||||
|
||||
MediaInfoRole, /**< The media info for the event. */
|
||||
|
||||
IsReplyRole, /**< Is the message a reply to another event. */
|
||||
ReplyAuthor, /**< The author of the event that was replied to. */
|
||||
ReplyIdRole, /**< The matrix ID of the message that was replied to. */
|
||||
ReplyDelegateTypeRole, /**< The delegate type of the message that was replied to. */
|
||||
ReplyDisplayRole, /**< The body of the message that was replied to. */
|
||||
ReplyMediaInfoRole, /**< The media info of the message that was replied to. */
|
||||
ContentModelRole, /**< The MessageContentModel for the event. */
|
||||
|
||||
IsThreadedRole,
|
||||
ThreadRootRole,
|
||||
@@ -80,10 +70,6 @@ public:
|
||||
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
|
||||
IsRedactedRole, /**< Whether an event has been deleted. */
|
||||
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
|
||||
LatitudeRole, /**< Latitude for a location event. */
|
||||
LongitudeRole, /**< Longitude for a location event. */
|
||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||
PollHandlerRole, /**< The PollHandler for the event, if any. */
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
@@ -135,7 +121,6 @@ private:
|
||||
bool movingEvent = false;
|
||||
KFormat m_format;
|
||||
|
||||
QMap<QString, QSharedPointer<LinkPreviewer>> m_linkPreviewers;
|
||||
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
|
||||
|
||||
[[nodiscard]] int timelineBaseIndex() const;
|
||||
|
||||
@@ -127,9 +127,8 @@ QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||
chunks.removeDuplicates();
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
|
||||
QString chunksText;
|
||||
|
||||
@@ -122,9 +122,7 @@ void NotificationsModel::loadData()
|
||||
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == QStringLiteral("mxc") ? avatar : QUrl();
|
||||
|
||||
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
|
||||
EventHandler eventHandler;
|
||||
eventHandler.setRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
eventHandler.setEvent(roomEvent);
|
||||
EventHandler eventHandler(dynamic_cast<NeoChatRoom *>(room), roomEvent);
|
||||
beginInsertRows({}, m_notifications.length(), m_notifications.length());
|
||||
m_notifications += Notification{
|
||||
.roomId = notification.roomId,
|
||||
|
||||
@@ -23,7 +23,7 @@ ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, const NeoC
|
||||
{
|
||||
if (m_event != nullptr && m_room != nullptr) {
|
||||
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
|
||||
if (m_event->id() == eventId) {
|
||||
if (m_event && m_event->id() == eventId) {
|
||||
updateReactions();
|
||||
}
|
||||
});
|
||||
@@ -80,7 +80,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
"%2 reacted with %3",
|
||||
reaction.authors.count(),
|
||||
text,
|
||||
reactionText(reaction.reaction));
|
||||
m_shortcodes.contains(reaction.reaction) ? m_shortcodes[reaction.reaction] : reactionText(reaction.reaction));
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -111,6 +111,7 @@ void ReactionModel::updateReactions()
|
||||
beginResetModel();
|
||||
|
||||
m_reactions.clear();
|
||||
m_shortcodes.clear();
|
||||
|
||||
const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
|
||||
if (annotations.isEmpty()) {
|
||||
@@ -125,6 +126,9 @@ void ReactionModel::updateReactions()
|
||||
}
|
||||
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
|
||||
reactions[e->key()].append(m_room->user(e->senderId()));
|
||||
if (e->contentJson()[QStringLiteral("shortcode")].toString().length()) {
|
||||
m_shortcodes[e->key()] = e->contentJson()[QStringLiteral("shortcode")].toString().toHtmlEscaped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +162,18 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
QString ReactionModel::reactionText(const QString &text)
|
||||
QString ReactionModel::reactionText(QString text) const
|
||||
{
|
||||
text = text.toHtmlEscaped();
|
||||
if (text.startsWith(QStringLiteral("mxc://"))) {
|
||||
static QFont font;
|
||||
static int size = font.pixelSize();
|
||||
if (size == -1) {
|
||||
size = font.pointSizeF() * 1.333;
|
||||
}
|
||||
return QStringLiteral("<img src=\"%1\" width=\"%2\" height=\"%2\">")
|
||||
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
|
||||
}
|
||||
const auto isEmoji = [](const QString &text) {
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
|
||||
@@ -71,8 +71,9 @@ private:
|
||||
const NeoChatRoom *m_room;
|
||||
const Quotient::RoomMessageEvent *m_event;
|
||||
QList<Reaction> m_reactions;
|
||||
QMap<QString, QString> m_shortcodes;
|
||||
|
||||
void updateReactions();
|
||||
static QString reactionText(const QString &text);
|
||||
QString reactionText(QString text) const;
|
||||
};
|
||||
Q_DECLARE_METATYPE(ReactionModel *)
|
||||
|
||||
@@ -28,11 +28,6 @@ Q_DECLARE_METATYPE(Quotient::JoinState)
|
||||
RoomListModel::RoomListModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
const auto collapsedSections = NeoChatConfig::collapsedSections();
|
||||
for (auto collapsedSection : collapsedSections) {
|
||||
m_categoryVisibility[collapsedSection] = false;
|
||||
}
|
||||
|
||||
connect(this, &RoomListModel::highlightCountChanged, this, [this]() {
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
@@ -298,30 +293,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
return room->topic();
|
||||
}
|
||||
if (role == CategoryRole) {
|
||||
if (room->joinState() == JoinState::Invite) {
|
||||
return NeoChatRoomType::Invited;
|
||||
}
|
||||
if (room->isFavourite()) {
|
||||
return NeoChatRoomType::Favorite;
|
||||
}
|
||||
if (room->isLowPriority()) {
|
||||
return NeoChatRoomType::Deprioritized;
|
||||
}
|
||||
if (room->isDirectChat()) {
|
||||
return NeoChatRoomType::Direct;
|
||||
}
|
||||
const RoomCreateEvent *creationEvent = room->creation();
|
||||
if (!creationEvent) {
|
||||
return NeoChatRoomType::Normal;
|
||||
}
|
||||
QJsonObject contentJson = creationEvent->contentJson();
|
||||
QJsonObject::const_iterator typeIter = contentJson.find("type"_ls);
|
||||
if (typeIter != contentJson.end()) {
|
||||
if (typeIter.value().toString() == "m.space"_ls) {
|
||||
return NeoChatRoomType::Space;
|
||||
}
|
||||
}
|
||||
return NeoChatRoomType::Normal;
|
||||
return NeoChatRoomType::typeForRoom(room);
|
||||
}
|
||||
if (role == NotificationCountRole) {
|
||||
return room->notificationCount();
|
||||
@@ -341,16 +313,11 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
if (role == CurrentRoomRole) {
|
||||
return QVariant::fromValue(room);
|
||||
}
|
||||
if (role == CategoryVisibleRole) {
|
||||
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
|
||||
}
|
||||
if (role == SubtitleTextRole) {
|
||||
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||
return QString();
|
||||
}
|
||||
EventHandler eventHandler;
|
||||
eventHandler.setRoom(room);
|
||||
eventHandler.setEvent(room->lastEvent());
|
||||
EventHandler eventHandler(room, room->lastEvent());
|
||||
return eventHandler.subtitleText();
|
||||
}
|
||||
if (role == AvatarImageRole) {
|
||||
@@ -363,7 +330,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
return room->isSpace();
|
||||
}
|
||||
if (role == IsChildSpaceRole) {
|
||||
return SpaceHierarchyCache::instance().isChildSpace(room->id());
|
||||
return SpaceHierarchyCache::instance().isChild(room->id());
|
||||
}
|
||||
if (role == ReplacementIdRole) {
|
||||
return room->successorId();
|
||||
@@ -399,7 +366,6 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
roles[LastActiveTimeRole] = "lastActiveTime";
|
||||
roles[JoinStateRole] = "joinState";
|
||||
roles[CurrentRoomRole] = "currentRoom";
|
||||
roles[CategoryVisibleRole] = "categoryVisible";
|
||||
roles[SubtitleTextRole] = "subtitleText";
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[RoomIdRole] = "roomId";
|
||||
@@ -408,67 +374,6 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
return roles;
|
||||
}
|
||||
|
||||
QString RoomListModel::categoryName(int category)
|
||||
{
|
||||
switch (category) {
|
||||
case NeoChatRoomType::Invited:
|
||||
return i18n("Invited");
|
||||
case NeoChatRoomType::Favorite:
|
||||
return i18n("Favorite");
|
||||
case NeoChatRoomType::Direct:
|
||||
return i18n("Friends");
|
||||
case NeoChatRoomType::Normal:
|
||||
return i18n("Normal");
|
||||
case NeoChatRoomType::Deprioritized:
|
||||
return i18n("Low priority");
|
||||
case NeoChatRoomType::Space:
|
||||
return i18n("Spaces");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QString RoomListModel::categoryIconName(int category)
|
||||
{
|
||||
switch (category) {
|
||||
case NeoChatRoomType::Invited:
|
||||
return QStringLiteral("user-invisible");
|
||||
case NeoChatRoomType::Favorite:
|
||||
return QStringLiteral("favorite");
|
||||
case NeoChatRoomType::Direct:
|
||||
return QStringLiteral("dialog-messages");
|
||||
case NeoChatRoomType::Normal:
|
||||
return QStringLiteral("group");
|
||||
case NeoChatRoomType::Deprioritized:
|
||||
return QStringLiteral("object-order-lower");
|
||||
case NeoChatRoomType::Space:
|
||||
return QStringLiteral("group");
|
||||
default:
|
||||
return QStringLiteral("tools-report-bug");
|
||||
}
|
||||
}
|
||||
|
||||
void RoomListModel::setCategoryVisible(int category, bool visible)
|
||||
{
|
||||
beginResetModel();
|
||||
auto collapsedSections = NeoChatConfig::collapsedSections();
|
||||
if (visible) {
|
||||
collapsedSections.removeAll(category);
|
||||
} else {
|
||||
collapsedSections.push_back(category);
|
||||
}
|
||||
NeoChatConfig::setCollapsedSections(collapsedSections);
|
||||
NeoChatConfig::self()->save();
|
||||
|
||||
m_categoryVisibility[category] = visible;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool RoomListModel::categoryVisible(int category) const
|
||||
{
|
||||
return m_categoryVisibility.value(category, true);
|
||||
}
|
||||
|
||||
NeoChatRoom *RoomListModel::roomByAliasOrId(const QString &aliasOrId)
|
||||
{
|
||||
for (const auto &room : std::as_const(m_rooms)) {
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
namespace Quotient
|
||||
@@ -14,27 +16,6 @@ class Connection;
|
||||
class Room;
|
||||
}
|
||||
|
||||
class NeoChatRoomType : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the room list categories a room can be assigned.
|
||||
*/
|
||||
enum Types {
|
||||
Invited = 1, /**< The user has been invited to the room. */
|
||||
Favorite, /**< The room is set as a favourite. */
|
||||
Direct, /**< The room is a direct chat. */
|
||||
Normal, /**< The default category for a joined room. */
|
||||
Deprioritized, /**< The room is set as low priority. */
|
||||
Space, /**< The room is a space. */
|
||||
};
|
||||
Q_ENUM(Types)
|
||||
};
|
||||
|
||||
/**
|
||||
* @class RoomListModel
|
||||
*
|
||||
@@ -70,7 +51,6 @@ public:
|
||||
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
|
||||
JoinStateRole, /**< The local user's join state in the room. */
|
||||
CurrentRoomRole, /**< The room object for the room. */
|
||||
CategoryVisibleRole, /**< If the room's category is visible. */
|
||||
SubtitleTextRole, /**< The text to show as the room subtitle. */
|
||||
AvatarImageRole, /**< The room avatar as an image. */
|
||||
RoomIdRole, /**< The room matrix ID. */
|
||||
@@ -116,30 +96,6 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] NeoChatRoom *roomAt(int row) const;
|
||||
|
||||
/**
|
||||
* @brief Return a string to represent the given room category.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] static QString categoryName(int category);
|
||||
|
||||
/**
|
||||
* @brief Return a string with the name of the given room category icon.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] static QString categoryIconName(int category);
|
||||
|
||||
/**
|
||||
* @brief Set whether a given category should be visible or not.
|
||||
*
|
||||
* @param category the NeoChatRoomType::Types value for the category (it's an
|
||||
* int due to the pain of Q_INVOKABLES and cpp enums).
|
||||
* @param visible true if the category should be visible, false if not.
|
||||
*/
|
||||
Q_INVOKABLE void setCategoryVisible(int category, bool visible);
|
||||
|
||||
/**
|
||||
* @brief Return whether a room category is set to be visible.
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] bool categoryVisible(int category) const;
|
||||
|
||||
/**
|
||||
* @brief Return the model row for the given room.
|
||||
*/
|
||||
@@ -165,8 +121,6 @@ private:
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
QList<NeoChatRoom *> m_rooms;
|
||||
|
||||
QMap<int, bool> m_categoryVisibility;
|
||||
|
||||
int m_notificationCount = 0;
|
||||
int m_highlightCount = 0;
|
||||
QString m_activeSpaceId;
|
||||
|
||||
323
src/models/roomtreemodel.cpp
Normal file
323
src/models/roomtreemodel.cpp
Normal file
@@ -0,0 +1,323 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "roomtreemodel.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "eventhandler.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroomtype.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
RoomTreeModel::RoomTreeModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
initializeCategories();
|
||||
}
|
||||
|
||||
void RoomTreeModel::initializeCategories()
|
||||
{
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
for (const auto &room : m_rooms[key]) {
|
||||
room->disconnect(this);
|
||||
}
|
||||
}
|
||||
m_rooms.clear();
|
||||
for (int i = 0; i < 8; i++) {
|
||||
m_rooms[NeoChatRoomType::Types(i)] = {};
|
||||
}
|
||||
}
|
||||
|
||||
void RoomTreeModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (m_connection == connection) {
|
||||
return;
|
||||
}
|
||||
disconnect(m_connection.get(), nullptr, this, nullptr);
|
||||
m_connection = connection;
|
||||
beginResetModel();
|
||||
initializeCategories();
|
||||
endResetModel();
|
||||
connect(connection, &Connection::newRoom, this, &RoomTreeModel::newRoom);
|
||||
connect(connection, &Connection::leftRoom, this, &RoomTreeModel::leftRoom);
|
||||
|
||||
for (const auto &room : m_connection->allRooms()) {
|
||||
newRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
}
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
void RoomTreeModel::newRoom(Room *r)
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(r);
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
beginInsertRows(index(type, 0), m_rooms[type].size(), m_rooms[type].size());
|
||||
m_rooms[type].append(room);
|
||||
connectRoomSignals(room);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void RoomTreeModel::leftRoom(Room *r)
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(r);
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
auto row = m_rooms[type].indexOf(room);
|
||||
if (row == -1) {
|
||||
return;
|
||||
}
|
||||
beginRemoveRows(index(type, 0), row, row);
|
||||
m_rooms[type][row]->disconnect(this);
|
||||
m_rooms[type].removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void RoomTreeModel::moveRoom(Quotient::Room *room)
|
||||
{
|
||||
// We can't assume the type as it has changed so currently the return of
|
||||
// NeoChatRoomType::typeForRoom doesn't match it's current location. So find the room.
|
||||
NeoChatRoomType::Types oldType;
|
||||
int oldRow = -1;
|
||||
for (const auto &key : m_rooms.keys()) {
|
||||
if (m_rooms[key].contains(room)) {
|
||||
oldType = key;
|
||||
oldRow = m_rooms[key].indexOf(room);
|
||||
}
|
||||
}
|
||||
|
||||
if (oldRow == -1) {
|
||||
return;
|
||||
}
|
||||
const auto newType = NeoChatRoomType::typeForRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
if (newType == oldType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto oldParent = index(oldType, 0, {});
|
||||
const auto newParent = index(newType, 0, {});
|
||||
// HACK: We're doing this as a remove then insert because moving doesn't work
|
||||
// properly with DelegateChooser for whatever reason.
|
||||
beginRemoveRows(oldParent, oldRow, oldRow);
|
||||
m_rooms[oldType].removeAt(oldRow);
|
||||
endRemoveRows();
|
||||
beginInsertRows(newParent, m_rooms[newType].size(), m_rooms[newType].size());
|
||||
m_rooms[newType].append(dynamic_cast<NeoChatRoom *>(room));
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void RoomTreeModel::connectRoomSignals(NeoChatRoom *room)
|
||||
{
|
||||
connect(room, &Room::displaynameChanged, this, [this, room] {
|
||||
refreshRoomRoles(room, {DisplayNameRole});
|
||||
});
|
||||
connect(room, &Room::unreadStatsChanged, this, [this, room] {
|
||||
refreshRoomRoles(room, {NotificationCountRole, HighlightCountRole});
|
||||
});
|
||||
connect(room, &Room::avatarChanged, this, [this, room] {
|
||||
refreshRoomRoles(room, {AvatarRole});
|
||||
});
|
||||
connect(room, &Room::tagsChanged, this, [this, room] {
|
||||
moveRoom(room);
|
||||
});
|
||||
connect(room, &Room::joinStateChanged, this, [this, room] {
|
||||
refreshRoomRoles(room);
|
||||
});
|
||||
connect(room, &Room::addedMessages, this, [this, room] {
|
||||
refreshRoomRoles(room, {SubtitleTextRole, LastActiveTimeRole});
|
||||
});
|
||||
connect(room, &Room::pendingEventMerged, this, [this, room] {
|
||||
refreshRoomRoles(room, {SubtitleTextRole});
|
||||
});
|
||||
}
|
||||
|
||||
void RoomTreeModel::refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles)
|
||||
{
|
||||
const auto roomType = NeoChatRoomType::typeForRoom(room);
|
||||
const auto it = std::find(m_rooms[roomType].begin(), m_rooms[roomType].end(), room);
|
||||
if (it == m_rooms[roomType].end()) {
|
||||
qCritical() << "Room" << room->id() << "not found in the room list";
|
||||
return;
|
||||
}
|
||||
const auto parentIndex = index(roomType, 0, {});
|
||||
const auto idx = index(it - m_rooms[roomType].begin(), 0, parentIndex);
|
||||
Q_EMIT dataChanged(idx, idx, roles);
|
||||
}
|
||||
|
||||
NeoChatConnection *RoomTreeModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
int RoomTreeModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return 1;
|
||||
}
|
||||
|
||||
int RoomTreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (!parent.isValid()) {
|
||||
return m_rooms.keys().size();
|
||||
}
|
||||
if (!parent.parent().isValid()) {
|
||||
return m_rooms.values()[parent.row()].size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QModelIndex RoomTreeModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.internalPointer()) {
|
||||
return {};
|
||||
}
|
||||
return this->index(NeoChatRoomType::typeForRoom(static_cast<NeoChatRoom *>(index.internalPointer())), 0, QModelIndex());
|
||||
}
|
||||
|
||||
QModelIndex RoomTreeModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!parent.isValid()) {
|
||||
return createIndex(row, column, nullptr);
|
||||
}
|
||||
if (row >= rowCount(parent)) {
|
||||
return {};
|
||||
}
|
||||
return createIndex(row, column, m_rooms[NeoChatRoomType::Types(parent.row())][row]);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> RoomTreeModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[DisplayNameRole] = "displayName";
|
||||
roles[AvatarRole] = "avatar";
|
||||
roles[CanonicalAliasRole] = "canonicalAlias";
|
||||
roles[TopicRole] = "topic";
|
||||
roles[CategoryRole] = "category";
|
||||
roles[NotificationCountRole] = "notificationCount";
|
||||
roles[HighlightCountRole] = "highlightCount";
|
||||
roles[LastActiveTimeRole] = "lastActiveTime";
|
||||
roles[JoinStateRole] = "joinState";
|
||||
roles[CurrentRoomRole] = "currentRoom";
|
||||
roles[SubtitleTextRole] = "subtitleText";
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[RoomIdRole] = "roomId";
|
||||
roles[IsChildSpaceRole] = "isChildSpace";
|
||||
roles[IsDirectChat] = "isDirectChat";
|
||||
roles[DelegateTypeRole] = "delegateType";
|
||||
roles[IconRole] = "icon";
|
||||
return roles;
|
||||
}
|
||||
|
||||
// TODO room type changes
|
||||
QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (!index.parent().isValid()) {
|
||||
if (role == DisplayNameRole) {
|
||||
return NeoChatRoomType::typeName(index.row());
|
||||
}
|
||||
if (role == DelegateTypeRole) {
|
||||
if (index.row() == NeoChatRoomType::Search) {
|
||||
return QStringLiteral("search");
|
||||
}
|
||||
if (index.row() == NeoChatRoomType::AddDirect) {
|
||||
return QStringLiteral("addDirect");
|
||||
}
|
||||
return QStringLiteral("section");
|
||||
}
|
||||
if (role == IconRole) {
|
||||
return NeoChatRoomType::typeIconName(index.row());
|
||||
}
|
||||
if (role == CategoryRole) {
|
||||
return index.row();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
const auto room = m_rooms.values()[index.parent().row()][index.row()].get();
|
||||
Q_ASSERT(room);
|
||||
|
||||
if (role == DisplayNameRole) {
|
||||
return room->displayName();
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
return room->avatarMediaId();
|
||||
}
|
||||
if (role == CanonicalAliasRole) {
|
||||
return room->canonicalAlias();
|
||||
}
|
||||
if (role == TopicRole) {
|
||||
return room->topic();
|
||||
}
|
||||
if (role == CategoryRole) {
|
||||
return NeoChatRoomType::typeForRoom(room);
|
||||
}
|
||||
if (role == NotificationCountRole) {
|
||||
return room->notificationCount();
|
||||
}
|
||||
if (role == HighlightCountRole) {
|
||||
return room->highlightCount();
|
||||
}
|
||||
if (role == LastActiveTimeRole) {
|
||||
return room->lastActiveTime();
|
||||
}
|
||||
if (role == JoinStateRole) {
|
||||
if (!room->successorId().isEmpty()) {
|
||||
return QStringLiteral("upgraded");
|
||||
}
|
||||
return QVariant::fromValue(room->joinState());
|
||||
}
|
||||
if (role == CurrentRoomRole) {
|
||||
return QVariant::fromValue(room);
|
||||
}
|
||||
if (role == SubtitleTextRole) {
|
||||
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||
return QString();
|
||||
}
|
||||
EventHandler eventHandler(room, room->lastEvent());
|
||||
return eventHandler.subtitleText();
|
||||
}
|
||||
if (role == AvatarImageRole) {
|
||||
return room->avatar(128);
|
||||
}
|
||||
if (role == RoomIdRole) {
|
||||
return room->id();
|
||||
}
|
||||
if (role == IsSpaceRole) {
|
||||
return room->isSpace();
|
||||
}
|
||||
if (role == IsChildSpaceRole) {
|
||||
return SpaceHierarchyCache::instance().isChild(room->id());
|
||||
}
|
||||
if (role == ReplacementIdRole) {
|
||||
return room->successorId();
|
||||
}
|
||||
if (role == IsDirectChat) {
|
||||
return room->isDirectChat();
|
||||
}
|
||||
if (role == DelegateTypeRole) {
|
||||
return QStringLiteral("normal");
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex RoomTreeModel::indexForRoom(NeoChatRoom *room) const
|
||||
{
|
||||
if (room == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto type = NeoChatRoomType::typeForRoom(room);
|
||||
auto row = m_rooms[type].indexOf(room);
|
||||
if (row >= 0) {
|
||||
return index(row, 0, index(type, 0));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
#include "moc_roomtreemodel.cpp"
|
||||
94
src/models/roomtreemodel.h
Normal file
94
src/models/roomtreemodel.h
Normal file
@@ -0,0 +1,94 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QPointer>
|
||||
|
||||
#include "enums/neochatroomtype.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Room;
|
||||
}
|
||||
|
||||
class NeoChatConnection;
|
||||
class NeoChatRoom;
|
||||
|
||||
class RoomTreeModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
DisplayNameRole = Qt::DisplayRole, /**< The display name of the room. */
|
||||
AvatarRole, /**< The source URL for the room's avatar. */
|
||||
CanonicalAliasRole, /**< The room canonical alias. */
|
||||
TopicRole, /**< The room topic. */
|
||||
CategoryRole, /**< The room category, e.g favourite. */
|
||||
NotificationCountRole, /**< The number of notifications in the room. */
|
||||
HighlightCountRole, /**< The number of highlighted messages in the room. */
|
||||
LastActiveTimeRole, /**< The timestamp of the last event sent in the room. */
|
||||
JoinStateRole, /**< The local user's join state in the room. */
|
||||
CurrentRoomRole, /**< The room object for the room. */
|
||||
SubtitleTextRole, /**< The text to show as the room subtitle. */
|
||||
AvatarImageRole, /**< The room avatar as an image. */
|
||||
RoomIdRole, /**< The room matrix ID. */
|
||||
IsSpaceRole, /**< Whether the room is a space. */
|
||||
IsChildSpaceRole, /**< Whether this space is a child of a different space. */
|
||||
ReplacementIdRole, /**< The room id of the room replacing this one, if any. */
|
||||
IsDirectChat, /**< Whether this room is a direct chat. */
|
||||
DelegateTypeRole,
|
||||
IconRole,
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
explicit RoomTreeModel(QObject *parent = nullptr);
|
||||
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
NeoChatConnection *connection() const;
|
||||
|
||||
/**
|
||||
* @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 Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
Q_INVOKABLE QModelIndex indexForRoom(NeoChatRoom *room) const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection = nullptr;
|
||||
QMap<NeoChatRoomType::Types, QList<QPointer<NeoChatRoom>>> m_rooms;
|
||||
|
||||
void initializeCategories();
|
||||
void connectRoomSignals(NeoChatRoom *room);
|
||||
|
||||
void newRoom(Quotient::Room *room);
|
||||
void leftRoom(Quotient::Room *room);
|
||||
void moveRoom(Quotient::Room *room);
|
||||
|
||||
void refreshRoomRoles(NeoChatRoom *room, const QList<int> &roles = {});
|
||||
};
|
||||
@@ -3,8 +3,9 @@
|
||||
|
||||
#include "searchmodel.h"
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
@@ -79,13 +80,9 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
auto row = index.row();
|
||||
const auto &event = *m_result->results[row].result;
|
||||
|
||||
EventHandler eventHandler;
|
||||
eventHandler.setRoom(m_room);
|
||||
eventHandler.setEvent(&event);
|
||||
EventHandler eventHandler(m_room, &event);
|
||||
|
||||
switch (role) {
|
||||
case DisplayRole:
|
||||
return eventHandler.getRichBody();
|
||||
case ShowAuthorRole:
|
||||
return true;
|
||||
case AuthorRole:
|
||||
@@ -105,22 +102,8 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
return false;
|
||||
case ShowReadMarkersRole:
|
||||
return false;
|
||||
case IsReplyRole:
|
||||
return eventHandler.hasReply();
|
||||
case ReplyIdRole:
|
||||
return eventHandler.hasReply();
|
||||
case ReplyAuthorRole:
|
||||
return eventHandler.getReplyAuthor();
|
||||
case ReplyDelegateTypeRole:
|
||||
return eventHandler.getReplyDelegateType();
|
||||
case ReplyDisplayRole:
|
||||
return eventHandler.getReplyRichBody();
|
||||
case ReplyMediaInfoRole:
|
||||
return eventHandler.getReplyMediaInfo();
|
||||
case IsPendingRole:
|
||||
return false;
|
||||
case ShowLinkPreviewRole:
|
||||
return false;
|
||||
case HighlightRole:
|
||||
return eventHandler.isHighlighted();
|
||||
case EventIdRole:
|
||||
@@ -129,6 +112,17 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
return eventHandler.isThreaded();
|
||||
case ThreadRootRole:
|
||||
return eventHandler.threadRoot();
|
||||
case ContentModelRole: {
|
||||
if (!event.isStateEvent()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(&event, m_room));
|
||||
}
|
||||
if (event.isStateEvent()) {
|
||||
if (event.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(&event, m_room));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
||||
return DelegateType::Message;
|
||||
}
|
||||
@@ -146,7 +140,6 @@ QHash<int, QByteArray> SearchModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{DelegateTypeRole, "delegateType"},
|
||||
{DisplayRole, "display"},
|
||||
{AuthorRole, "author"},
|
||||
{ShowSectionRole, "showSection"},
|
||||
{SectionRole, "section"},
|
||||
@@ -157,25 +150,15 @@ QHash<int, QByteArray> SearchModel::roleNames() const
|
||||
{ExcessReadMarkersRole, "excessReadMarkers"},
|
||||
{HighlightRole, "isHighlighted"},
|
||||
{ReadMarkersString, "readMarkersString"},
|
||||
{PlainTextRole, "plainText"},
|
||||
{VerifiedRole, "verified"},
|
||||
{ProgressInfoRole, "progressInfo"},
|
||||
{ShowReactionsRole, "showReactions"},
|
||||
{IsReplyRole, "isReply"},
|
||||
{ReplyAuthorRole, "replyAuthor"},
|
||||
{ReplyIdRole, "replyId"},
|
||||
{ReplyDelegateTypeRole, "replyDelegateType"},
|
||||
{ReplyDisplayRole, "replyDisplay"},
|
||||
{ReplyMediaInfoRole, "replyMediaInfo"},
|
||||
{ReactionRole, "reaction"},
|
||||
{ReadMarkersRole, "readMarkers"},
|
||||
{IsPendingRole, "isPending"},
|
||||
{ShowReadMarkersRole, "showReadMarkers"},
|
||||
{MimeTypeRole, "mimeType"},
|
||||
{ShowLinkPreviewRole, "showLinkPreview"},
|
||||
{LinkPreviewRole, "linkPreview"},
|
||||
{IsThreadedRole, "isThreaded"},
|
||||
{ThreadRootRole, "threadRoot"},
|
||||
{ContentModelRole, "contentModel"},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -191,19 +174,6 @@ void SearchModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
|
||||
connect(m_room, &NeoChatRoom::replyLoaded, this, [this](const auto &eventId, const auto &replyId) {
|
||||
Q_UNUSED(replyId);
|
||||
const auto &results = m_result->results;
|
||||
auto it = std::find_if(results.begin(), results.end(), [eventId](const auto &event) {
|
||||
return event.result->id() == eventId;
|
||||
});
|
||||
if (it == results.end()) {
|
||||
return;
|
||||
}
|
||||
auto row = it - results.begin();
|
||||
Q_EMIT dataChanged(index(row, 0), index(row, 0), {ReplyDelegateTypeRole, ReplyDisplayRole, ReplyMediaInfoRole, ReplyAuthorRole});
|
||||
});
|
||||
}
|
||||
|
||||
bool SearchModel::searching() const
|
||||
|
||||
@@ -51,8 +51,7 @@ public:
|
||||
* since the same delegates are used.
|
||||
*/
|
||||
enum Roles {
|
||||
DisplayRole = Qt::DisplayRole,
|
||||
DelegateTypeRole,
|
||||
DelegateTypeRole = Qt::DisplayRole + 1,
|
||||
ShowAuthorRole,
|
||||
AuthorRole,
|
||||
ShowSectionRole,
|
||||
@@ -63,25 +62,15 @@ public:
|
||||
ExcessReadMarkersRole,
|
||||
HighlightRole,
|
||||
ReadMarkersString,
|
||||
PlainTextRole,
|
||||
VerifiedRole,
|
||||
ProgressInfoRole,
|
||||
ShowReactionsRole,
|
||||
IsReplyRole,
|
||||
ReplyAuthorRole,
|
||||
ReplyIdRole,
|
||||
ReplyDelegateTypeRole,
|
||||
ReplyDisplayRole,
|
||||
ReplyMediaInfoRole,
|
||||
ReactionRole,
|
||||
ReadMarkersRole,
|
||||
IsPendingRole,
|
||||
ShowReadMarkersRole,
|
||||
MimeTypeRole,
|
||||
ShowLinkPreviewRole,
|
||||
LinkPreviewRole,
|
||||
IsThreadedRole,
|
||||
ThreadRootRole,
|
||||
ContentModelRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
explicit SearchModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -3,9 +3,7 @@
|
||||
|
||||
#include "sortfilterroomlistmodel.h"
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "roomlistmodel.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
SortFilterRoomListModel::SortFilterRoomListModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
@@ -21,53 +19,6 @@ SortFilterRoomListModel::SortFilterRoomListModel(QObject *parent)
|
||||
});
|
||||
}
|
||||
|
||||
void SortFilterRoomListModel::setRoomSortOrder(SortFilterRoomListModel::RoomSortOrder sortOrder)
|
||||
{
|
||||
m_sortOrder = sortOrder;
|
||||
Q_EMIT roomSortOrderChanged();
|
||||
if (sortOrder == SortFilterRoomListModel::Alphabetical) {
|
||||
setSortRole(RoomListModel::DisplayNameRole);
|
||||
} else if (sortOrder == SortFilterRoomListModel::LastActivity) {
|
||||
setSortRole(RoomListModel::LastActiveTimeRole);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
SortFilterRoomListModel::RoomSortOrder SortFilterRoomListModel::roomSortOrder() const
|
||||
{
|
||||
return m_sortOrder;
|
||||
}
|
||||
|
||||
bool SortFilterRoomListModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||
{
|
||||
if (m_sortOrder == SortFilterRoomListModel::LastActivity) {
|
||||
// display favorite rooms always on top
|
||||
const auto categoryLeft = static_cast<NeoChatRoomType::Types>(sourceModel()->data(source_left, RoomListModel::CategoryRole).toInt());
|
||||
const auto categoryRight = static_cast<NeoChatRoomType::Types>(sourceModel()->data(source_right, RoomListModel::CategoryRole).toInt());
|
||||
|
||||
if (categoryLeft == NeoChatRoomType::Types::Favorite && categoryRight == NeoChatRoomType::Types::Favorite) {
|
||||
return sourceModel()->data(source_left, RoomListModel::LastActiveTimeRole).toDateTime()
|
||||
> sourceModel()->data(source_right, RoomListModel::LastActiveTimeRole).toDateTime();
|
||||
}
|
||||
if (categoryLeft == NeoChatRoomType::Types::Favorite) {
|
||||
return true;
|
||||
} else if (categoryRight == NeoChatRoomType::Types::Favorite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sourceModel()->data(source_left, RoomListModel::LastActiveTimeRole).toDateTime()
|
||||
> sourceModel()->data(source_right, RoomListModel::LastActiveTimeRole).toDateTime();
|
||||
}
|
||||
if (m_sortOrder != SortFilterRoomListModel::Categories) {
|
||||
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||
}
|
||||
if (sourceModel()->data(source_left, RoomListModel::CategoryRole) != sourceModel()->data(source_right, RoomListModel::CategoryRole)) {
|
||||
return sourceModel()->data(source_left, RoomListModel::CategoryRole).toInt() < sourceModel()->data(source_right, RoomListModel::CategoryRole).toInt();
|
||||
}
|
||||
return sourceModel()->data(source_left, RoomListModel::LastActiveTimeRole).toDateTime()
|
||||
> sourceModel()->data(source_right, RoomListModel::LastActiveTimeRole).toDateTime();
|
||||
}
|
||||
|
||||
void SortFilterRoomListModel::setFilterText(const QString &text)
|
||||
{
|
||||
m_filterText = text;
|
||||
@@ -81,66 +32,15 @@ QString SortFilterRoomListModel::filterText() const
|
||||
|
||||
bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
|
||||
|
||||
bool acceptRoom =
|
||||
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
|
||||
|
||||
bool isDirectChat = sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsDirectChat).toBool();
|
||||
// In `show direct chats` mode we only care about whether or not it's a direct chat or if the filter string matches.'
|
||||
if (m_mode == DirectChats) {
|
||||
return isDirectChat && acceptRoom;
|
||||
}
|
||||
|
||||
// When not in `show direct chats` mode, filter them out.
|
||||
if (isDirectChat && m_mode == Rooms) {
|
||||
if (sourceModel()->data(index, RoomListModel::JoinStateRole).toString() == QStringLiteral("upgraded")
|
||||
&& dynamic_cast<RoomListModel *>(sourceModel())->connection()->room(sourceModel()->data(index, RoomListModel::ReplacementIdRole).toString())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() == QStringLiteral("upgraded")
|
||||
&& dynamic_cast<RoomListModel *>(sourceModel())
|
||||
->connection()
|
||||
->room(sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::ReplacementIdRole).toString())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_activeSpaceId.isEmpty()) {
|
||||
return acceptRoom;
|
||||
} else {
|
||||
const auto &rooms = SpaceHierarchyCache::instance().getRoomListForSpace(m_activeSpaceId, false);
|
||||
return std::find(rooms.begin(), rooms.end(), sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::RoomIdRole).toString())
|
||||
!= rooms.end()
|
||||
&& acceptRoom;
|
||||
}
|
||||
}
|
||||
|
||||
QString SortFilterRoomListModel::activeSpaceId() const
|
||||
{
|
||||
return m_activeSpaceId;
|
||||
}
|
||||
|
||||
void SortFilterRoomListModel::setActiveSpaceId(const QString &spaceId)
|
||||
{
|
||||
m_activeSpaceId = spaceId;
|
||||
Q_EMIT activeSpaceIdChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
SortFilterRoomListModel::Mode SortFilterRoomListModel::mode() const
|
||||
{
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
void SortFilterRoomListModel::setMode(SortFilterRoomListModel::Mode mode)
|
||||
{
|
||||
if (m_mode == mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_mode = mode;
|
||||
Q_EMIT modeChanged();
|
||||
invalidate();
|
||||
return sourceModel()->data(index, RoomListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(index, RoomListModel::IsSpaceRole).toBool() == false;
|
||||
}
|
||||
|
||||
#include "moc_sortfilterroomlistmodel.cpp"
|
||||
|
||||
@@ -30,65 +30,18 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The order by which the rooms will be sorted.
|
||||
*
|
||||
* @sa RoomSortOrder
|
||||
*/
|
||||
Q_PROPERTY(RoomSortOrder roomSortOrder READ roomSortOrder WRITE setRoomSortOrder NOTIFY roomSortOrderChanged)
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
/**
|
||||
* @brief Set the ID of the space to show rooms for.
|
||||
*/
|
||||
Q_PROPERTY(QString activeSpaceId READ activeSpaceId WRITE setActiveSpaceId NOTIFY activeSpaceIdChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether only direct chats should be shown.
|
||||
*/
|
||||
Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged)
|
||||
|
||||
public:
|
||||
enum RoomSortOrder {
|
||||
Alphabetical,
|
||||
LastActivity,
|
||||
Categories,
|
||||
};
|
||||
Q_ENUM(RoomSortOrder)
|
||||
|
||||
enum Mode {
|
||||
Rooms,
|
||||
DirectChats,
|
||||
All,
|
||||
};
|
||||
Q_ENUM(Mode)
|
||||
|
||||
explicit SortFilterRoomListModel(QObject *parent = nullptr);
|
||||
|
||||
void setRoomSortOrder(RoomSortOrder sortOrder);
|
||||
[[nodiscard]] RoomSortOrder roomSortOrder() const;
|
||||
|
||||
void setFilterText(const QString &text);
|
||||
[[nodiscard]] QString filterText() const;
|
||||
|
||||
QString activeSpaceId() const;
|
||||
void setActiveSpaceId(const QString &spaceId);
|
||||
|
||||
Mode mode() const;
|
||||
void setMode(Mode mode);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Returns true if the value of source_left is less than source_right.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::lessThan
|
||||
*/
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
@@ -97,14 +50,8 @@ protected:
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomSortOrderChanged();
|
||||
void filterTextChanged();
|
||||
void activeSpaceIdChanged();
|
||||
void modeChanged();
|
||||
|
||||
private:
|
||||
RoomSortOrder m_sortOrder = Categories;
|
||||
Mode m_mode = All;
|
||||
QString m_filterText;
|
||||
QString m_activeSpaceId;
|
||||
};
|
||||
|
||||
161
src/models/sortfilterroomtreemodel.cpp
Normal file
161
src/models/sortfilterroomtreemodel.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "sortfilterroomtreemodel.h"
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroomtype.h"
|
||||
#include "roomtreemodel.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
SortFilterRoomTreeModel::SortFilterRoomTreeModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
setRecursiveFilteringEnabled(true);
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &SortFilterRoomTreeModel::filterTextChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(this, &SortFilterRoomTreeModel::sourceModelChanged, this, [this]() {
|
||||
sourceModel()->disconnect(this);
|
||||
connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
connect(sourceModel(), &QAbstractItemModel::rowsRemoved, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
});
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::CollapsedChanged, this, &SortFilterRoomTreeModel::invalidateFilter);
|
||||
}
|
||||
|
||||
void SortFilterRoomTreeModel::setRoomSortOrder(SortFilterRoomTreeModel::RoomSortOrder sortOrder)
|
||||
{
|
||||
m_sortOrder = sortOrder;
|
||||
Q_EMIT roomSortOrderChanged();
|
||||
if (sortOrder == SortFilterRoomTreeModel::Alphabetical) {
|
||||
setSortRole(RoomTreeModel::DisplayNameRole);
|
||||
} else if (sortOrder == SortFilterRoomTreeModel::LastActivity) {
|
||||
setSortRole(RoomTreeModel::LastActiveTimeRole);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
SortFilterRoomTreeModel::RoomSortOrder SortFilterRoomTreeModel::roomSortOrder() const
|
||||
{
|
||||
return m_sortOrder;
|
||||
}
|
||||
|
||||
bool SortFilterRoomTreeModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||
{
|
||||
if (m_sortOrder == SortFilterRoomTreeModel::LastActivity) {
|
||||
// display favorite rooms always on top
|
||||
const auto categoryLeft = static_cast<NeoChatRoomType::Types>(sourceModel()->data(source_left, RoomTreeModel::CategoryRole).toInt());
|
||||
const auto categoryRight = static_cast<NeoChatRoomType::Types>(sourceModel()->data(source_right, RoomTreeModel::CategoryRole).toInt());
|
||||
|
||||
if (categoryLeft == NeoChatRoomType::Types::Favorite && categoryRight == NeoChatRoomType::Types::Favorite) {
|
||||
return sourceModel()->data(source_left, RoomTreeModel::LastActiveTimeRole).toDateTime()
|
||||
> sourceModel()->data(source_right, RoomTreeModel::LastActiveTimeRole).toDateTime();
|
||||
}
|
||||
if (categoryLeft == NeoChatRoomType::Types::Favorite) {
|
||||
return true;
|
||||
} else if (categoryRight == NeoChatRoomType::Types::Favorite) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return sourceModel()->data(source_left, RoomTreeModel::LastActiveTimeRole).toDateTime()
|
||||
> sourceModel()->data(source_right, RoomTreeModel::LastActiveTimeRole).toDateTime();
|
||||
}
|
||||
if (m_sortOrder != SortFilterRoomTreeModel::Categories) {
|
||||
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||
}
|
||||
if (sourceModel()->data(source_left, RoomTreeModel::CategoryRole) != sourceModel()->data(source_right, RoomTreeModel::CategoryRole)) {
|
||||
return sourceModel()->data(source_left, RoomTreeModel::CategoryRole).toInt() < sourceModel()->data(source_right, RoomTreeModel::CategoryRole).toInt();
|
||||
}
|
||||
return sourceModel()->data(source_left, RoomTreeModel::LastActiveTimeRole).toDateTime()
|
||||
> sourceModel()->data(source_right, RoomTreeModel::LastActiveTimeRole).toDateTime();
|
||||
}
|
||||
|
||||
void SortFilterRoomTreeModel::setFilterText(const QString &text)
|
||||
{
|
||||
m_filterText = text;
|
||||
Q_EMIT filterTextChanged();
|
||||
}
|
||||
|
||||
QString SortFilterRoomTreeModel::filterText() const
|
||||
{
|
||||
return m_filterText;
|
||||
}
|
||||
|
||||
bool SortFilterRoomTreeModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
if (!source_parent.isValid()) {
|
||||
if (sourceModel()->data(sourceModel()->index(source_row, 0), RoomTreeModel::CategoryRole).toInt() == NeoChatRoomType::Search
|
||||
&& NeoChatConfig::collapsed()) {
|
||||
return true;
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(source_row, 0), RoomTreeModel::CategoryRole).toInt() == NeoChatRoomType::AddDirect
|
||||
&& m_mode == DirectChats) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QModelIndex index = sourceModel()->index(source_row, 0, source_parent);
|
||||
|
||||
bool acceptRoom = sourceModel()->data(index, RoomTreeModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(index, RoomTreeModel::IsSpaceRole).toBool() == false;
|
||||
|
||||
bool isDirectChat = sourceModel()->data(index, RoomTreeModel::IsDirectChat).toBool();
|
||||
// In `show direct chats` mode we only care about whether or not it's a direct chat or if the filter string matches.'
|
||||
if (m_mode == DirectChats) {
|
||||
return isDirectChat && acceptRoom;
|
||||
}
|
||||
|
||||
// When not in `show direct chats` mode, filter them out.
|
||||
if (isDirectChat && m_mode == Rooms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceModel()->data(index, RoomTreeModel::JoinStateRole).toString() == QStringLiteral("upgraded")
|
||||
&& dynamic_cast<RoomTreeModel *>(sourceModel())->connection()->room(sourceModel()->data(index, RoomTreeModel::ReplacementIdRole).toString())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_activeSpaceId.isEmpty()) {
|
||||
if (!SpaceHierarchyCache::instance().isChild(sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString())) {
|
||||
return acceptRoom;
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
const auto &rooms = SpaceHierarchyCache::instance().getRoomListForSpace(m_activeSpaceId, false);
|
||||
return std::find(rooms.begin(), rooms.end(), sourceModel()->data(index, RoomTreeModel::RoomIdRole).toString()) != rooms.end() && acceptRoom;
|
||||
}
|
||||
}
|
||||
|
||||
QString SortFilterRoomTreeModel::activeSpaceId() const
|
||||
{
|
||||
return m_activeSpaceId;
|
||||
}
|
||||
|
||||
void SortFilterRoomTreeModel::setActiveSpaceId(const QString &spaceId)
|
||||
{
|
||||
m_activeSpaceId = spaceId;
|
||||
Q_EMIT activeSpaceIdChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
SortFilterRoomTreeModel::Mode SortFilterRoomTreeModel::mode() const
|
||||
{
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
void SortFilterRoomTreeModel::setMode(SortFilterRoomTreeModel::Mode mode)
|
||||
{
|
||||
if (m_mode == mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_mode = mode;
|
||||
Q_EMIT modeChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
#include "moc_sortfilterroomtreemodel.cpp"
|
||||
111
src/models/sortfilterroomtreemodel.h
Normal file
111
src/models/sortfilterroomtreemodel.h
Normal file
@@ -0,0 +1,111 @@
|
||||
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class SortFilterRoomTreeModel
|
||||
*
|
||||
* This model sorts and filters the room list.
|
||||
*
|
||||
* There are numerous room sort orders available:
|
||||
* - Categories - sort rooms by their NeoChatRoomType and then by last activty within
|
||||
* each category.
|
||||
* - LastActivity - sort rooms by the last active time in the room.
|
||||
* - Alphabetical - sort the rooms alphabetically by room name.
|
||||
*
|
||||
* The model can be given a filter string that will only show rooms who's name includes
|
||||
* the text.
|
||||
*
|
||||
* The model can also be given an active space ID and will only show rooms within
|
||||
* that space.
|
||||
*
|
||||
* All space rooms and upgraded rooms will also be filtered out.
|
||||
*/
|
||||
class SortFilterRoomTreeModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The order by which the rooms will be sorted.
|
||||
*
|
||||
* @sa RoomSortOrder
|
||||
*/
|
||||
Q_PROPERTY(RoomSortOrder roomSortOrder READ roomSortOrder WRITE setRoomSortOrder NOTIFY roomSortOrderChanged)
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
/**
|
||||
* @brief Set the ID of the space to show rooms for.
|
||||
*/
|
||||
Q_PROPERTY(QString activeSpaceId READ activeSpaceId WRITE setActiveSpaceId NOTIFY activeSpaceIdChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether only direct chats should be shown.
|
||||
*/
|
||||
Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged)
|
||||
|
||||
public:
|
||||
enum RoomSortOrder {
|
||||
Alphabetical,
|
||||
LastActivity,
|
||||
Categories,
|
||||
};
|
||||
Q_ENUM(RoomSortOrder)
|
||||
|
||||
enum Mode {
|
||||
Rooms,
|
||||
DirectChats,
|
||||
All,
|
||||
};
|
||||
Q_ENUM(Mode)
|
||||
|
||||
explicit SortFilterRoomTreeModel(QObject *parent = nullptr);
|
||||
|
||||
void setRoomSortOrder(RoomSortOrder sortOrder);
|
||||
[[nodiscard]] RoomSortOrder roomSortOrder() const;
|
||||
|
||||
void setFilterText(const QString &text);
|
||||
[[nodiscard]] QString filterText() const;
|
||||
|
||||
QString activeSpaceId() const;
|
||||
void setActiveSpaceId(const QString &spaceId);
|
||||
|
||||
Mode mode() const;
|
||||
void setMode(Mode mode);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Returns true if the value of source_left is less than source_right.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::lessThan
|
||||
*/
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomSortOrderChanged();
|
||||
void filterTextChanged();
|
||||
void activeSpaceIdChanged();
|
||||
void modeChanged();
|
||||
|
||||
private:
|
||||
RoomSortOrder m_sortOrder = Categories;
|
||||
Mode m_mode = All;
|
||||
QString m_filterText;
|
||||
QString m_activeSpaceId;
|
||||
};
|
||||
@@ -244,6 +244,20 @@ QVariant SpaceChildrenModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
return QVariant::fromValue(nullptr);
|
||||
}
|
||||
if (role == OrderRole) {
|
||||
if (child->parentItem() == nullptr) {
|
||||
return QString();
|
||||
}
|
||||
const auto childState = child->parentItem()->childStateContent(child);
|
||||
return childState[QLatin1String("order")].toString();
|
||||
}
|
||||
if (role == ChildTimestampRole) {
|
||||
if (child->parentItem() == nullptr) {
|
||||
return QString();
|
||||
}
|
||||
const auto childState = child->parentItem()->childState(child);
|
||||
return childState[QLatin1String("origin_server_ts")].toString();
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -325,6 +339,8 @@ QHash<int, QByteArray> SpaceChildrenModel::roleNames() const
|
||||
roles[IsDeclaredParentRole] = "isDeclaredParent";
|
||||
roles[CanRemove] = "canRemove";
|
||||
roles[ParentRoomRole] = "parentRoom";
|
||||
roles[OrderRole] = "order";
|
||||
roles[ChildTimestampRole] = "childTimestamp";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -51,6 +51,8 @@ public:
|
||||
IsDeclaredParentRole,
|
||||
CanRemove,
|
||||
ParentRoomRole,
|
||||
OrderRole,
|
||||
ChildTimestampRole,
|
||||
};
|
||||
|
||||
explicit SpaceChildrenModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -26,10 +26,27 @@ QString SpaceChildSortFilterModel::filterText() const
|
||||
|
||||
bool SpaceChildSortFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||
{
|
||||
if (!source_left.data(SpaceChildrenModel::IsSpaceRole).toBool() && source_right.data(SpaceChildrenModel::IsSpaceRole).toBool()) {
|
||||
if (source_left.data(SpaceChildrenModel::IsSpaceRole).toBool() && source_right.data(SpaceChildrenModel::IsSpaceRole).toBool()) {
|
||||
if (!source_left.data(SpaceChildrenModel::OrderRole).toString().isEmpty() && !source_right.data(SpaceChildrenModel::OrderRole).toString().isEmpty()) {
|
||||
return QString::compare(source_left.data(SpaceChildrenModel::OrderRole).toString(), source_right.data(SpaceChildrenModel::OrderRole).toString())
|
||||
< 0;
|
||||
}
|
||||
return source_left.data(SpaceChildrenModel::ChildTimestampRole).toDateTime() > source_right.data(SpaceChildrenModel::ChildTimestampRole).toDateTime();
|
||||
}
|
||||
if (source_left.data(SpaceChildrenModel::IsSpaceRole).toBool()) {
|
||||
return true;
|
||||
} else if (source_right.data(SpaceChildrenModel::IsSpaceRole).toBool()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
if (!source_left.data(SpaceChildrenModel::OrderRole).toString().isEmpty() && !source_right.data(SpaceChildrenModel::OrderRole).toString().isEmpty()) {
|
||||
return QString::compare(source_left.data(SpaceChildrenModel::OrderRole).toString(), source_right.data(SpaceChildrenModel::OrderRole).toString()) < 0;
|
||||
}
|
||||
if (!source_left.data(SpaceChildrenModel::OrderRole).toString().isEmpty()) {
|
||||
return true;
|
||||
} else if (!source_right.data(SpaceChildrenModel::OrderRole).toString().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return source_left.data(SpaceChildrenModel::ChildTimestampRole).toDateTime() > source_right.data(SpaceChildrenModel::ChildTimestampRole).toDateTime();
|
||||
}
|
||||
|
||||
bool SpaceChildSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
|
||||
@@ -141,6 +141,22 @@ bool SpaceTreeItem::isSpace() const
|
||||
return m_isSpace;
|
||||
}
|
||||
|
||||
QJsonObject SpaceTreeItem::childState(const SpaceTreeItem *child) const
|
||||
{
|
||||
if (child == nullptr) {
|
||||
return {};
|
||||
}
|
||||
if (child->parentItem() != this) {
|
||||
return {};
|
||||
}
|
||||
for (const auto &childState : m_childStates) {
|
||||
if (childState->stateKey() == child->id()) {
|
||||
return childState->fullJson();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonObject SpaceTreeItem::childStateContent(const SpaceTreeItem *child) const
|
||||
{
|
||||
if (child == nullptr) {
|
||||
|
||||
@@ -125,6 +125,11 @@ public:
|
||||
*/
|
||||
bool isSpace() const;
|
||||
|
||||
/**
|
||||
* @brief Return the m.space.child stripped state Json for the given child.
|
||||
*/
|
||||
QJsonObject childState(const SpaceTreeItem *child) const;
|
||||
|
||||
/**
|
||||
* @brief Return the m.space.child state event content for the given child.
|
||||
*/
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
#include "controller.h"
|
||||
#include "jobs/neochatchangepasswordjob.h"
|
||||
#include "jobs/neochatdeactivateaccountjob.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
@@ -97,6 +99,21 @@ void NeoChatConnection::connectSignals()
|
||||
Q_EMIT directChatInvitesChanged();
|
||||
}
|
||||
});
|
||||
|
||||
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() {
|
||||
Q_EMIT homeNotificationsChanged();
|
||||
});
|
||||
for (const auto room : allRooms()) {
|
||||
connect(room, &NeoChatRoom::unreadStatsChanged, this, [this, room]() {
|
||||
if (room != nullptr) {
|
||||
auto category = NeoChatRoomType::typeForRoom(static_cast<NeoChatRoom *>(room));
|
||||
if (!SpaceHierarchyCache::instance().isChild(room->id()) && (category == NeoChatRoomType::Normal || category == NeoChatRoomType::Favorite)
|
||||
&& room->successorId().isEmpty()) {
|
||||
Q_EMIT homeNotificationsChanged();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void NeoChatConnection::logout(bool serverSideLogout)
|
||||
@@ -240,7 +257,7 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic, co
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Room creation failed: %1", job->errorString()), {});
|
||||
});
|
||||
connectSingleShot(this, &Connection::newRoom, this, [](Room *room) {
|
||||
RoomManager::instance().enterRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -272,7 +289,7 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Space creation failed: %1", job->errorString()), {});
|
||||
});
|
||||
connectSingleShot(this, &Connection::newRoom, this, [](Room *room) {
|
||||
RoomManager::instance().enterRoom(dynamic_cast<NeoChatRoom *>(room));
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -295,9 +312,9 @@ void NeoChatConnection::openOrCreateDirectChat(User *user)
|
||||
const auto existing = directChats();
|
||||
|
||||
if (existing.contains(user)) {
|
||||
const auto room = static_cast<NeoChatRoom *>(this->room(existing.value(user)));
|
||||
const auto room = this->room(existing.value(user));
|
||||
if (room) {
|
||||
RoomManager::instance().enterRoom(room);
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -319,6 +336,29 @@ qsizetype NeoChatConnection::directChatNotifications() const
|
||||
return notifications;
|
||||
}
|
||||
|
||||
qsizetype NeoChatConnection::homeNotifications() const
|
||||
{
|
||||
qsizetype notifications = 0;
|
||||
QStringList added;
|
||||
const auto &spaceHierarchyCache = SpaceHierarchyCache::instance();
|
||||
for (const auto &room : allRooms()) {
|
||||
auto category = NeoChatRoomType::typeForRoom(static_cast<NeoChatRoom *>(room));
|
||||
if (!added.contains(room->id()) && room->joinState() == JoinState::Join && !room->isDirectChat() && !spaceHierarchyCache.isChild(room->id())
|
||||
&& room->successorId().isEmpty()) {
|
||||
switch (category) {
|
||||
case NeoChatRoomType::Normal:
|
||||
case NeoChatRoomType::Favorite:
|
||||
notifications += room->notificationCount();
|
||||
break;
|
||||
default:
|
||||
notifications += room->highlightCount();
|
||||
}
|
||||
added += room->id();
|
||||
}
|
||||
}
|
||||
return notifications;
|
||||
}
|
||||
|
||||
bool NeoChatConnection::directChatInvites() const
|
||||
{
|
||||
auto inviteRooms = rooms(JoinState::Invite);
|
||||
|
||||
@@ -32,6 +32,11 @@ class NeoChatConnection : public Quotient::Connection
|
||||
*/
|
||||
Q_PROPERTY(qsizetype directChatNotifications READ directChatNotifications NOTIFY directChatNotificationsChanged)
|
||||
|
||||
/**
|
||||
* @brief The total number of notifications for all rooms in the home tab.
|
||||
*/
|
||||
Q_PROPERTY(qsizetype homeNotifications READ homeNotifications NOTIFY homeNotificationsChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether there is at least one invite to a direct chat.
|
||||
*/
|
||||
@@ -109,6 +114,7 @@ public:
|
||||
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
|
||||
|
||||
qsizetype directChatNotifications() const;
|
||||
qsizetype homeNotifications() const;
|
||||
bool directChatInvites() const;
|
||||
|
||||
// note: this is intentionally a copied QString because
|
||||
@@ -123,6 +129,7 @@ public:
|
||||
Q_SIGNALS:
|
||||
void labelChanged();
|
||||
void directChatNotificationsChanged();
|
||||
void homeNotificationsChanged();
|
||||
void directChatInvitesChanged();
|
||||
void isOnlineChanged();
|
||||
void passwordStatus(NeoChatConnection::PasswordStatus status);
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "neochatconfig.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "roomlastmessageprovider.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "texthandler.h"
|
||||
#include "urlhelper.h"
|
||||
#include "utils.h"
|
||||
@@ -125,6 +126,16 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
Q_EMIT urlPreviewEnabledChanged();
|
||||
}
|
||||
});
|
||||
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() {
|
||||
if (isSpace()) {
|
||||
Q_EMIT childrenNotificationCountChanged();
|
||||
}
|
||||
});
|
||||
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceNotifcationCountChanged, this, [this](const QStringList &spaces) {
|
||||
if (spaces.contains(id())) {
|
||||
Q_EMIT childrenNotificationCountChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool NeoChatRoom::hasFileUploading() const
|
||||
@@ -502,9 +513,7 @@ void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||
}
|
||||
|
||||
if (isThread) {
|
||||
EventHandler eventHandler;
|
||||
eventHandler.setRoom(this);
|
||||
eventHandler.setEvent(&**replyIt);
|
||||
EventHandler eventHandler(this, &**replyIt);
|
||||
|
||||
const bool isFallingBack = !eventHandler.isThreaded();
|
||||
|
||||
@@ -551,9 +560,7 @@ void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||
if (isReply) {
|
||||
const auto &replyEvt = **replyIt;
|
||||
|
||||
EventHandler eventHandler;
|
||||
eventHandler.setRoom(this);
|
||||
eventHandler.setEvent(&**replyIt);
|
||||
EventHandler eventHandler(this, &**replyIt);
|
||||
|
||||
// clang-format off
|
||||
QJsonObject json{
|
||||
@@ -1272,7 +1279,7 @@ void NeoChatRoom::removeParent(const QString &parentId)
|
||||
}
|
||||
}
|
||||
|
||||
bool NeoChatRoom::isSpace()
|
||||
bool NeoChatRoom::isSpace() const
|
||||
{
|
||||
const auto creationEvent = this->creation();
|
||||
if (!creationEvent) {
|
||||
@@ -1282,6 +1289,14 @@ bool NeoChatRoom::isSpace()
|
||||
return creationEvent->roomType() == RoomType::Space;
|
||||
}
|
||||
|
||||
qsizetype NeoChatRoom::childrenNotificationCount()
|
||||
{
|
||||
if (!isSpace()) {
|
||||
return 0;
|
||||
}
|
||||
return SpaceHierarchyCache::instance().notificationCountForSpace(id());
|
||||
}
|
||||
|
||||
void NeoChatRoom::addChild(const QString &childId, bool setChildParent, bool canonical, bool suggested)
|
||||
{
|
||||
if (!isSpace()) {
|
||||
|
||||
@@ -126,6 +126,13 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The number of notifications in this room's children.
|
||||
*
|
||||
* Will always return 0 if this is not a space.
|
||||
*/
|
||||
Q_PROPERTY(qsizetype childrenNotificationCount READ childrenNotificationCount NOTIFY childrenNotificationCountChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the local user has an invite to the room.
|
||||
*
|
||||
@@ -524,7 +531,9 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void removeParent(const QString &parentId);
|
||||
|
||||
[[nodiscard]] bool isSpace();
|
||||
[[nodiscard]] bool isSpace() const;
|
||||
|
||||
qsizetype childrenNotificationCount();
|
||||
|
||||
/**
|
||||
* @brief Add the given room as a child.
|
||||
@@ -815,6 +824,7 @@ Q_SIGNALS:
|
||||
void parentIdsChanged();
|
||||
void canonicalParentChanged();
|
||||
void lastActiveTimeChanged();
|
||||
void childrenNotificationCountChanged();
|
||||
void isInviteChanged();
|
||||
void readOnlyChanged();
|
||||
void displayNameChanged();
|
||||
|
||||
@@ -216,7 +216,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
auto connection = dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().get(room->localUser()->id()));
|
||||
Controller::instance().setActiveConnection(connection);
|
||||
RoomManager::instance().setConnection(connection);
|
||||
RoomManager::instance().enterRoom(room);
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
});
|
||||
|
||||
if (canReply) {
|
||||
@@ -251,7 +251,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS
|
||||
}
|
||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||
notification->close();
|
||||
RoomManager::instance().enterRoom(room);
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
});
|
||||
|
||||
const auto acceptAction = notification->addAction(i18nc("@action:button The thing being accepted is an invitation to chat", "Accept"));
|
||||
|
||||
@@ -40,9 +40,8 @@ FormCard.FormCardPage {
|
||||
if (fileDialog) {
|
||||
return;
|
||||
}
|
||||
|
||||
fileDialog = openFileDialog.createObject(this);
|
||||
fileDialog.chosen.connect((receivedSource) => {
|
||||
fileDialog.chosen.connect(receivedSource => {
|
||||
if (!receivedSource) {
|
||||
return;
|
||||
}
|
||||
@@ -137,7 +136,9 @@ FormCard.FormCardPage {
|
||||
visible: root.connection !== undefined && root.connection.canChangePassword === false
|
||||
text: i18n("Your server doesn't support changing your password")
|
||||
}
|
||||
FormCard.FormDelegateSeparator { visible: root.connection !== undefined && root.connection.canChangePassword === false }
|
||||
FormCard.FormDelegateSeparator {
|
||||
visible: root.connection !== undefined && root.connection.canChangePassword === false
|
||||
}
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: currentPassword
|
||||
label: i18n("Current Password:")
|
||||
@@ -212,7 +213,11 @@ FormCard.FormCardPage {
|
||||
FormCard.FormButtonDelegate {
|
||||
id: deactivateAccountButton
|
||||
text: i18n("Deactivate Account")
|
||||
onClicked: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ConfirmDeactivateAccountDialog.qml", {connection: root.connection}, {title: i18nc("@title", "Confirm Deactivating Account")})
|
||||
onClicked: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ConfirmDeactivateAccountDialog.qml", {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Confirm Deactivating Account")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,26 +24,30 @@ QQC2.Menu {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18n("Account editor")
|
||||
});
|
||||
})
|
||||
}
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Notification settings")
|
||||
icon.name: "notifications"
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/SettingsPage.qml", {
|
||||
defaultPage: "notifications",
|
||||
connection: root.connection,
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18n("Configure")
|
||||
});
|
||||
title: i18n("Configure"),
|
||||
width: Kirigami.Units.gridUnit * 50,
|
||||
height: Kirigami.Units.gridUnit * 42
|
||||
})
|
||||
}
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Devices")
|
||||
icon.name: "computer-symbolic"
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/SettingsPage.qml", {
|
||||
defaultPage: "devices",
|
||||
connection: root.connection,
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18n("Configure")
|
||||
title: i18n("Configure"),
|
||||
width: Kirigami.Units.gridUnit * 50,
|
||||
height: Kirigami.Units.gridUnit * 42
|
||||
})
|
||||
}
|
||||
QQC2.MenuItem {
|
||||
|
||||
@@ -80,7 +80,7 @@ FormCard.FormCardPage {
|
||||
connection: accountDelegate.connection
|
||||
onAccepted: {
|
||||
if (AccountRegistry.accountCount === 1) {
|
||||
root.Window.window.close()
|
||||
root.Window.window.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -93,7 +93,9 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormDelegateSeparator { below: addAccountDelegate }
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: addAccountDelegate
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: addAccountDelegate
|
||||
@@ -107,7 +109,7 @@ FormCard.FormCardPage {
|
||||
target: Controller
|
||||
function onConnectionAdded() {
|
||||
if (pageStack.layers.depth > 2) {
|
||||
pageStack.layers.pop()
|
||||
pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,9 @@ FormCard.FormCardPage {
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.ButtonGroup { id: themeGroup }
|
||||
QQC2.ButtonGroup {
|
||||
id: themeGroup
|
||||
}
|
||||
ThemeRadioButton {
|
||||
thin: timelineModeSetting.width < Kirigami.Units.gridUnit * 22
|
||||
innerObject: [
|
||||
@@ -196,7 +198,9 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { below: compactRoomListDelegate }
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: compactRoomListDelegate
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: compactRoomListDelegate
|
||||
@@ -208,7 +212,11 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: compactRoomListDelegate ; below: colorSchemeDelegate.item ; visible: colorSchemeDelegate.visible }
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: compactRoomListDelegate
|
||||
below: colorSchemeDelegate.item
|
||||
visible: colorSchemeDelegate.visible
|
||||
}
|
||||
|
||||
Loader {
|
||||
id: colorSchemeDelegate
|
||||
@@ -231,7 +239,10 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: showFancyEffectsDelegate ; below: hasWindowSystemDelegate }
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: showFancyEffectsDelegate
|
||||
below: hasWindowSystemDelegate
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: hasWindowSystemDelegate
|
||||
@@ -245,7 +256,10 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: hasWindowSystemDelegate; below: transparencyDelegate }
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: hasWindowSystemDelegate
|
||||
below: transparencyDelegate
|
||||
}
|
||||
|
||||
FormCard.AbstractFormDelegate {
|
||||
id: transparencyDelegate
|
||||
@@ -269,7 +283,9 @@ FormCard.FormCardPage {
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
|
||||
HoverHandler { id: sliderHover }
|
||||
HoverHandler {
|
||||
id: sliderHover
|
||||
}
|
||||
QQC2.ToolTip.visible: sliderHover.hovered && !enabled
|
||||
QQC2.ToolTip.text: i18n("Only enabled if the transparent chat page is enabled.")
|
||||
}
|
||||
@@ -280,7 +296,11 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: transparencyDelegate; below: showLocalMessagesOnRightDelegate; visible: transparencyDelegate.visible }
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: transparencyDelegate
|
||||
below: showLocalMessagesOnRightDelegate
|
||||
visible: transparencyDelegate.visible
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: showLocalMessagesOnRightDelegate
|
||||
@@ -288,25 +308,27 @@ FormCard.FormCardPage {
|
||||
checked: Config.showLocalMessagesOnRight
|
||||
enabled: !Config.isShowLocalMessagesOnRightImmutable && !Config.compactLayout
|
||||
onToggled: {
|
||||
Config.showLocalMessagesOnRight = checked
|
||||
Config.save()
|
||||
Config.showLocalMessagesOnRight = checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator { above: showLocalMessagesOnRightDelegate; below: showLinkPreviewDelegate }
|
||||
FormCard.FormDelegateSeparator {
|
||||
above: showLocalMessagesOnRightDelegate
|
||||
below: showLinkPreviewDelegate
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: showLinkPreviewDelegate
|
||||
text: i18n("Show links preview in the chat messages")
|
||||
checked: Config.showLinkPreview
|
||||
onToggled: {
|
||||
Config.showLinkPreview = checked
|
||||
Config.save()
|
||||
Config.showLinkPreview = checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Show Avatar")
|
||||
}
|
||||
@@ -315,8 +337,8 @@ FormCard.FormCardPage {
|
||||
text: i18n("In chat")
|
||||
checked: Config.showAvatarInTimeline
|
||||
onToggled: {
|
||||
Config.showAvatarInTimeline = checked
|
||||
Config.save()
|
||||
Config.showAvatarInTimeline = checked;
|
||||
Config.save();
|
||||
}
|
||||
enabled: !Config.isShowAvatarInTimelineImmutable
|
||||
}
|
||||
@@ -326,8 +348,8 @@ FormCard.FormCardPage {
|
||||
checked: Config.showAvatarInRoomDrawer
|
||||
enabled: !Config.isShowAvatarInRoomDrawerImmutable
|
||||
onToggled: {
|
||||
Config.showAvatarInRoomDrawer = checked
|
||||
Config.save()
|
||||
Config.showAvatarInRoomDrawer = checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,11 +27,10 @@ QQC2.Popup {
|
||||
text: i18n("Choose local file")
|
||||
|
||||
onClicked: {
|
||||
root.close()
|
||||
|
||||
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
fileDialog.chosen.connect(path => root.chosen(path))
|
||||
fileDialog.open()
|
||||
root.close();
|
||||
var fileDialog = openFileDialog.createObject(QQC2.ApplicationWindow.overlay);
|
||||
fileDialog.chosen.connect(path => root.chosen(path));
|
||||
fileDialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,11 +45,11 @@ QQC2.Popup {
|
||||
icon.name: 'insert-image'
|
||||
text: i18n("Clipboard image")
|
||||
onClicked: {
|
||||
const path = StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + "/screenshots/" + (new Date()).getTime() + ".png"
|
||||
const path = StandardPaths.standardLocations(StandardPaths.CacheLocation)[0] + "/screenshots/" + (new Date()).getTime() + ".png";
|
||||
if (!Clipboard.saveImage(path)) {
|
||||
return;
|
||||
}
|
||||
root.chosen(path)
|
||||
root.chosen(path);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.kde.neochat
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
signal attachmentCancelled()
|
||||
signal attachmentCancelled
|
||||
|
||||
property string attachmentPath
|
||||
|
||||
@@ -47,7 +47,7 @@ ColumnLayout {
|
||||
|
||||
onClicked: {
|
||||
let imageEditor = applicationWindow().pageStack.layers.push(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function(newPath) {
|
||||
imageEditor.newPathChanged.connect(function (newPath) {
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
root.attachmentPath = newPath;
|
||||
});
|
||||
@@ -61,7 +61,7 @@ ColumnLayout {
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Cancel sending attachment")
|
||||
icon.name: "dialog-close"
|
||||
onTriggered: attachmentCancelled();
|
||||
onTriggered: attachmentCancelled()
|
||||
shortcut: "Escape"
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
@@ -81,14 +81,14 @@ ColumnLayout {
|
||||
|
||||
onSourceChanged: {
|
||||
// Reset source size height, which affect implicitHeight
|
||||
sourceSize.height = -1
|
||||
sourceSize.height = -1;
|
||||
}
|
||||
|
||||
onSourceSizeChanged: {
|
||||
if (implicitHeight > Kirigami.Units.gridUnit * 8) {
|
||||
// This can save a lot of RAM when loading large images.
|
||||
// It also improves visual quality for large images.
|
||||
sourceSize.height = Kirigami.Units.gridUnit * 8
|
||||
sourceSize.height = Kirigami.Units.gridUnit * 8;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
168
src/qml/AudioComponent.qml
Normal file
168
src/qml/AudioComponent.qml
Normal file
@@ -0,0 +1,168 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtMultimedia
|
||||
|
||||
import org.kde.coreaddons
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
/**
|
||||
* @brief A component to show audio from a message.
|
||||
*/
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The NeoChatRoom the delegate is being displayed in.
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
/**
|
||||
* @brief The matrix ID of the message event.
|
||||
*/
|
||||
required property string eventId
|
||||
|
||||
/**
|
||||
* @brief The display text of the message.
|
||||
*/
|
||||
required property string display
|
||||
|
||||
/**
|
||||
* @brief The media info for the event.
|
||||
*
|
||||
* This should consist of the following:
|
||||
* - source - The mxc URL for the media.
|
||||
* - mimeType - The MIME type of the media (should be image/xxx for this delegate).
|
||||
* - mimeIcon - The MIME icon name (should be image-xxx).
|
||||
* - size - The file size in bytes.
|
||||
* - 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.
|
||||
*/
|
||||
required property var mediaInfo
|
||||
|
||||
/**
|
||||
* @brief FileTransferInfo for any downloading files.
|
||||
*/
|
||||
required property var fileTransferInfo
|
||||
|
||||
/**
|
||||
* @brief Whether the media has been downloaded.
|
||||
*/
|
||||
readonly property bool downloaded: root.fileTransferInfo && root.fileTransferInfo.completed
|
||||
onDownloadedChanged: if (downloaded) {
|
||||
audio.play()
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The maximum width that the bubble's content can be.
|
||||
*/
|
||||
property real maxContentWidth: -1
|
||||
|
||||
MediaPlayer {
|
||||
id: audio
|
||||
onErrorOccurred: (error, errorString) => console.warn("Audio playback error:" + error + errorString)
|
||||
audioOutput: AudioOutput {}
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "notDownloaded"
|
||||
when: !root.fileTransferInfo.completed && !root.fileTransferInfo.active
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: root.room.downloadFile(root.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloading"
|
||||
when: root.fileTransferInfo.active && !root.fileTransferInfo.completed
|
||||
PropertyChanges {
|
||||
target: downloadBar
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-stop"
|
||||
onClicked: {
|
||||
root.room.cancelFileTransfer(root.eventId)
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "paused"
|
||||
when: root.fileTransferInfo.completed && (audio.playbackState === MediaPlayer.StoppedState || audio.playbackState === MediaPlayer.PausedState)
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: {
|
||||
audio.source = root.fileTransferInfo.localPath;
|
||||
audio.play()
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
when: root.fileTransferInfo.completed && audio.playbackState === MediaPlayer.PlayingState
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
|
||||
icon.name: "media-playback-pause"
|
||||
|
||||
onClicked: audio.pause()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
RowLayout {
|
||||
QQC2.ToolButton {
|
||||
id: playButton
|
||||
}
|
||||
QQC2.Label {
|
||||
text: root.display
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
QQC2.ProgressBar {
|
||||
id: downloadBar
|
||||
visible: false
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: root.mediaInfo.size
|
||||
value: root.fileTransferInfo.progress
|
||||
}
|
||||
RowLayout {
|
||||
visible: audio.hasAudio
|
||||
|
||||
QQC2.Slider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: audio.duration
|
||||
value: audio.position
|
||||
onMoved: audio.seek(value)
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
visible: root.maxContentWidth > Kirigami.Units.gridUnit * 12
|
||||
|
||||
text: Format.formatDuration(audio.position) + "/" + Format.formatDuration(audio.duration)
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
visible: audio.hasAudio && root.maxContentWidth < Kirigami.Units.gridUnit * 12
|
||||
|
||||
text: Format.formatDuration(audio.position) + "/" + Format.formatDuration(audio.duration)
|
||||
}
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtMultimedia
|
||||
|
||||
import org.kde.coreaddons
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
/**
|
||||
* @brief A timeline delegate for an audio message.
|
||||
*
|
||||
* @inherit MessageDelegate
|
||||
*/
|
||||
MessageDelegate {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The media info for the event.
|
||||
*
|
||||
* This should consist of the following:
|
||||
* - source - The mxc URL for the media.
|
||||
* - mimeType - The MIME type of the media (should be audio/xxx for this delegate).
|
||||
* - mimeIcon - The MIME icon name (should be audio-xxx).
|
||||
* - size - The file size in bytes.
|
||||
* - duration - The length in seconds of the audio media.
|
||||
*/
|
||||
required property var mediaInfo
|
||||
|
||||
/**
|
||||
* @brief Whether the media has been downloaded.
|
||||
*/
|
||||
readonly property bool downloaded: root.progressInfo && root.progressInfo.completed
|
||||
onDownloadedChanged: audio.play()
|
||||
|
||||
onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, "", "", mediaInfo.mimeType, progressInfo)
|
||||
|
||||
bubbleContent: ColumnLayout {
|
||||
MediaPlayer {
|
||||
id: audio
|
||||
source: root.progressInfo.localPath
|
||||
}
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "notDownloaded"
|
||||
when: !root.progressInfo.completed && !root.progressInfo.active
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: root.room.downloadFile(root.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloading"
|
||||
when: root.progressInfo.active && !root.progressInfo.completed
|
||||
PropertyChanges {
|
||||
target: downloadBar
|
||||
visible: true
|
||||
}
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-stop"
|
||||
onClicked: {
|
||||
root.room.cancelFileTransfer(root.eventId)
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "paused"
|
||||
when: root.progressInfo.completed && (audio.playbackState === Audio.StoppedState || audio.playbackState === Audio.PausedState)
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: {
|
||||
audio.play()
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
when: root.progressInfo.completed && audio.playbackState === Audio.PlayingState
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
|
||||
icon.name: "media-playback-pause"
|
||||
|
||||
onClicked: audio.pause()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
RowLayout {
|
||||
QQC2.ToolButton {
|
||||
id: playButton
|
||||
}
|
||||
QQC2.Label {
|
||||
text: root.display
|
||||
wrapMode: Text.Wrap
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
QQC2.ProgressBar {
|
||||
id: downloadBar
|
||||
visible: false
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: root.mediaInfo.size
|
||||
value: root.progressInfo.progress
|
||||
}
|
||||
RowLayout {
|
||||
visible: audio.hasAudio
|
||||
|
||||
QQC2.Slider {
|
||||
Layout.fillWidth: true
|
||||
from: 0
|
||||
to: audio.duration
|
||||
value: audio.position
|
||||
onMoved: audio.seek(value)
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
visible: root.contentMaxWidth > Kirigami.Units.gridUnit * 12
|
||||
|
||||
text: Format.formatDuration(audio.position) + "/" + Format.formatDuration(audio.duration)
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||
visible: audio.hasAudio && root.contentMaxWidth < Kirigami.Units.gridUnit * 12
|
||||
|
||||
text: Format.formatDuration(audio.position) + "/" + Format.formatDuration(audio.duration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,8 @@ Delegates.RoundedItemDelegate {
|
||||
|
||||
property url source
|
||||
|
||||
signal contextMenuRequested()
|
||||
signal selected()
|
||||
signal contextMenuRequested
|
||||
signal selected
|
||||
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
@@ -24,9 +24,9 @@ Delegates.RoundedItemDelegate {
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
Accessible.onPressAction: selected();
|
||||
Keys.onSpacePressed: selected();
|
||||
Keys.onEnterPressed: selected();
|
||||
Accessible.onPressAction: selected()
|
||||
Keys.onSpacePressed: selected()
|
||||
Keys.onEnterPressed: selected()
|
||||
|
||||
onPressAndHold: root.contextMenuRequested()
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user