Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85802883
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
15 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/src/matrix-room-list.cpp b/src/matrix-room-list.cpp
index 9c9b7e5..ebb2535 100644
--- a/src/matrix-room-list.cpp
+++ b/src/matrix-room-list.cpp
@@ -1,190 +1,194 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <zug/into_vector.hpp>
#include <zug/transducer/cat.hpp>
#include <lager/commit.hpp>
#include <lager/constant.hpp>
#include <lager/lenses/optional.hpp>
#include "matrix-room-list.hpp"
#include "matrix-room.hpp"
#include "matrix-user-given-attrs-map.hpp"
#include "matrix-utils.hpp"
#include "helper.hpp"
using namespace Kazv;
static Timestamp latestEventTimestamp(const RoomModel &room)
{
if (room.timeline.empty()) {
return 0;
}
auto latestEventId = room.timeline[room.timeline.size() - 1];
auto latestEvent = room.messages[latestEventId];
return latestEvent.originServerTs();
}
MatrixRoomList::MatrixRoomList(Kazv::Client client, QString tagId, QString filter, QObject *parent)
: KazvAbstractListModel(parent)
, m_client(client)
, m_tagId(tagId)
, m_tagIdCursor(lager::make_sensor([this] { return m_tagId.toStdString(); }))
, m_filter(lager::make_state(filter.toStdString(), lager::automatic_tag{}))
, m_userGivenNicknameMap(userGivenNicknameMapFor(m_client).map(&Event::content))
, m_roomIds(lager::with(m_tagIdCursor, m_client.rooms(), m_filter, m_client.roomIdsByTagId(), m_userGivenNicknameMap)
.map([](const auto &tagIdStdStr, const auto &allRooms, const auto &filter, const auto &roomsByTagMap, const auto &userGivenNicknameMap) {
auto toId = zug::map([](const auto &pair) {
return pair.first;
});
auto roomName = [](const auto &room) {
auto content = room.stateEvents[{"m.room.name", ""}].content().get();
if (content.contains("name") && content["name"].is_string()) {
return content["name"].template get<std::string>();
}
return std::string();
};
auto roomHeroNames = [m=userGivenNicknameMap](const auto &room) {
auto heroEvents = room.heroMemberEvents();
return intoImmer(
immer::flex_vector<std::string>(),
zug::map([m](const Event &ev) {
auto userId = ev.stateKey();
auto content = ev.content().get();
auto res = std::vector<std::string>{};
if (content.contains("displayname") && content["displayname"].is_string()) {
res.push_back(content["displayname"].template get<std::string>());
}
if (m.get().contains(userId)
&& m.get()[userId].is_string()) {
res.push_back(m.get()[userId].template get<std::string>());
}
return res;
})
| zug::cat,
heroEvents
);
};
auto applyFilter = zug::filter([&filter, &allRooms, &roomName, &roomHeroNames](const auto &id) {
if (filter.empty()) {
return true;
}
const auto &room = allRooms[id];
// Use exact match for room id
if (room.roomId == filter) {
return true;
}
auto name = roomName(room);
if (!name.empty()) {
// Use substring match for name search
return name.find(filter) != std::string::npos;
}
// The room has no name, use hero names for the search
auto heroes = roomHeroNames(room);
// If any of the room hero matches the filter, consider it a match
return std::any_of(heroes.begin(), heroes.end(),
[&filter](const auto &name) {
return name.find(filter) != std::string::npos;
})
|| std::any_of(room.heroIds.begin(), room.heroIds.end(),
[&filter](const auto &id) {
return id.find(filter) != std::string::npos;
});
});
auto sortByTimestampDesc = [allRooms](std::vector<std::string> container) {
std::sort(
container.begin(),
container.end(),
[allRooms](const std::string &idA, const std::string &idB) {
const auto &roomA = allRooms[idA];
const auto &roomB = allRooms[idB];
auto aIsInvite = roomA.membership == Invite;
auto bIsInvite = roomB.membership == Invite;
+ auto aIsFavourite = roomA.tags().count("m.favourite") == 1;
+ auto bIsFavourite = roomB.tags().count("m.favourite") == 1;
if (aIsInvite != bIsInvite) {
/**
* if my membership in A is invite,
* then the membership in B is not invite,
* so A should come first;
* otherwise B should come first
**/
return aIsInvite;
+ } else if (aIsFavourite != bIsFavourite) {
+ return aIsFavourite;
} else {
return latestEventTimestamp(roomA)
> latestEventTimestamp(roomB);
}
}
);
return immer::flex_vector<std::string>(container.begin(), container.end());
};
if (tagIdStdStr.empty()) {
return sortByTimestampDesc(zug::into_vector(
toId | applyFilter,
allRooms
));
} else {
return sortByTimestampDesc(zug::into_vector(
toId | applyFilter,
roomsByTagMap[tagIdStdStr]
));
}
}))
, LAGER_QT(filter)(m_filter.xform(strToQt, qStringToStd))
, LAGER_QT(roomIds)(m_roomIds.xform(zug::map(
[](auto container) {
return zug::into(QStringList{}, strToQt, std::move(container));
})))
{
initCountCursor(m_roomIds.xform(containerSize));
}
MatrixRoomList::~MatrixRoomList() = default;
void MatrixRoomList::setTagId(QString tagId)
{
m_tagId = tagId;
lager::commit(m_tagIdCursor);
}
MatrixRoom *MatrixRoomList::at(int index) const
{
qDebug() << "Room at index " << index << " requested";
return new MatrixRoom(
m_client.roomByCursor(
lager::with(m_roomIds, lager::make_constant(index))
.xform(zug::map([](auto ids, auto i) {
try {
return ids.at(i);
} catch (const std::out_of_range &) {
return std::string{};
}
}))),
m_client.userId());
}
QString MatrixRoomList::roomIdAt(int index) const
{
using namespace Kazv::CursorOp;
return +m_roomIds[index][lager::lenses::or_default].xform(strToQt);
}
MatrixRoom *MatrixRoomList::room(QString roomId) const
{
return new MatrixRoom(
m_client.room(roomId.toStdString()),
m_client.userId(),
userGivenNicknameMapFor(m_client)
);
}
diff --git a/src/tests/matrix-room-list-test.cpp b/src/tests/matrix-room-list-test.cpp
index fc2e8c2..88cb0d4 100644
--- a/src/tests/matrix-room-list-test.cpp
+++ b/src/tests/matrix-room-list-test.cpp
@@ -1,209 +1,234 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <memory>
#include <QtTest>
#include <matrix-room-timeline.hpp>
#include <matrix-sdk.hpp>
#include <matrix-room-list.hpp>
#include <matrix-room.hpp>
#include <matrix-event.hpp>
#include <matrix-utils.hpp>
#include "test-model.hpp"
#include "test-utils.hpp"
#include "factory.hpp"
using namespace Kazv;
using namespace Kazv::Factory;
class MatrixRoomListTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testRoomList();
void testSorted();
void testSortedWithTag();
void testFilter();
+ void testPriority();
};
static auto tagEvent = Event{R"({
"content": {
"tags": {"m.favourite": {}}
},
"type": "m.tag"
})"_json};
void MatrixRoomListTest::testRoomList()
{
auto model = makeTestModel();
RoomModel room;
room.roomId = "!test:tusooa.xyz";
room.accountData = room.accountData.set("m.tag", tagEvent);
model.client.roomList.rooms = model.client.roomList.rooms.set(room.roomId, room);
std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
auto roomList = toUniquePtr(sdk->roomList());
QCOMPARE(roomList->count(), 2);
roomList->setTagId("m.favourite");
QCOMPARE(roomList->count(), 1);
roomList->setTagId("u.xxx");
QCOMPARE(roomList->count(), 0);
}
void MatrixRoomListTest::testSorted()
{
auto model = makeTestModel();
model.client = makeClient();
auto room1 = makeRoom(
withRoomTimeline({
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 1000)),
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 2000))
})
| withRoomMembership(Join)
);
auto room2 = makeRoom(
withRoomTimeline({
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 700)),
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 1500))
})
| withRoomMembership(Join)
);
auto room3 = makeRoom(
withRoomTimeline({
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 1500)),
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 2500))
})
| withRoomMembership(Join)
);
auto room4 = makeRoom(
withRoomTimeline({
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 300)),
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 500))
})
| withRoomMembership(Invite)
);
withRoom(room1)(model.client);
withRoom(room2)(model.client);
withRoom(room3)(model.client);
withRoom(room4)(model.client);
std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
auto roomList = toUniquePtr(sdk->roomList());
QCOMPARE(roomList->roomIdAt(0), QString::fromStdString(room4.roomId));
QCOMPARE(roomList->roomIdAt(1), QString::fromStdString(room3.roomId));
QCOMPARE(roomList->roomIdAt(2), QString::fromStdString(room1.roomId));
QCOMPARE(roomList->roomIdAt(3), QString::fromStdString(room2.roomId));
}
void MatrixRoomListTest::testSortedWithTag()
{
auto model = makeTestModel();
model.client = makeClient();
auto room1 = makeRoom(
withRoomTimeline({
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 1000)),
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 2000))
})
| withRoomAccountData({tagEvent})
);
auto room2 = makeRoom(
withRoomTimeline({
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 700)),
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 1500))
})
| withRoomAccountData({tagEvent})
);
auto room3 = makeRoom(
withRoomTimeline({
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 1500)),
makeEvent(withEventKV("/origin_server_ts"_json_pointer, 2500))
})
| withRoomAccountData({tagEvent})
);
withRoom(room1)(model.client);
withRoom(room2)(model.client);
withRoom(room3)(model.client);
std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
auto roomList = toUniquePtr(sdk->roomList());
QCOMPARE(roomList->roomIdAt(0), QString::fromStdString(room3.roomId));
QCOMPARE(roomList->roomIdAt(1), QString::fromStdString(room1.roomId));
QCOMPARE(roomList->roomIdAt(2), QString::fromStdString(room2.roomId));
}
void MatrixRoomListTest::testFilter()
{
auto model = makeTestModel();
model.client = makeClient();
auto room1 = makeRoom(
withRoomState({makeEvent(withStateKey("") | withEventType("m.room.name") | withEventContent(json{{"name", "some room"}}))})
);
auto room2 = makeRoom(
withRoomState({makeEvent(withStateKey("") | withEventType("m.room.name") | withEventContent(json{{"name", "some other room"}}))})
);
auto room3 = makeRoom(
withRoomId("!some:example.org")
| withRoomState({
makeMemberEvent(withStateKey("@foo:tusooa.xyz") | withEventKV(json::json_pointer("/content/displayname"), "User aaa")),
makeMemberEvent(withStateKey("@bar:tusooa.xyz") | withEventKV(json::json_pointer("/content/displayname"), "User bbb")),
})
);
room3.heroIds = immer::flex_vector<std::string>{"@foo:tusooa.xyz", "@bar:tusooa.xyz"};
withRoom(room1)(model.client);
withRoom(room2)(model.client);
withRoom(room3)(model.client);
auto nicknameEvent = makeEvent(
withEventType(USER_GIVEN_NICKNAME_EVENT_TYPES[0])
| withEventContent(json{{"@foo:tusooa.xyz", "xxxx"}})
);
withAccountData({ nicknameEvent })(model.client);
std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
auto roomList = toUniquePtr(sdk->roomList());
roomList->setfilter("some");
QCOMPARE(roomList->count(), 2);
QCOMPARE(roomList->roomIds().contains(QString::fromStdString(room2.roomId)), true);
QCOMPARE(roomList->roomIds().contains(QString::fromStdString(room1.roomId)), true);
roomList->setfilter("!some:example.org");
QCOMPARE(roomList->count(), 1);
QCOMPARE(roomList->roomIdAt(0), QString::fromStdString(room3.roomId));
roomList->setfilter("foo");
QCOMPARE(roomList->count(), 1);
QCOMPARE(roomList->roomIdAt(0), QString::fromStdString(room3.roomId));
roomList->setfilter("aaa");
QCOMPARE(roomList->count(), 1);
QCOMPARE(roomList->roomIdAt(0), QString::fromStdString(room3.roomId));
// By custom nickname of user (@foo:tusooa.xyz)
roomList->setfilter("xxxx");
QCOMPARE(roomList->count(), 1);
QCOMPARE(roomList->roomIdAt(0), QString::fromStdString(room3.roomId));
}
+void MatrixRoomListTest::testPriority()
+{
+ auto model = makeTestModel();
+ RoomModel roomFavourited = makeRoom(
+ withRoomId("!favourite:tusooa.xyz")
+ | withRoomAccountData(immer::flex_vector{
+ makeEvent(withEventContent(tagEvent))}));
+ RoomModel roomInvited = makeRoom(
+ withRoomId("!invite:tusooa.xyz")
+ | withRoomMembership(RoomMembership::Invite));
+ model.client.roomList.rooms = model.client.roomList.rooms.set(
+ roomInvited.roomId, roomInvited);
+ model.client.roomList.rooms = model.client.roomList.rooms.set(
+ roomFavourited.roomId, roomFavourited);
+
+ std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
+ auto roomList = toUniquePtr(sdk->roomList());
+
+ QCOMPARE(roomList->count(), 3);
+ // Priority: Invite > Favourite
+ QCOMPARE(roomList->roomIdAt(0), "!invite:tusooa.xyz");
+ QCOMPARE(roomList->roomIdAt(1), "!favourite:tusooa.xyz");
+}
+
QTEST_MAIN(MatrixRoomListTest)
#include "matrix-room-list-test.moc"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Oct 8, 8:50 PM (12 h, 36 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784273
Default Alt Text
(15 KB)
Attached To
Mode
rK kazv
Attached
Detach File
Event Timeline
Log In to Comment