Page MenuHomePhorge

D41.1726798381.diff
No OneTemporary

D41.1726798381.diff

diff --git a/src/contents/ui/Bubble.qml b/src/contents/ui/Bubble.qml
--- a/src/contents/ui/Bubble.qml
+++ b/src/contents/ui/Bubble.qml
@@ -25,7 +25,7 @@
property var backgroundItem: Rectangle {
anchors.fill: parent
- color: hovered ? Kirigami.Theme.activeBackgroundColor : Kirigami.Theme.backgroundColor
+ color: (hovered || isSelected) ? Kirigami.Theme.activeBackgroundColor : Kirigami.Theme.backgroundColor
}
background: compactMode ? null : backgroundItem
@@ -106,13 +106,15 @@
ColumnLayout {
Layout.fillWidth: true
- property var inReplyTo: Item {
+ property var inReplyTo: MouseArea {
Layout.fillWidth: true
Layout.margins: 0
Layout.minimumHeight: Math.min(inReplyToLayout.implicitHeight, Kirigami.Units.gridUnit * 5)
Layout.maximumHeight: Kirigami.Units.gridUnit * 5
clip: true
+ onClicked: eventListView.goToEvent(bubbleRootLayout.replyToOrAnnotatedEventId)
+
RowLayout {
id: inReplyToLayout
anchors {
diff --git a/src/contents/ui/EventView.qml b/src/contents/ui/EventView.qml
--- a/src/contents/ui/EventView.qml
+++ b/src/contents/ui/EventView.qml
@@ -21,6 +21,7 @@
property var prevEventType: prevEvent ? getMessageType(prevEvent) : 'ignore'
property var sender
property var stateKeyUser
+ property var isSelected: false
property var messageType: getMessageType(event)
@@ -45,6 +46,7 @@
Types.Text {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -53,6 +55,7 @@
Types.Emote {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -61,6 +64,7 @@
Types.Notice {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -70,6 +74,7 @@
event: eventView.event
sender: eventView.sender
stateKeyUser: eventView.stateKeyUser
+ isSelected: eventView.isSelected
}
}
@@ -78,6 +83,7 @@
Types.Image {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -86,6 +92,7 @@
Types.File {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -94,6 +101,7 @@
Types.Video {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -102,6 +110,7 @@
Types.Audio {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -110,6 +119,7 @@
Types.Fallback {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -118,6 +128,7 @@
Types.Redacted {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
@@ -126,6 +137,7 @@
Types.Reaction {
event: eventView.event
sender: eventView.sender
+ isSelected: eventView.isSelected
}
}
diff --git a/src/contents/ui/RoomTimelineView.qml b/src/contents/ui/RoomTimelineView.qml
--- a/src/contents/ui/RoomTimelineView.qml
+++ b/src/contents/ui/RoomTimelineView.qml
@@ -27,8 +27,16 @@
verticalLayoutDirection: ListView.BottomToTop
delegate: EventViewWrapper {
+ property var eventListView: ListView.view
prevEvent: index < timeline.count - 1 ? timeline.at(index + 1) : undefined
event: timeline.at(index)
width: ListView.view.width
+ isSelected: event.eventId === roomTimelineView.selectedEventId
+ }
+
+ function goToEvent(eventId) {
+ selectedEventId = eventId;
+ const index = timeline.indexOfEvent(eventId);
+ positionViewAtIndex(index, ListView.Center);
}
}
diff --git a/src/contents/ui/event-types/Simple.qml b/src/contents/ui/event-types/Simple.qml
--- a/src/contents/ui/event-types/Simple.qml
+++ b/src/contents/ui/event-types/Simple.qml
@@ -17,6 +17,7 @@
property var sender
default property var children
+ property var isSelected
property var nameProvider: Kazv.UserNameProvider {
user: sender
diff --git a/src/matrix-room-timeline.hpp b/src/matrix-room-timeline.hpp
--- a/src/matrix-room-timeline.hpp
+++ b/src/matrix-room-timeline.hpp
@@ -45,6 +45,8 @@
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-room-timeline.cpp b/src/matrix-room-timeline.cpp
--- a/src/matrix-room-timeline.cpp
+++ b/src/matrix-room-timeline.cpp
@@ -86,6 +86,35 @@
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();
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -28,4 +28,11 @@
TEST_NAME quicktest
LINK_LIBRARIES Qt5::QuickTest kazvtestlib
)
+
+if(KAZV_BUILD_BENCHMARKS)
+ ecm_add_test(
+ matrix-room-timeline-benchmark-test.cpp
+ LINK_LIBRARIES Qt5::Test kazvtestlib
+ )
+endif()
set_tests_properties(quicktest PROPERTIES WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
diff --git a/src/tests/matrix-room-timeline-benchmark-test.cpp b/src/tests/matrix-room-timeline-benchmark-test.cpp
new file mode 100644
--- /dev/null
+++ b/src/tests/matrix-room-timeline-benchmark-test.cpp
@@ -0,0 +1,102 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2023 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <libkazv-config.hpp>
+#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
+
+#include <memory>
+
+#include <QtTest>
+#include <QRandomGenerator>
+
+#include <factory.hpp>
+
+#include <matrix-room-timeline.hpp>
+#include <matrix-sdk.hpp>
+#include <matrix-room-list.hpp>
+#include <matrix-room.hpp>
+
+#include "test-model.hpp"
+#include "test-utils.hpp"
+
+using namespace Kazv;
+using namespace Kazv::Factory;
+
+Q_DECLARE_METATYPE(EventList)
+
+static int eventListMetaType{qRegisterMetaType<EventList>("EventList")};
+
+class MatrixRoomTimelineBenchmarkTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum IndexType {
+ Random,
+ CloseToLatest,
+ Nonexistent,
+ };
+
+ Q_ENUM(IndexType)
+
+private Q_SLOTS:
+ void testIndexOfEvent_data();
+ void testIndexOfEvent();
+};
+
+void MatrixRoomTimelineBenchmarkTest::testIndexOfEvent_data()
+{
+ QTest::addColumn<EventList>("events");
+ QTest::addColumn<IndexType>("type");
+
+ auto events = EventList{};
+ for (int i = 0; i < 10000; ++i) {
+ events = std::move(events).push_back(makeEvent(withEventId("$" + std::to_string(i)) | withEventKV("/origin_server_ts"_json_pointer, i * 1000)));
+ }
+
+ QTest::addRow("random event") << events << Random;
+ QTest::addRow("event close to the latest") << events << CloseToLatest;
+ QTest::addRow("nonexistent event") << events << Nonexistent;
+}
+
+void MatrixRoomTimelineBenchmarkTest::testIndexOfEvent()
+{
+ QFETCH(EventList, events);
+ QFETCH(IndexType, type);
+
+ auto r = makeRoom(withRoomTimeline(events));
+ r.localEchoes = {
+ LocalEchoDesc{"txn1", makeEvent(withEventId("")), LocalEchoDesc::Sending},
+ LocalEchoDesc{"txn2", makeEvent(withEventId("")), LocalEchoDesc::Sending},
+ };
+
+ auto model = SdkModel{makeClient(withRoom(r))};
+ std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
+ auto roomList = toUniquePtr(sdk->roomList());
+ auto room = toUniquePtr(roomList->room(QString::fromStdString(r.roomId)));
+ auto timeline = toUniquePtr(room->timeline());
+
+ if (type == Random) {
+ QRandomGenerator g;
+ const int highest = events.size();
+ QBENCHMARK {
+ timeline->indexOfEvent(QString::fromStdString(
+ "$" + std::to_string(g.bounded(highest))));
+ }
+ } else if (type == CloseToLatest) {
+ QBENCHMARK {
+ timeline->indexOfEvent("$9940");
+ }
+ } else if (type == Nonexistent) {
+ QBENCHMARK {
+ timeline->indexOfEvent("$xxx");
+ }
+ }
+}
+
+QTEST_MAIN(MatrixRoomTimelineBenchmarkTest)
+
+#include "matrix-room-timeline-benchmark-test.moc"
diff --git a/src/tests/matrix-room-timeline-test.cpp b/src/tests/matrix-room-timeline-test.cpp
--- a/src/tests/matrix-room-timeline-test.cpp
+++ b/src/tests/matrix-room-timeline-test.cpp
@@ -35,6 +35,7 @@
void testLocalEcho();
void testReadReceipts();
void testEdits();
+ void testIndexOfEvent();
};
void MatrixRoomTimelineTest::testLocalEcho()
@@ -128,6 +129,34 @@
QVERIFY(event->isEdited());
}
+void MatrixRoomTimelineTest::testIndexOfEvent()
+{
+ // reverse index
+ // 4 3 2 1 0
+ // event/txnId
+ // $1 $2 $3 txn1 txn2
+ auto r = makeRoom(withRoomTimeline({
+ makeEvent(withEventId("$1") | withEventKV("/origin_server_ts"_json_pointer, 1000)),
+ makeEvent(withEventId("$2") | withEventKV("/origin_server_ts"_json_pointer, 2000)),
+ makeEvent(withEventId("$3") | withEventKV("/origin_server_ts"_json_pointer, 3000)),
+ }));
+ r.localEchoes = {
+ LocalEchoDesc{"txn1", makeEvent(withEventId("")), LocalEchoDesc::Sending},
+ LocalEchoDesc{"txn2", makeEvent(withEventId("")), LocalEchoDesc::Sending},
+ };
+
+ auto model = SdkModel{makeClient(withRoom(r))};
+ std::unique_ptr<MatrixSdk> sdk{makeTestSdk(model)};
+ auto roomList = toUniquePtr(sdk->roomList());
+ auto room = toUniquePtr(roomList->room(QString::fromStdString(r.roomId)));
+ auto timeline = toUniquePtr(room->timeline());
+
+ QCOMPARE(timeline->indexOfEvent("$1"), 4);
+ QCOMPARE(timeline->indexOfEvent("$2"), 3);
+ QCOMPARE(timeline->indexOfEvent("$3"), 2);
+ QCOMPARE(timeline->indexOfEvent("$4"), -1);
+}
+
QTEST_MAIN(MatrixRoomTimelineTest)
#include "matrix-room-timeline-test.moc"

File Metadata

Mime Type
text/plain
Expires
Thu, Sep 19, 7:13 PM (4 h, 13 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16134
Default Alt Text
D41.1726798381.diff (10 KB)

Event Timeline