Page MenuHomePhorge

D325.1783494660.diff
No OneTemporary

Size
18 KB
Referenced Files
None
Subscribers
None

D325.1783494660.diff

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
@@ -268,13 +268,20 @@
}
onIsGappedChanged: maybePaginateBack()
- onEventChanged: maybePaginateBack()
- Component.onCompleted: maybePaginateBack()
+ onEventChanged: {
+ maybePaginateBack();
+ ensureEvent();
+ }
+ Component.onCompleted: {
+ maybePaginateBack();
+ ensureEvent();
+ }
Connections {
target: eventView.event
function onEventIdChanged() {
eventView.maybePaginateBack();
+ ensureEvent();
}
}
@@ -285,4 +292,10 @@
backfill();
}
}
+
+ function ensureEvent() {
+ if (!event.isAvailable) {
+ loadEventRequested(event.eventId);
+ }
+ }
}
diff --git a/src/contents/ui/RoomPage.qml b/src/contents/ui/RoomPage.qml
--- a/src/contents/ui/RoomPage.qml
+++ b/src/contents/ui/RoomPage.qml
@@ -183,6 +183,7 @@
RoomTimelineView {
id: roomTimelineView
objectName: 'roomTimelineView'
+ room: roomPage.room
timeline: roomPage.roomTimeline
}
diff --git a/src/contents/ui/RoomPinnedEventsPage.qml b/src/contents/ui/RoomPinnedEventsPage.qml
--- a/src/contents/ui/RoomPinnedEventsPage.qml
+++ b/src/contents/ui/RoomPinnedEventsPage.qml
@@ -22,6 +22,7 @@
title: l10n.get('room-pinned-events-page-title', { room: roomNameProvider.name })
RoomTimelineView {
+ room: roomPinnedEventsPage.room
timeline: roomPinnedEventsPage.pinnedEvents
}
}
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
@@ -16,7 +16,9 @@
objectName: 'roomTimelineView'
Accessible.role: Accessible.List
+ property var room
property var timeline
+ property var loadEventRequests: ({})
property string selectedEventId
@@ -24,6 +26,7 @@
signal unpinEventRequested(string eventId)
signal deleteEventRequested(eventIdOrTxnId: string, isLocalEcho: bool)
signal deleteEventRequestedNoConfirm(eventIdOrTxnId: string, isLocalEcho: bool)
+ signal loadEventRequested(string eventId)
property var pinEvent: Kazv.AsyncHandler {
property var eventIds
@@ -86,6 +89,19 @@
doDeleteEvent(eventIdOrTxnId, isLocalEcho);
}
+ onLoadEventRequested: (eventId) => {
+ if (!(eventId in loadEventRequests)) {
+ loadEventRequests[eventId] = matrixSession.loadEvent(room.roomId, eventId);
+ loadEventRequests[eventId].resolved.connect((isSuccess) => {
+ if (isSuccess) {
+ delete loadEventRequests[eventId];
+ } else {
+ loadEventRequests[eventId] = undefined;
+ }
+ });
+ }
+ }
+
function doDeleteEvent(eventIdOrTxnId, isLocalEcho) {
if (isLocalEcho) {
deleteLocalEcho.txnId = eventIdOrTxnId;
diff --git a/src/matrix-event.hpp b/src/matrix-event.hpp
--- a/src/matrix-event.hpp
+++ b/src/matrix-event.hpp
@@ -39,7 +39,7 @@
lager::reader<Kazv::EventList> m_edits;
public:
- explicit MatrixEvent(lager::reader<std::variant<Kazv::Event, Kazv::LocalEchoDesc>> event, std::optional<Kazv::Room> room = std::nullopt, QObject *parent = 0);
+ explicit MatrixEvent(lager::reader<std::variant<Kazv::Event, Kazv::LocalEchoDesc, std::string>> event, std::optional<Kazv::Room> room = std::nullopt, QObject *parent = 0);
explicit MatrixEvent(lager::reader<Kazv::Event> event, std::optional<Kazv::Room> room = std::nullopt, QObject *parent = 0);
~MatrixEvent() override;
@@ -65,6 +65,7 @@
LAGER_QT_READER(QString, formattedTime);
LAGER_QT_READER(QString, formattedDateTime);
LAGER_QT_READER(bool, isEdited);
+ LAGER_QT_READER(bool, isAvailable);
Q_INVOKABLE MatrixEventReaderListModel *readers() const;
diff --git a/src/matrix-event.cpp b/src/matrix-event.cpp
--- a/src/matrix-event.cpp
+++ b/src/matrix-event.cpp
@@ -15,25 +15,35 @@
using namespace Kazv;
-static std::optional<LocalEchoDesc> getLocalEcho(std::variant<Event, LocalEchoDesc> event)
+static std::optional<LocalEchoDesc> getLocalEcho(std::variant<Event, LocalEchoDesc, std::string> event)
{
- if (std::holds_alternative<LocalEchoDesc>(event)) {
- return std::get<LocalEchoDesc>(event);
- } else {
- return std::nullopt;
- }
+ using R = std::optional<LocalEchoDesc>;
+ return lager::match(std::move(event))(
+ [](const LocalEchoDesc &l) -> R { return std::optional(l); },
+ [](auto) -> R { return std::nullopt; }
+ );
}
-static Event getEvent(std::variant<Event, LocalEchoDesc> event)
+static Event getEvent(std::variant<Event, LocalEchoDesc, std::string> event)
{
- if (std::holds_alternative<LocalEchoDesc>(event)) {
- return std::get<LocalEchoDesc>(event).event;
- } else {
- return std::get<Event>(event);
- }
+ return lager::match(std::move(event))(
+ [](const LocalEchoDesc &l) -> Event { return l.event; },
+ [](const Event &e) -> Event { return e; },
+ [](const std::string &id) -> Event {
+ return Event(json{{"event_id", id}});
+ }
+ );
+}
+
+static bool getAvailable(std::variant<Event, LocalEchoDesc, std::string> event)
+{
+ return lager::match(std::move(event))(
+ [](const std::string &) { return false; },
+ [](auto) { return true; }
+ );
}
-MatrixEvent::MatrixEvent(lager::reader<std::variant<Event, LocalEchoDesc>> event, std::optional<Room> room, QObject *parent)
+MatrixEvent::MatrixEvent(lager::reader<std::variant<Event, LocalEchoDesc, std::string>> event, std::optional<Room> room, QObject *parent)
: QObject(parent)
, m_localEcho(event.map(getLocalEcho))
, m_event(event.map(getEvent))
@@ -143,11 +153,12 @@
);
}))
, LAGER_QT(isEdited)(m_edits.map([](const auto &edits) { return !edits.empty(); }))
+ , LAGER_QT(isAvailable)(event.map(getAvailable))
{
}
MatrixEvent::MatrixEvent(lager::reader<Event> event, std::optional<Kazv::Room> room, QObject *parent)
- : MatrixEvent(event.map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> { return e; }), room, parent)
+ : MatrixEvent(event.map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc, std::string> { return e; }), room, parent)
{
}
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
@@ -61,7 +61,7 @@
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> {
+ .map([index](const auto &tlEventIds, const auto &messagesMap, const auto &localEchoes, const auto &count) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc, std::string> {
auto rIndex = count - index - 1;
if (static_cast<unsigned int>(rIndex) < tlEventIds.size()) {
return messagesMap[tlEventIds[rIndex]];
diff --git a/src/matrix-room.hpp b/src/matrix-room.hpp
--- a/src/matrix-room.hpp
+++ b/src/matrix-room.hpp
@@ -38,6 +38,7 @@
QML_UNCREATABLE("")
Kazv::Room m_room;
+ lager::reader<immer::map<std::string, Kazv::Event>> m_messages;
lager::reader<Kazv::Event> m_userGivenNicknameEvent;
lager::reader<std::string> m_selfUserId;
lager::reader<immer::flex_vector<std::string>> m_memberNames;
diff --git a/src/matrix-room.cpp b/src/matrix-room.cpp
--- a/src/matrix-room.cpp
+++ b/src/matrix-room.cpp
@@ -42,6 +42,7 @@
MatrixRoom::MatrixRoom(Kazv::Room room, lager::reader<std::string> selfUserId, lager::reader<Kazv::Event> userGivenNicknameEvent, QObject *parent)
: QObject(parent)
, m_room(room)
+ , m_messages(room.messagesMap())
, m_userGivenNicknameEvent(userGivenNicknameEvent)
, m_selfUserId(selfUserId)
, m_memberNames(m_room.members())
@@ -455,9 +456,16 @@
MatrixEvent *MatrixRoom::messageById(QString eventId) const
{
+ auto eventIdStd = eventId.toStdString();
return new MatrixEvent(
- m_room.message(lager::make_constant(eventId.toStdString()))
- .map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> { return e; }),
+ m_messages[eventIdStd]
+ .map([eventIdStd](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc, std::string> {
+ if (e.has_value()) {
+ return e.value();
+ } else {
+ return eventIdStd;
+ }
+ }),
m_room
);
}
@@ -466,7 +474,7 @@
{
return new MatrixEvent(
m_room.localEcho(lager::make_constant(txnId.toStdString()))
- .map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> { return e; }),
+ .map([](const auto &e) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc, std::string> { return e; }),
m_room
);
}
diff --git a/src/matrix-session.hpp b/src/matrix-session.hpp
--- a/src/matrix-session.hpp
+++ b/src/matrix-session.hpp
@@ -276,6 +276,16 @@
*/
MatrixPromise *backfillRoomFromEvent(const QString &roomId, const QString &eventId);
+ /**
+ * Load one event from the store.
+ *
+ * @param roomId The id of the room to operate on.
+ * @param eventId The id of the event to load.
+ * @return A MatrixPromise that resolves when loading is finished.
+ * The Promise is considered successful if and only if the event is loaded.
+ */
+ MatrixPromise *loadEvent(const QString &roomId, const QString &eventId);
+
/**
* Import from key backup file.
*
diff --git a/src/matrix-session.cpp b/src/matrix-session.cpp
--- a/src/matrix-session.cpp
+++ b/src/matrix-session.cpp
@@ -566,6 +566,38 @@
);
}
+MatrixPromise *MatrixSession::loadEvent(const QString &roomId, const QString &eventId)
+{
+ return new MatrixPromise(
+ m_context.createPromise([
+ dbStoreGetter=m_dbStoreGetter,
+ client=m_clientOnSecondaryRoot.toEventLoop(),
+ roomId,
+ eventId
+ ](auto resolve) {
+ dbStoreGetter().getEventById(roomId, eventId).then([client, resolve, roomId](auto &&res) {
+ if (!res.has_value()) {
+ resolve(EffectStatus(/* succ = */ false));
+ return;
+ }
+ auto [event, _isInTimeline] = std::move(res).value();
+ // Disregard isInTimeline because when we load one single event,
+ // we do not want to add it to the timeline. If we add it,
+ // we lose info about the which events are before/after it,
+ // causing the remaining timeline to be gapped without a way
+ // to refill it.
+ immer::map<std::string, Kazv::EventList> relatedEvents = {
+ {roomId.toStdString(), {event}},
+ };
+ client.loadEventsFromStorage({}, relatedEvents)
+ .then([resolve](auto &&) {
+ resolve(EffectStatus(/* succ = */ true));
+ });
+ });
+ })
+ );
+}
+
MatrixPromise *MatrixSession::importFromKeyBackupFile(QUrl fileUrl, QString password)
{
if (!fileUrl.isLocalFile()) {
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -135,6 +135,7 @@
set(appium_tests_SOURCES
TestStickerPicker.py
+ TestEventViewUnavailable.py
)
foreach(testfilename ${appium_tests_SOURCES})
message(STATUS "Adding appium test ${testfilename}")
diff --git a/src/tests/appium-tests/TestEventViewUnavailable.py b/src/tests/appium-tests/TestEventViewUnavailable.py
new file mode 100755
--- /dev/null
+++ b/src/tests/appium-tests/TestEventViewUnavailable.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python3
+# This file is part of kazv.
+# SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+from helpers import utils
+from appium.webdriver.common.appiumby import AppiumBy
+from selenium.webdriver.support.ui import WebDriverWait
+import json
+
+class EventViewUnavailableTest(utils.ComponentTest):
+ def test_load(self):
+ create_button = self.driver.find_element(by=AppiumBy.NAME, value='create-event-view')
+ create_button.click()
+ self.driver.find_element(by=AppiumBy.NAME, value='spy-count=1')
+ self.driver.find_element(by=AppiumBy.NAME, value='spy-last-arg=$abcdef')
diff --git a/src/tests/appium-tests/TestEventViewUnavailable.qml b/src/tests/appium-tests/TestEventViewUnavailable.qml
new file mode 100644
--- /dev/null
+++ b/src/tests/appium-tests/TestEventViewUnavailable.qml
@@ -0,0 +1,80 @@
+/*
+ * This file is part of kazv.
+ * SPDX-FileCopyrightText: 2026 tusooa <tusooa@kazv.moe>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtTest
+
+import '../../contents/ui' as Kazv
+import 'qml-helpers' as AppiumQmlHelpers
+
+ColumnLayout {
+ id: upper
+ property var unavailableEvent: QtObject {
+ property string eventId: '$abcdef'
+ property string sender: ''
+ property string type: ''
+ property string stateKey: ''
+ property var content: {}
+ property boolean isAvailable: false
+ }
+ property var sender: ({
+ membership: 'join',
+ userId: '@foo:tusooa.xyz',
+ name: 'foo',
+ avatarMxcUri: '',
+ })
+
+ signal loadEventRequested(string eventId)
+
+ SignalSpy {
+ id: loadEventRequestedSpy
+ signalName: 'loadEventRequested'
+ }
+
+ function reset() {
+ loadEventRequestedSpy.clear();
+ loadEventRequestedSpy.target = upper;
+ spyLastArgLabel.value = spyLastArgLabel.getValue('');
+ }
+
+ Button {
+ text: 'create-event-view'
+ onClicked: {
+ const ev = upper.eventViewComp.createObject(upper);
+ }
+ }
+
+ AppiumQmlHelpers.InfoLabel {
+ tag: 'spy-count'
+ value: sendMessageRequestedSpy.count
+ }
+
+ AppiumQmlHelpers.InfoLabel {
+ id: spyLastArgLabel
+ Connections {
+ target: stickerPicker
+ function onSendMessageRequested(json) {
+ spyLastArgLabel.value = spyLastArgLabel.getValue(json);
+ }
+ }
+ tag: 'spy-last-arg'
+ value: getValue('')
+
+ function getValue(v) {
+ return v;
+ }
+ }
+
+ property var eventViewComp: Component {
+ Kazv.EventView {
+ Layout.fillWidth: true
+ event: upper.unavailableEvent
+ sender: upper.sender
+ }
+ }
+}
diff --git a/src/tests/matrix-event-test.cpp b/src/tests/matrix-event-test.cpp
--- a/src/tests/matrix-event-test.cpp
+++ b/src/tests/matrix-event-test.cpp
@@ -32,6 +32,7 @@
void testEdits();
void testEncryptedEdits();
void testReactions();
+ void testAvailable();
};
void MatrixEventTest::testEdits()
@@ -164,6 +165,25 @@
QVERIFY(r0->key() == u"meww"_s || r1->key() == u"meww"_s);
}
+void MatrixEventTest::testAvailable()
+{
+ auto r = makeRoom();
+ auto events = EventList{
+ makeEvent(withEventId("$0") | withEventKV("/room_id"_json_pointer, r.roomId)),
+ makeEvent(withEventId("$1") | withEventKV("/room_id"_json_pointer, r.roomId)),
+ };
+ withRoomTimeline(events)(r);
+
+ auto model = SdkModel{makeClient(withRoom(r))};
+ SessionSetup s(model);
+ auto roomList = toUniquePtr(s.session.roomList());
+ auto room = toUniquePtr(roomList->room(QString::fromStdString(r.roomId)));
+ auto e0 = toUniquePtr(room->messageById(u"$0"_s));
+ QVERIFY(e0->isAvailable());
+ auto e2 = toUniquePtr(room->messageById(u"$2"_s));
+ QVERIFY(!e2->isAvailable());
+}
+
QTEST_MAIN(MatrixEventTest)
#include "matrix-event-test.moc"
diff --git a/src/tests/quick-tests/tst_EventView.qml b/src/tests/quick-tests/tst_EventView.qml
--- a/src/tests/quick-tests/tst_EventView.qml
+++ b/src/tests/quick-tests/tst_EventView.qml
@@ -57,8 +57,9 @@
isState: false,
unsignedData: {},
isLocalEcho: true,
- isSending: true,
+ isSending: true,
isFailed: false,
+ isAvailable: true,
txnId: 'some-txn-id',
})
@@ -73,6 +74,7 @@
msgtype: 'm.text',
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -87,6 +89,7 @@
format: 'org.matrix.custom.html',
formatted_body: '<strong>some body</strong>',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -102,6 +105,7 @@
format: 'org.matrix.custom.html',
formatted_body: '<mx-reply><blockquote>some quote</blockquote></mx-reply><strong>some body</strong>',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -117,6 +121,7 @@
format: 'org.matrix.custom.html',
formatted_body: '<strong>some body</strong>',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -127,6 +132,7 @@
stateKey: '',
content: {},
redacted: true,
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -143,6 +149,7 @@
w: Kirigami.Units.gridUnit * 10,
}
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -154,6 +161,7 @@
content: {
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -166,6 +174,7 @@
msgtype: 'm.image',
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -178,6 +187,7 @@
msgtype: 'm.text',
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -191,6 +201,7 @@
msgtype: 'm.image',
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -203,6 +214,7 @@
msgtype: 'm.image',
body: 'some body',
},
+ isAvailable: true,
})
property var unknownMsgtypeEvent: ({
@@ -214,6 +226,7 @@
msgtype: 'moe.kazv.mxc.unknown.msgtype',
body: 'some body',
},
+ isAvailable: true,
})
property var cannotDecryptEvent: ({
@@ -225,6 +238,7 @@
msgtype: 'moe.kazv.mxc.cannot.decrypt',
body: 'some body',
},
+ isAvailable: true,
})
property var editedEvent: ({
@@ -236,6 +250,7 @@
msgtype: 'm.text',
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
isEdited: true,
})
@@ -248,6 +263,7 @@
msgtype: 'm.text',
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})
@@ -259,6 +275,7 @@
msgtype: 'm.text',
body: 'some body',
},
+ isAvailable: true,
formattedTime: '4:06 P.M.',
})

File Metadata

Mime Type
text/plain
Expires
Wed, Jul 8, 12:11 AM (22 h, 12 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1671538
Default Alt Text
D325.1783494660.diff (18 KB)

Event Timeline