Page MenuHomePhorge

No OneTemporary

Size
43 KB
Referenced Files
None
Subscribers
None
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index efcaf6b..41d1d8a 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,99 +1,100 @@
if(WIN32)
set(KAZV_IS_WINDOWS 1)
else()
set(KAZV_IS_WINDOWS 0)
endif()
configure_file(kazv-version.cpp.in kazv-version.cpp)
configure_file(kazv-platform.hpp.in kazv-platform.hpp)
configure_file(kazv-defs.hpp.in kazv-defs.hpp)
set(kazvprivlib_SRCS
qt-job-handler.cpp
qt-job.cpp
${CMAKE_CURRENT_BINARY_DIR}/kazv-version.cpp
matrix-sdk.cpp
matrix-room.cpp
matrix-room-list.cpp
matrix-room-timeline.cpp
matrix-room-member.cpp
matrix-room-member-list-model.cpp
matrix-event-reader.cpp
matrix-event-reader-list-model.cpp
matrix-event.cpp
matrix-link.cpp
meta-types.cpp
l10n-provider.cpp
qt-rand-adapter.cpp
helper.cpp
matrix-promise.cpp
kazv-util.cpp
matrix-sticker-pack.cpp
matrix-sticker.cpp
matrix-sticker-pack-list.cpp
matrix-sticker-pack-source.cpp
matrix-user-given-attrs-map.cpp
+ kazv-abstract-list-model.cpp
qt-promise-handler.cpp
device-mgmt/matrix-device.cpp
device-mgmt/matrix-device-list.cpp
kazv-config.cpp
kazv-path-config.cpp
kazv-io-manager.cpp
kazv-io-job.cpp
kazv-file.cpp
upload-job-model.cpp
kazv-markdown.cpp
shortcuts/shortcut-util.cpp
register-types.cpp
)
ecm_qt_declare_logging_category(kazvprivlib_SRCS
HEADER kazv-log.hpp
IDENTIFIER kazvLog
CATEGORY_NAME moe.kazv.mxc.kazv
)
add_library(kazvprivlib STATIC ${kazvprivlib_SRCS})
target_link_libraries(kazvprivlib PUBLIC
libkazv::kazvall
Qt${QT_MAJOR_VERSION}::Core
Qt${QT_MAJOR_VERSION}::Network
Threads::Threads
Qt${QT_MAJOR_VERSION}::Gui
Qt${QT_MAJOR_VERSION}::Qml
Qt${QT_MAJOR_VERSION}::Quick
Qt${QT_MAJOR_VERSION}::QuickControls2
Qt${QT_MAJOR_VERSION}::Svg
Qt${QT_MAJOR_VERSION}::Concurrent
Qt${QT_MAJOR_VERSION}::Widgets
KF${KF_MAJOR_VERSION}::ConfigCore KF${KF_MAJOR_VERSION}::KIOCore
KF${KF_MAJOR_VERSION}::Notifications
${CMARK_TARGET_NAME}
)
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
target_include_directories(kazvprivlib PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/device-mgmt)
set(kazv_SRCS
main.cpp
resources.qrc
l10n/translations.qrc
)
add_executable(kazv ${kazv_SRCS})
target_link_libraries(kazv
PRIVATE
kazvprivlib
)
set(kazv_INSTALL_ARGS ${KF_INSTALL_TARGETS_DEFAULT_ARGS})
if(NOT kazv_INSTALL_ARGS)
set(kazv_INSTALL_ARGS ${KF${KF_MAJOR_VERSION}_INSTALL_TARGETS_DEFAULT_ARGS})
endif()
install(TARGETS kazv ${kazv_INSTALL_ARGS})
add_subdirectory(tests)
diff --git a/src/kazv-abstract-list-model.cpp b/src/kazv-abstract-list-model.cpp
new file mode 100644
index 0000000..222fdb9
--- /dev/null
+++ b/src/kazv-abstract-list-model.cpp
@@ -0,0 +1,76 @@
+/*
+ * 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 <QTimer>
+#include <lager/constant.hpp>
+#include "kazv-abstract-list-model.hpp"
+
+KazvAbstractListModel::KazvAbstractListModel(lager::reader<int> countArg, InitMethod initMethod, QObject *parent)
+ : QAbstractListModel(parent)
+ , m_internalCount(0)
+{
+ connect(this, &KazvAbstractListModel::countChanged, this, &KazvAbstractListModel::updateInternalCount);
+
+ initCountCursor(countArg, initMethod);
+}
+
+KazvAbstractListModel::KazvAbstractListModel(QObject *parent)
+ : QAbstractListModel(parent)
+ , m_internalCount(0)
+ , LAGER_QT(count)(lager::make_constant(0))
+{
+ connect(this, &KazvAbstractListModel::countChanged, this, &KazvAbstractListModel::updateInternalCount);
+}
+
+KazvAbstractListModel::~KazvAbstractListModel() = default;
+
+void KazvAbstractListModel::initCountCursor(lager::reader<int> countArg, InitMethod initMethod)
+{
+ LAGER_QT(count) = countArg;
+
+ if (initMethod == InitNow) {
+ m_internalCount = count();
+ } else {
+ // The following is taken from MatrixRoomTimeline.
+ // HACK: If internally the count is set to its full count at the beginning,
+ // ListView will instantiate everything in the timeline.
+ // I don't know why it is the case, so I keep the internal count as 0
+ // when it is constructed, and update it in the next tick.
+ QTimer::singleShot(0, this, [this] { Q_EMIT countChanged(count()); });
+ }
+}
+
+QVariant KazvAbstractListModel::data(const QModelIndex &/* index */, int /* role */) const
+{
+ return QVariant();
+}
+
+int KazvAbstractListModel::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid()) {
+ return 0;
+ } else {
+ return m_internalCount;
+ }
+}
+
+void KazvAbstractListModel::updateInternalCount()
+{
+ auto curCount = count();
+ auto oldCount = m_internalCount;
+ auto diff = std::abs(curCount - oldCount);
+
+ if (curCount > oldCount) {
+ beginInsertRows(QModelIndex(), 0, diff - 1);
+ m_internalCount = curCount;
+ endInsertRows();
+ } else if (curCount < oldCount) {
+ beginRemoveRows(QModelIndex(), 0, diff - 1);
+ m_internalCount = curCount;
+ endRemoveRows();
+ }
+}
diff --git a/src/kazv-abstract-list-model.hpp b/src/kazv-abstract-list-model.hpp
new file mode 100644
index 0000000..d85d61f
--- /dev/null
+++ b/src/kazv-abstract-list-model.hpp
@@ -0,0 +1,40 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#pragma once
+#include <kazv-defs.hpp>
+#include <QObject>
+#include <QQmlEngine>
+#include <QAbstractListModel>
+#include <lager/extra/qt.hpp>
+
+class KazvAbstractListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ int m_internalCount;
+
+public:
+ enum InitMethod {
+ InitNow,
+ InitLater,
+ };
+
+ explicit KazvAbstractListModel(lager::reader<int> count, InitMethod initMethod = InitNow, QObject *parent = 0);
+ explicit KazvAbstractListModel(QObject *parent = 0);
+ ~KazvAbstractListModel() override;
+
+ LAGER_QT_READER(int, count);
+
+ int rowCount(const QModelIndex &index) const override;
+ QVariant data(const QModelIndex &index, int role) const override;
+
+protected:
+ void initCountCursor(lager::reader<int> count, InitMethod initMethod = InitNow);
+
+private Q_SLOTS:
+ void updateInternalCount();
+};
diff --git a/src/matrix-room-list.cpp b/src/matrix-room-list.cpp
index e6d4b0c..9c9b7e5 100644
--- a/src/matrix-room-list.cpp
+++ b/src/matrix-room-list.cpp
@@ -1,225 +1,190 @@
/*
* 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)
- : QAbstractListModel(parent)
+ : KazvAbstractListModel(parent)
, m_client(client)
, m_tagId(tagId)
, m_tagIdCursor(lager::make_sensor([this] { return m_tagId.toStdString(); }))
- , m_internalCount(0)
, 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;
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 {
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(count)(m_roomIds.xform(containerSize))
, LAGER_QT(roomIds)(m_roomIds.xform(zug::map(
[](auto container) {
return zug::into(QStringList{}, strToQt, std::move(container));
})))
{
- m_internalCount = count();
-
- connect(this, &MatrixRoomList::countChanged, this, &MatrixRoomList::updateInternalCount);
+ 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)
);
}
-
-QVariant MatrixRoomList::data(const QModelIndex &/* index */, int /* role */) const
-{
- return QVariant();
-}
-
-int MatrixRoomList::rowCount(const QModelIndex &parent = QModelIndex()) const
-{
- if (parent.isValid()) {
- return 0;
- } else {
- return count();
- }
-}
-
-void MatrixRoomList::updateInternalCount()
-{
- auto curCount = count();
- auto oldCount = m_internalCount;
- auto diff = std::abs(curCount - oldCount);
-
- if (curCount > oldCount) {
- beginInsertRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endInsertRows();
- } else if (curCount < oldCount) {
- beginRemoveRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endRemoveRows();
- }
-}
diff --git a/src/matrix-room-list.hpp b/src/matrix-room-list.hpp
index 3342ac5..4527570 100644
--- a/src/matrix-room-list.hpp
+++ b/src/matrix-room-list.hpp
@@ -1,57 +1,48 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020 Tusooa Zhu <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
-#include <QAbstractListModel>
-
#include <lager/sensor.hpp>
#include <lager/state.hpp>
#include <lager/extra/qt.hpp>
#include <client/client.hpp>
+#include "kazv-abstract-list-model.hpp"
Q_MOC_INCLUDE("matrix-user-given-attrs-map.hpp")
class MatrixRoom;
class MatrixUserGivenAttrsMap;
-class MatrixRoomList : public QAbstractListModel
+class MatrixRoomList : public KazvAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Kazv::Client m_client;
QString m_tagId;
lager::sensor<std::string> m_tagIdCursor;
- int m_internalCount;
lager::state<std::string, lager::automatic_tag> m_filter;
lager::reader<Kazv::JsonWrap> m_userGivenNicknameMap;
lager::reader<immer::flex_vector<std::string>> m_roomIds;
public:
explicit MatrixRoomList(Kazv::Client client, QString tagId = QString(), QString filter = QString(), QObject *parent = 0);
~MatrixRoomList() override;
Q_INVOKABLE void setTagId(QString tagId);
LAGER_QT_CURSOR(QString, filter);
- LAGER_QT_READER(int, count);
LAGER_QT_READER(QStringList, roomIds);
Q_INVOKABLE MatrixRoom *at(int index) const;
Q_INVOKABLE QString roomIdAt(int index) const;
Q_INVOKABLE MatrixRoom *room(QString roomId) const;
-
- QVariant data(const QModelIndex &index, int role) const override;
- int rowCount(const QModelIndex &parent) const override;
-
-private Q_SLOTS:
- void updateInternalCount();
};
diff --git a/src/matrix-room-member-list-model.cpp b/src/matrix-room-member-list-model.cpp
index 724c255..daef5bc 100644
--- a/src/matrix-room-member-list-model.cpp
+++ b/src/matrix-room-member-list-model.cpp
@@ -1,99 +1,63 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <cmath>
#include <lager/lenses/optional.hpp>
#include <cursorutil.hpp>
#include "helper.hpp"
#include "matrix-room-member-list-model.hpp"
#include "matrix-room-member.hpp"
using namespace Kazv;
MatrixRoomMemberListModel::MatrixRoomMemberListModel(lager::reader<Kazv::EventList> members, QString filter, lager::reader<Kazv::Event> userGivenNicknameEvent, QObject *parent)
- : QAbstractListModel(parent)
+ : KazvAbstractListModel(parent)
, m_filter(lager::make_state(filter.toStdString(), lager::automatic_tag{}))
, m_members(lager::with(
members,
m_filter,
userGivenNicknameEvent.map(&Event::content)
).map([](const auto &members, const auto &filter, const auto &map) {
if (filter.empty()) {
return members;
}
return intoImmer(EventList{}, zug::filter([filter, map=map.get()](const auto &e) {
auto userId = e.stateKey();
if (userId.find(filter) != std::string::npos) {
return true;
}
// user given nickname match
auto content = e.content().get();
if (map.contains(userId) && map[userId].is_string()) {
auto name = map[userId].template get<std::string>();
if (name.find(filter) != std::string::npos) {
return true;
}
}
if (content.contains("displayname")
&& content["displayname"].is_string()) {
auto name = content["displayname"].template get<std::string>();
return name.find(filter) != std::string::npos;
}
return false;
}), members);
}))
- , m_internalCount(0)
, LAGER_QT(filter)(m_filter.xform(strToQt, qStringToStd))
- , LAGER_QT(count)(m_members.map([](const auto &m) -> int { return m.size(); }))
{
- m_internalCount = count();
-
- connect(this, &MatrixRoomMemberListModel::countChanged,
- this, &MatrixRoomMemberListModel::updateInternalCount);
+ initCountCursor(m_members.map([](const auto &m) -> int { return m.size(); }));
}
MatrixRoomMemberListModel::~MatrixRoomMemberListModel() = default;
MatrixRoomMember *MatrixRoomMemberListModel::at(int index) const
{
return new MatrixRoomMember(m_members[index][lager::lenses::or_default]);
}
-
-int MatrixRoomMemberListModel::rowCount(const QModelIndex &index) const
-{
- if (index.isValid()) {
- return 0;
- } else {
- return count();
- }
-}
-
-QVariant MatrixRoomMemberListModel::data(const QModelIndex &, int) const
-{
- return QVariant();
-}
-
-void MatrixRoomMemberListModel::updateInternalCount()
-{
- auto curCount = count();
- auto oldCount = m_internalCount;
- auto diff = std::abs(curCount - oldCount);
-
- if (curCount > oldCount) {
- beginInsertRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endInsertRows();
- } else if (curCount < oldCount) {
- beginRemoveRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endRemoveRows();
- }
-}
diff --git a/src/matrix-room-member-list-model.hpp b/src/matrix-room-member-list-model.hpp
index 0d38d57..bed8eb7 100644
--- a/src/matrix-room-member-list-model.hpp
+++ b/src/matrix-room-member-list-model.hpp
@@ -1,44 +1,35 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
-#include <QAbstractListModel>
-
#include <lager/extra/qt.hpp>
#include <lager/state.hpp>
#include <client/room/room.hpp>
+#include "kazv-abstract-list-model.hpp"
class MatrixRoomMember;
-class MatrixRoomMemberListModel : public QAbstractListModel
+class MatrixRoomMemberListModel : public KazvAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
lager::state<std::string, lager::automatic_tag> m_filter;
lager::reader<Kazv::EventList> m_members;
- int m_internalCount;
public:
explicit MatrixRoomMemberListModel(lager::reader<Kazv::EventList> members, QString filter = QString(), lager::reader<Kazv::Event> userGivenNicknameEvent = lager::make_constant(Kazv::Event()), QObject *parent = 0);
~MatrixRoomMemberListModel() override;
LAGER_QT_CURSOR(QString, filter);
- LAGER_QT_READER(int, count);
-
- int rowCount(const QModelIndex &index) const override;
- QVariant data(const QModelIndex &index, int role) const override;
Q_INVOKABLE MatrixRoomMember *at(int index) const;
-
-private Q_SLOTS:
- void updateInternalCount();
};
diff --git a/src/matrix-room-timeline.cpp b/src/matrix-room-timeline.cpp
index 7ef5fd5..814d90a 100644
--- a/src/matrix-room-timeline.cpp
+++ b/src/matrix-room-timeline.cpp
@@ -1,132 +1,94 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <cmath>
#include <lager/lenses/optional.hpp>
#include <QTimer>
#include <cursorutil.hpp>
#include "matrix-room-timeline.hpp"
#include "matrix-event.hpp"
#include "helper.hpp"
#include "kazv-log.hpp"
using namespace Kazv;
MatrixRoomTimeline::MatrixRoomTimeline(Kazv::Room room, QObject *parent)
- : QAbstractListModel(parent)
+ : KazvAbstractListModel(parent)
, m_room(room)
, m_timelineEventIds(m_room.timelineEventIds())
, m_messagesMap(m_room.messagesMap())
- , m_internalCount(0)
, m_localEchoes(m_room.localEchoes())
, m_timelineGaps(m_room.timelineGaps())
- , LAGER_QT(count)(lager::with(
- m_timelineEventIds.xform(containerSize),
- m_localEchoes.xform(containerSize)
- ).map([](const auto &tlSize, const auto &localSize) { return tlSize + localSize; }))
, LAGER_QT(gaps)(m_timelineGaps.map([](auto g) {
QSet<QString> res{};
for (const auto &[k, _v] : g) {
res.insert(QString::fromStdString(k));
}
return res;
}))
{
- connect(this, &MatrixRoomTimeline::countChanged, this, &MatrixRoomTimeline::updateInternalCount);
- // HACK: If internally the count is set to its full count at the beginning,
- // ListView will instantiate everything in the timeline.
- // I don't know why it is the case, so I keep the internal count as 0
- // when it is constructed, and update it in the next tick.
- QTimer::singleShot(0, this, [this] { Q_EMIT countChanged(count()); });
+ initCountCursor(lager::with(
+ m_timelineEventIds.xform(containerSize),
+ m_localEchoes.xform(containerSize)
+ ).map([](const auto &tlSize, const auto &localSize) { return tlSize + localSize; }), InitLater);
}
MatrixRoomTimeline::~MatrixRoomTimeline() = default;
MatrixEvent *MatrixRoomTimeline::at(int index) const
{
return new MatrixEvent(lager::with(m_timelineEventIds, m_messagesMap, m_localEchoes, LAGER_QT(count))
.map([index](const auto &tlEventIds, const auto &messagesMap, const auto &localEchoes, const auto &count) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> {
auto rIndex = count - index - 1;
if (static_cast<unsigned int>(rIndex) < tlEventIds.size()) {
return messagesMap[tlEventIds[rIndex]];
} else {
auto remainingIndex = rIndex - tlEventIds.size();
if (remainingIndex < localEchoes.size()) {
return localEchoes[remainingIndex];
} else {
return Event();
}
}
}), m_room);
}
-int MatrixRoomTimeline::rowCount(const QModelIndex &index) const
-{
- if (index.isValid()) {
- return 0;
- } else {
- return m_internalCount;
- }
-}
-
-QVariant MatrixRoomTimeline::data(const QModelIndex &, int) const
-{
- return QVariant();
-}
-
int MatrixRoomTimeline::indexOfEvent(const QString &eventId) const
{
auto eventMap = m_messagesMap.get();
auto eventIdStd = eventId.toStdString();
if (eventMap.count(eventIdStd) == 0) {
return -1;
}
auto eventIds = m_timelineEventIds.get();
auto it = std::lower_bound(eventIds.begin(), eventIds.end(), eventIdStd,
[eventMap](const auto &idA, const auto &idB) {
auto a = std::make_pair(eventMap[idA].originServerTs(), idA);
auto b = std::make_pair(eventMap[idB].originServerTs(), idB);
return a < b;
}
);
if (it == eventIds.end()) {
return -1;
} else {
// suppose there are A events and B local echoes
// forward index:
// [0, 1, ..., A-1] [A, A+1, ..., A+B-1]
// reverse index:
// [A+B-1, A+B-2, ..., B] [B-1, B-2 , 0]
// forward = it.index()
// forward + reverse = A+B-1 = count() - 1
return count() - it.index() - 1;
}
}
-
-void MatrixRoomTimeline::updateInternalCount()
-{
- auto curCount = count();
- auto oldCount = m_internalCount;
- auto diff = std::abs(curCount - oldCount);
-
- if (curCount > oldCount) {
- beginInsertRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endInsertRows();
- } else if (curCount < oldCount) {
- beginRemoveRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endRemoveRows();
- }
-}
diff --git a/src/matrix-room-timeline.hpp b/src/matrix-room-timeline.hpp
index 9abeeb5..88ef1c0 100644
--- a/src/matrix-room-timeline.hpp
+++ b/src/matrix-room-timeline.hpp
@@ -1,50 +1,42 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
-#include <QAbstractListModel>
#include <lager/extra/qt.hpp>
#include <client/room/room.hpp>
+#include "kazv-abstract-list-model.hpp"
class MatrixEvent;
-class MatrixRoomTimeline : public QAbstractListModel
+class MatrixRoomTimeline : public KazvAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Kazv::Room m_room;
lager::reader<immer::flex_vector<std::string>> m_timelineEventIds;
lager::reader<immer::map<std::string, Kazv::Event>> m_messagesMap;
- int m_internalCount;
lager::reader<immer::flex_vector<Kazv::LocalEchoDesc>> m_localEchoes;
lager::reader<immer::map<std::string, std::string>> m_timelineGaps;
public:
explicit MatrixRoomTimeline(Kazv::Room room, QObject *parent = 0);
~MatrixRoomTimeline() override;
- LAGER_QT_READER(int, count);
LAGER_QT_READER(QSet<QString>, gaps);
- int rowCount(const QModelIndex &index) const override;
- QVariant data(const QModelIndex &index, int role) const override;
-
Q_INVOKABLE MatrixEvent *at(int index) const;
Q_INVOKABLE int indexOfEvent(const QString &eventId) const;
-
-private Q_SLOTS:
- void updateInternalCount();
};
diff --git a/src/matrix-sticker-pack-list.cpp b/src/matrix-sticker-pack-list.cpp
index 6fccff3..3380ce6 100644
--- a/src/matrix-sticker-pack-list.cpp
+++ b/src/matrix-sticker-pack-list.cpp
@@ -1,89 +1,54 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <lager/lenses/optional.hpp>
#include <lager/lenses/at.hpp>
#include "matrix-sticker-pack.hpp"
#include "matrix-sticker-pack-list.hpp"
using namespace Kazv;
static const std::string accountDataEventType = "im.ponies.user_emotes";
lager::reader<immer::flex_vector<MatrixStickerPackSource>> getEventsFromClient(Client client)
{
lager::reader<Event> userEmotes = client.accountData()[accountDataEventType][lager::lenses::or_default];
return userEmotes.map([](Event e) {
return immer::flex_vector<MatrixStickerPackSource>{
{MatrixStickerPackSource::AccountData, accountDataEventType, e},
};
});
}
MatrixStickerPackList::MatrixStickerPackList(Client client, QObject *parent)
- : QAbstractListModel(parent)
+ : KazvAbstractListModel(parent)
, m_client(client)
, m_events(getEventsFromClient(m_client))
- , m_internalCount(0)
- , LAGER_QT(count)(m_events.map([](const auto &events) {
- return static_cast<int>(events.size());
- }))
{
- m_internalCount = count();
-
- connect(this, &MatrixStickerPackList::countChanged, this, &MatrixStickerPackList::updateInternalCount);
+ initCountCursor(m_events.map([](const auto &events) {
+ return static_cast<int>(events.size());
+ }));
}
MatrixStickerPackList::~MatrixStickerPackList() = default;
MatrixStickerPack *MatrixStickerPackList::at(int index) const
{
return new MatrixStickerPack(m_events.map([index](const auto &events) {
if (events.size() > std::size_t(index)) {
return events[index];
} else {
return MatrixStickerPackSource{
MatrixStickerPackSource::AccountData,
accountDataEventType,
Event(),
};
}
}));
}
-
-QVariant MatrixStickerPackList::data(const QModelIndex &/* index */, int /* role */) const
-{
- return QVariant();
-}
-
-int MatrixStickerPackList::rowCount(const QModelIndex &parent = QModelIndex()) const
-{
- if (parent.isValid()) {
- return 0;
- } else {
- return count();
- }
-}
-
-void MatrixStickerPackList::updateInternalCount()
-{
- auto curCount = count();
- auto oldCount = m_internalCount;
- auto diff = std::abs(curCount - oldCount);
-
- if (curCount > oldCount) {
- beginInsertRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endInsertRows();
- } else if (curCount < oldCount) {
- beginRemoveRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endRemoveRows();
- }
-}
diff --git a/src/matrix-sticker-pack-list.hpp b/src/matrix-sticker-pack-list.hpp
index a8ce356..cbb6d96 100644
--- a/src/matrix-sticker-pack-list.hpp
+++ b/src/matrix-sticker-pack-list.hpp
@@ -1,50 +1,40 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
-#include <QAbstractListModel>
#include <immer/flex_vector.hpp>
#include <lager/reader.hpp>
#include <lager/extra/qt.hpp>
#include <client/client.hpp>
#include <base/event.hpp>
-
+#include "kazv-abstract-list-model.hpp"
#include "matrix-sticker-pack-source.hpp"
Q_MOC_INCLUDE("matrix-sticker-pack.hpp")
class MatrixStickerPack;
-class MatrixStickerPackList : public QAbstractListModel
+class MatrixStickerPackList : public KazvAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Kazv::Client m_client;
lager::reader<immer::flex_vector<MatrixStickerPackSource>> m_events;
- int m_internalCount;
public:
explicit MatrixStickerPackList(Kazv::Client client, QObject *parent = 0);
~MatrixStickerPackList() override;
- LAGER_QT_READER(int, count);
-
Q_INVOKABLE MatrixStickerPack *at(int index) const;
-
- QVariant data(const QModelIndex &index, int role) const override;
- int rowCount(const QModelIndex &parent) const override;
-
-private Q_SLOTS:
- void updateInternalCount();
};
diff --git a/src/matrix-sticker-pack.cpp b/src/matrix-sticker-pack.cpp
index 4b1e9e5..22fdd08 100644
--- a/src/matrix-sticker-pack.cpp
+++ b/src/matrix-sticker-pack.cpp
@@ -1,148 +1,113 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include <kazv-defs.hpp>
#include <cursorutil.hpp>
#include "matrix-sticker.hpp"
#include "matrix-event.hpp"
#include "matrix-sticker-pack.hpp"
using namespace Kazv;
MatrixStickerPack::MatrixStickerPack(lager::reader<MatrixStickerPackSource> source, QObject *parent)
- : QAbstractListModel(parent)
+ : KazvAbstractListModel(parent)
, m_source(source)
, m_event(m_source[&MatrixStickerPackSource::event])
, m_images(m_event.xform(
eventContent
| zug::map([](const JsonWrap &content) {
if (content.get().contains("images")
&& content.get()["images"].is_object()) {
auto size = content.get()["images"].size();
auto items = content.get()["images"].items();
auto array = json(size, json::object());
std::transform(
items.begin(), items.end(),
array.begin(), [](const auto &it) {
return json::array({it.key(), it.value()});
}
);
return array;
} else {
return json::array();
}
})))
- , m_internalCount(0)
- , LAGER_QT(count)(m_images.map([](const JsonWrap &images) -> int {
- return images.get().size();
- }))
{
- m_internalCount = count();
-
- connect(this, &MatrixStickerPack::countChanged, this, &MatrixStickerPack::updateInternalCount);
+ initCountCursor(m_images.map([](const JsonWrap &images) -> int {
+ return images.get().size();
+ }));
}
MatrixStickerPack::~MatrixStickerPack() = default;
MatrixSticker *MatrixStickerPack::at(int index) const
{
using namespace nlohmann::literals;
auto image = m_images.map([index](const json &j) {
if (j.size() > std::size_t(index)) {
return j[index];
} else {
return json::array();
}
}).make();
return new MatrixSticker(
image.xform(jsonAtOr("/0"_json_pointer, json(std::string())) | zug::map([](const json &j) {
return j.template get<std::string>();
})),
image.xform(jsonAtOr("/1"_json_pointer, json::object()))
);
}
bool MatrixStickerPack::hasShortCode(const QString &shortCode) const
{
return m_event.map([shortCode=shortCode.toStdString()](const Event &e) {
auto content = e.content();
return content.get().contains("images")
&& content.get()["images"].is_object()
&& content.get()["images"].contains(shortCode);
})
.make().get();
}
-QVariant MatrixStickerPack::data(const QModelIndex &/* index */, int /* role */) const
-{
- return QVariant();
-}
-
-int MatrixStickerPack::rowCount(const QModelIndex &parent = QModelIndex()) const
-{
- if (parent.isValid()) {
- return 0;
- } else {
- return count();
- }
-}
-
-void MatrixStickerPack::updateInternalCount()
-{
- auto curCount = count();
- auto oldCount = m_internalCount;
- auto diff = std::abs(curCount - oldCount);
-
- if (curCount > oldCount) {
- beginInsertRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endInsertRows();
- } else if (curCount < oldCount) {
- beginRemoveRows(QModelIndex(), 0, diff - 1);
- m_internalCount = curCount;
- endRemoveRows();
- }
-}
-
QVariant MatrixStickerPack::addSticker(const QString &shortCode, MatrixEvent *event) const
{
if (!event) {
return QVariant::fromValue(m_source.get());
}
auto source = m_source.get();
auto eventJson = source.event.raw().get();
eventJson.merge_patch(json{
{"content", {
{"images", {
{shortCode.toStdString(), json::object()},
}},
}},
});
auto &sticker = eventJson["content"]["images"][shortCode.toStdString()];
auto content = event->content();
auto body = event->content()["body"];
if (!body.isUndefined()) {
sticker["body"] = body;
}
auto url = event->content()["url"];
if (!url.isUndefined()) {
sticker["url"] = url;
}
auto info = event->content()["info"];
if (!info.isUndefined()) {
sticker["info"] = info;
}
source.event = Event(eventJson);
return QVariant::fromValue(source);
}
diff --git a/src/matrix-sticker-pack.hpp b/src/matrix-sticker-pack.hpp
index 3301406..2edd558 100644
--- a/src/matrix-sticker-pack.hpp
+++ b/src/matrix-sticker-pack.hpp
@@ -1,63 +1,53 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <kazv-defs.hpp>
#include <QObject>
#include <QQmlEngine>
-#include <QAbstractListModel>
#include <lager/reader.hpp>
#include <lager/extra/qt.hpp>
#include <base/event.hpp>
-
+#include "kazv-abstract-list-model.hpp"
#include "matrix-sticker-pack-source.hpp"
#include "meta-types.hpp"
Q_MOC_INCLUDE("matrix-sticker.hpp")
Q_MOC_INCLUDE("matrix-event.hpp")
class MatrixSticker;
class MatrixEvent;
-class MatrixStickerPack : public QAbstractListModel
+class MatrixStickerPack : public KazvAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
lager::reader<MatrixStickerPackSource> m_source;
lager::reader<Kazv::Event> m_event;
lager::reader<Kazv::json> m_images;
- int m_internalCount;
public:
explicit MatrixStickerPack(lager::reader<MatrixStickerPackSource> source, QObject *parent = 0);
~MatrixStickerPack() override;
- LAGER_QT_READER(int, count);
-
Q_INVOKABLE MatrixSticker *at(int index) const;
Q_INVOKABLE bool hasShortCode(const QString &shortCode) const;
- QVariant data(const QModelIndex &index, int role) const override;
- int rowCount(const QModelIndex &parent) const override;
-
/**
* Add a sticker to the pack and return the source of the
* modified pack.
*
* @param shortCode The short code of the sticker.
* @param event The event of the sticker.
* @return The MatrixStickerPackSource of the modified pack.
*/
Q_INVOKABLE QVariant addSticker(const QString &shortCode, MatrixEvent *event) const;
-
-private Q_SLOTS:
- void updateInternalCount();
};
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index c69489d..2ad1a65 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,38 +1,39 @@
set(kazvtestlib_SRCS
test-model.cpp
)
add_library(kazvtestlib STATIC ${kazvtestlib_SRCS})
target_link_libraries(kazvtestlib PUBLIC kazvprivlib libkazv::kazvtestfixtures)
ecm_add_tests(
qt-job-handler-test.cpp
qt-promise-handler-test.cpp
qt-json-test.cpp
device-mgmt-test.cpp
matrix-sdk-test.cpp
matrix-promise-test.cpp
matrix-room-timeline-test.cpp
matrix-room-member-list-model-test.cpp
matrix-room-list-test.cpp
kazv-markdown-test.cpp
matrix-sticker-pack-test.cpp
matrix-link-test.cpp
matrix-user-given-attrs-map-test.cpp
+ kazv-abstract-list-model-test.cpp
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test kazvtestlib
)
ecm_add_test(
quick-test.cpp
TEST_NAME quicktest
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::QuickTest kazvtestlib
)
if(KAZV_BUILD_BENCHMARKS)
ecm_add_test(
matrix-room-timeline-benchmark-test.cpp
LINK_LIBRARIES Qt${QT_MAJOR_VERSION}::Test kazvtestlib
)
endif()
set_tests_properties(quicktest PROPERTIES WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
diff --git a/src/tests/kazv-abstract-list-model-test.cpp b/src/tests/kazv-abstract-list-model-test.cpp
new file mode 100644
index 0000000..21238df
--- /dev/null
+++ b/src/tests/kazv-abstract-list-model-test.cpp
@@ -0,0 +1,104 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2024 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <kazv-defs.hpp>
+#include <QtTest>
+#include <QSignalSpy>
+#include <lager/state.hpp>
+#include "kazv-abstract-list-model.hpp"
+
+class KazvAbstractListModelTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void testInit();
+ void testConstruct();
+ void testSignals();
+};
+
+class TestListModel : public KazvAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ TestListModel(lager::reader<int> count, InitMethod initMethod, QObject *parent = 0)
+ : KazvAbstractListModel(parent)
+ {
+ initCountCursor(count, initMethod);
+ }
+};
+
+void KazvAbstractListModelTest::testInit()
+{
+ {
+ auto count = lager::make_state(2, lager::automatic_tag{});
+ TestListModel model(count, KazvAbstractListModel::InitNow);
+ QSignalSpy spy(&model, &KazvAbstractListModel::countChanged);
+ QCOMPARE(spy.count(), 0);
+ QCOMPARE(model.count(), 2);
+ QCOMPARE(model.rowCount(QModelIndex()), 2);
+
+ count.set(5);
+ QCOMPARE(model.rowCount(QModelIndex()), 5);
+ }
+
+ {
+ auto count = lager::make_state(2, lager::automatic_tag{});
+ TestListModel model(count, KazvAbstractListModel::InitLater);
+ QSignalSpy spy(&model, &KazvAbstractListModel::countChanged);
+ QCOMPARE(model.rowCount(QModelIndex()), 0);
+ spy.wait();
+ QCOMPARE(spy.count(), 1);
+ QCOMPARE(model.count(), 2);
+ QCOMPARE(model.rowCount(QModelIndex()), 2);
+
+ count.set(5);
+ QCOMPARE(model.rowCount(QModelIndex()), 5);
+ }
+}
+
+void KazvAbstractListModelTest::testConstruct()
+{
+ auto count = lager::make_state(2, lager::automatic_tag{});
+ KazvAbstractListModel model(count, KazvAbstractListModel::InitNow);
+ QSignalSpy spy(&model, &KazvAbstractListModel::countChanged);
+ QCOMPARE(spy.count(), 0);
+ QCOMPARE(model.count(), 2);
+ QCOMPARE(model.rowCount(QModelIndex()), 2);
+
+ count.set(5);
+ QCOMPARE(model.rowCount(QModelIndex()), 5);
+}
+
+void KazvAbstractListModelTest::testSignals()
+{
+ auto count = lager::make_state(2, lager::automatic_tag{});
+ KazvAbstractListModel model(count, KazvAbstractListModel::InitNow);
+ QSignalSpy countChangedSpy(&model, &KazvAbstractListModel::countChanged);
+ QSignalSpy rowsInsertedSpy(&model, &QAbstractItemModel::rowsInserted);
+ QSignalSpy rowsRemovedSpy(&model, &QAbstractItemModel::rowsRemoved);
+
+ // 2 + 3 = 5
+ count.set(5);
+ QCOMPARE(countChangedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsInsertedSpy[0][1], 0);
+ QCOMPARE(rowsInsertedSpy[0][2], 2);
+ QCOMPARE(rowsRemovedSpy.count(), 0);
+
+ // 5 - 4 = 1
+ count.set(1);
+ QCOMPARE(countChangedSpy.count(), 2);
+ QCOMPARE(rowsInsertedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy.count(), 1);
+ QCOMPARE(rowsRemovedSpy[0][1], 0);
+ QCOMPARE(rowsRemovedSpy[0][2], 3);
+}
+
+QTEST_MAIN(KazvAbstractListModelTest)
+
+#include "kazv-abstract-list-model-test.moc"

File Metadata

Mime Type
text/x-diff
Expires
Sun, Oct 11, 1:17 PM (1 d, 14 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1785806
Default Alt Text
(43 KB)

Event Timeline