Page MenuHomePhorge

No OneTemporary

Size
15 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Thu, Oct 8, 8:50 PM (13 h, 23 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1784273
Default Alt Text
(15 KB)

Event Timeline