Page MenuHomePhorge

No OneTemporary

Size
29 KB
Referenced Files
None
Subscribers
None
diff --git a/src/contents/ui/EventView.qml b/src/contents/ui/EventView.qml
index 0595adb..dd09e9b 100644
--- a/src/contents/ui/EventView.qml
+++ b/src/contents/ui/EventView.qml
@@ -1,225 +1,242 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.2
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import Qt.labs.qmlmodels 1.0
import org.kde.kirigami 2.13 as Kirigami
import 'event-types' as Types
Item {
id: eventView
property var event
property var sender
property var stateKeyUser
property var messageType: getMessageType(event)
property var iconSize: Kirigami.Units.iconSizes.large
property var inlineBadgeSize: Kirigami.Units.iconSizes.smallMedium
property var senderNameSize: Kirigami.Units.gridUnit * 1.2
property var redactPromise: null
property var minHeight: Kirigami.Units.gridUnit * 2
+ property var isGapped: false
- height: Math.max(loader.item.implicitHeight || 0, minHeight)
+ height: messageType === 'ignore' ? 0 : Math.max((gappedLabel.visible ? gappedLabel.height : 0) + loader.item.implicitHeight || 0, minHeight)
Component {
id: textEV
Types.Text {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: emoteEV
Types.Emote {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: noticeEV
Types.Notice {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: stateEV
Types.State {
event: eventView.event
sender: eventView.sender
stateKeyUser: eventView.stateKeyUser
}
}
Component {
id: imageEV
Types.Image {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: fileEV
Types.File {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: videoEV
Types.Video {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: audioEV
Types.Audio {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: unknownEV
Label {
text: 'unknown event'
}
}
Component {
id: redactedEV
Types.Redacted {
event: eventView.event
sender: eventView.sender
}
}
Component {
id: ignoreEV
Item {
}
}
+ ToolButton {
+ id: gappedLabel
+ visible: eventView.isGapped
+ icon.name: 'content-loading-symbolic'
+ text: l10n.get('room-timeline-load-more-action')
+ anchors.top: eventView.top
+ anchors.bottom: loader.item.top
+ anchors.left: eventView.left
+ anchors.right: eventView.right
+ onClicked: { console.log('load gap from', event.eventId); eventView.paginateBack(); }
+ }
+
Loader {
sourceComponent: getSource(messageType)
id: loader
anchors.left: eventView.left
anchors.right: eventView.right
+ anchors.bottom: eventView.bottom
}
-
function getSource(t) {
switch (t) {
case 'text':
return textEV;
case 'emote':
return emoteEV;
case 'notice':
return noticeEV;
case 'state':
return stateEV;
case 'image':
return imageEV;
case 'file':
return fileEV;
case 'video':
return videoEV;
case 'audio':
return audioEV;
case 'redacted':
return redactedEV;
case 'ignore':
return ignoreEV;
default:
return unknownEV;
}
}
function getMessageType(e) {
if (e.redacted) {
return 'redacted';
}
if (e.isState) {
return 'state';
}
switch (e.type) {
case 'm.room.message':
switch (e.content.msgtype) {
case 'm.text':
return 'text';
case 'm.emote':
return 'emote';
case 'm.notice':
return 'notice';
case 'm.image':
return 'image';
case 'm.file':
return 'file';
case 'm.audio':
return 'audio';
case 'm.video':
return 'video';
case 'm.location':
return 'location';
default:
console.log('msg type=unknown');
return 'unknown';
}
case 'm.room.redaction':
return 'ignore';
default:
return 'unknown';
}
}
function redactSelf(reason) {
eventView.redactPromise = room.redactEvent(eventView.event.eventId, reason);
}
Connections {
target: eventView.redactPromise
function onResolved(success, data) {
if (!success) {
showPassiveNotification(l10n.get('event-delete-failed', { errorMsg: data.error, errorCode: data.errorCode }));
}
eventView.redactPromise = null;
}
}
+
+ function paginateBack() {
+ room.paginateBackFrom(event.eventId);
+ }
}
diff --git a/src/contents/ui/RoomTimelineView.qml b/src/contents/ui/RoomTimelineView.qml
index 851564d..1b3a496 100644
--- a/src/contents/ui/RoomTimelineView.qml
+++ b/src/contents/ui/RoomTimelineView.qml
@@ -1,35 +1,36 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
import QtQuick 2.2
import QtQuick.Layouts 1.15
import QtQuick.Controls 2.15
import org.kde.kirigami 2.13 as Kirigami
ListView {
id: roomTimelineView
property var timeline
property var eventIds: timeline.eventIds
property string selectedEventId
spacing: Kirigami.Units.largeSpacing
model: timeline
currentIndex: -1
verticalLayoutDirection: ListView.BottomToTop
delegate: EventView {
event: timeline.at(index)
sender: room.member(event.sender || matrixSdk.userId)
stateKeyUser: event.stateKey ? room.member(event.stateKey) : {}
width: ListView.view.width
+ isGapped: timeline.isGapped(event.eventId)
}
}
diff --git a/src/l10n/en/100-ui.ftl b/src/l10n/en/100-ui.ftl
index 485bcc4..3fc94bc 100644
--- a/src/l10n/en/100-ui.ftl
+++ b/src/l10n/en/100-ui.ftl
@@ -1,196 +1,198 @@
### This file is part of kazv.
### SPDX-FileCopyrightText: 2020-2023 tusooa <tusooa@kazv.moe>
### SPDX-License-Identifier: AGPL-3.0-or-later
app-title = { -kt-app-name }
global-drawer-title = { -kt-app-name }
global-drawer-action-switch-account = Switch account
global-drawer-action-hard-logout = Logout
global-drawer-action-save-session = Save current session
global-drawer-action-configure-shortcuts = Configure shortcuts
global-drawer-action-settings = Settings
global-drawer-action-create-room = Create room
global-drawer-action-join-room = Join room
action-settings-page-title = Configure shortcuts
action-settings-shortcut-prompt = Shortcut: { $shortcut }
action-settings-shortcut-none = (none)
action-settings-shortcut-edit-action = Edit
action-settings-shortcut-remove-action = Clear
action-settings-shortcut-conflict-modal-title = Conflicting shortcuts
action-settings-shortcut-conflict = The shortcut { $shortcut } has conflicts with other actions. <br>If you continue, the shortcuts for other actions will be cleared. <br><br>Conflicting actions: <br>{ $conflictingAction }
action-settings-shortcut-conflict-continue = Continue
action-settings-shortcut-conflict-cancel = Cancel
empty-room-page-title = No rooms selected
empty-room-page-description = There is no room selected now.
login-page-title = Log in
login-page-userid-prompt = User id:
login-page-userid-input-placeholder = E.g.: @foo:example.org
login-page-password-prompt = Password:
login-page-login-button = Log in
login-page-close-button = Close
login-page-existing-sessions-prompt = Choose from one of the existing sessions:
login-page-alternative-password-login-prompt = Or start a new session with user id and password:
login-page-restore-session-button = Restore session
login-page-request-failed-prompt = Login failed. Error code: { $errorCode }. Error message: { $errorMsg }.
login-page-discover-failed-prompt = Unable to detect the server this user is on, or the server is unavailable. Error code: { $errorCode }. Error message: { $errorMsg }.
main-page-title = { -kt-app-name } - { $userId }
main-page-recent-tab-title = Recent
main-page-favourites-tab-title = Favourites
main-page-people-tab-title = People
main-page-rooms-tab-title = Rooms
room-list-view-room-item-title-name = { $name }
room-list-view-room-item-title-heroes = { $hero } { $otherNum ->
[0] { "" }
[1] and { $secondHero }
*[other] and { $otherNum } others
}
room-list-view-room-item-title-id = Unnamed room ({ $roomId })
room-list-view-room-item-fav-action = Set as favourite
room-tags-fav-action-notification = Set { $name } as favourite
room-tags-fav-action-notification-failed = Cannot set { $name } as favourite. Error code: { $errorCode }. Error message: { $errorMsg }.
room-tags-unfav-action-notification = Removed { $name } from favourites
room-tags-unfav-action-notification-failed = Cannot remove { $name } from favourites. Error code: { $errorCode }. Error message: { $errorMsg }.
room-tags-add-tag-action-notification = Added tag { $tag } to { $name }
room-tags-add-tag-action-notification-failed = Cannot add tag { $tag } to { $name }. Error code: { $errorCode }. Error message: { $errorMsg }.
room-tags-remove-tag-action-notification = Removed tag { $tag } from { $name }
room-tags-remove-tag-action-notification-failed = Cannot remove tag { $tag } from { $name }. Error code: { $errorCode }. Error message: { $errorMsg }.
room-list-view-room-item-more-action = More...
room-settings-page-title = Room settings for { $room }
room-settings-tags = Room tags
room-settings-favourited = Set as favourite
room-settings-remove-tag = Remove tag
room-settings-add-tag = Add tag
send-message-box-input-placeholder = Type your message here...
send-message-box-send = Send
send-message-box-send-file = Send file
+room-timeline-load-more-action = Load more
+
## State events
## Common parameters:
## gender = gender of the sender (male/female/neutral)
## stateKeyUser = name of the state key user
## stateKeyUserGender = gender of the state key user
member-state-joined-room = joined the room.
member-state-changed-name-and-avatar = changed { $gender ->
[male] his
[female] her
*[neutral] their
} name and avatar.
member-state-changed-name = changed { $gender ->
[male] his
[female] her
*[neutral] their
} name.
member-state-changed-avatar = changed { $gender ->
[male] his
[female] her
*[neutral] their
} avatar.
member-state-invited = invited { $stateKeyUser } to the room.
member-state-left = left the room.
member-state-kicked = kicked { $stateKeyUser }.
member-state-banned = banned { $stateKeyUser }.
member-state-unbanned = unbanned { $stateKeyUser }.
state-room-created = created the room.
state-room-name-changed = changed the name of the room to { $newName }.
state-room-topic-changed = changed the topic of the room to { $newTopic }.
state-room-avatar-changed = changed the avatar of the room.
state-room-pinned-events-changed = changed the pinned events of the room.
state-room-alias-changed = changed the aliases of the room.
state-room-join-rules-changed = changed join rules of the room.
state-room-power-levels-changed = changed power levels of the room.
state-room-encryption-activated = enabled encryption for this room.
event-message-image-sent = sent an image "{ $body }".
event-message-file-sent = sent a file "{ $body }".
event-message-video-sent = sent a video "{ $body }".
event-message-audio-sent = sent an audio "{ $body }".
event-message-audio-play-audio = Play audio
event-sending = Sending...
event-send-failed = Failed to send
event-resend = Retry sending this event
event-deleted = (Deleted)
event-delete = Delete
event-delete-failed = Error deleting event. Error code: { $errorCode }. Error message: { $errorMsg }.
event-view-source = View source...
event-source-popup-title = Event source
event-source-decrypted = Decrypted event source
event-source-original = Original event source
media-file-menu-option-view = View
media-file-menu-option-save-as = Save as
kazv-io-download-success-prompt = Download successful
kazv-io-download-failure-prompt = Download failure: { $detail }
kazv-io-failure-detail-user-cancel = User canceled
kazv-io-failure-detail-network-error = Network error
kazv-io-failure-detail-open-file-error = Open file error
kazv-io-failure-detail-write-file-error = Write file error
kazv-io-failure-detail-kazv-error = Unknow Error, please report this as bug.
kazv-io-upload-failure-prompt = Upload failure: { $detail }
kazv-io-downloading-prompt = Downloading: { $fileName }
kazv-io-uploading-prompt = Uploading: { $fileName }
kazv-io-prompt-close = Got it
kazv-io-pause = Pause
kazv-io-resume = Resume
kazv-io-cancel = Cancel
create-room-page-title = Create room
create-room-page-visibility-prompt = Room visibility:
create-room-page-visibility-public = Public
create-room-page-visibility-private = Private
create-room-page-name-prompt = Room name (optional):
create-room-page-name-placeholder = No name
create-room-page-alias-prompt = Room alias (optional):
create-room-page-alias-placeholder = #foo:example.org
create-room-page-topic-prompt = Room topic (optional):
create-room-page-topic-placeholder = No topic
create-room-page-allow-federate-prompt = Allow users from other servers to join
create-room-page-action-create-room = Create room
create-room-page-success-prompt = Room created.
create-room-page-failed-prompt = Unable to create room. Error code: { $errorCode }. Error message: { $errorMsg }.
join-room-page-title = Join room
join-room-page-id-or-alias-prompt = Room id or alias:
join-room-page-id-or-alias-placeholder = #foo:example.org or !abcdef:example.org
join-room-page-servers-prompt = Via servers (optional, separated by newlines):
join-room-page-servers-placeholder =
example.org
example.com
join-room-page-action-join-room = Join room
join-room-page-success-prompt = Successfully joined room { $room }.
join-room-page-failed-prompt = Unable to join room { $room }. Error code: { $errorCode }. Error message: { $errorMsg }.
device-trust-level-unseen = Unseen
device-trust-level-seen = Seen
device-trust-level-verified = Verified
device-trust-level-blocked = Blocked
device-set-trust-level = Set trust level...
device-set-trust-level-dialog-title = Set trust level
device-set-trust-level-dialog-name-label = Device name: { $name }
device-set-trust-level-dialog-id-label = Device id: { $id }
device-set-trust-level-dialog-ed25519-key-label = Ed25519 public key: { $key }
device-set-trust-level-dialog-curve25519-key-label = Curve25519 public key: { $key }
device-set-trust-level-dialog-save = Save
device-set-trust-level-dialog-cancel = Cancel
settings-page-title = Settings
settings-save = Save settings
settings-profile-load-failed-prompt = Unable to load profile. Error code: { $errorCode }. Error message: { $errorMsg }.
settings-profile-change-avatar = Change avatar...
settings-profile-display-name = Display name:
settings-profile-save-failed-prompt = Unable to save profile. Error code: { $errorCode }. Error message: { $errorMsg }.
typing-indicator = { $typingUser } { $otherNum ->
[0] is typing...
[1] and { $secondTypingUser } are typing...
*[other] and { $otherNum } others are typing...
}
diff --git a/src/matrix-room-timeline.cpp b/src/matrix-room-timeline.cpp
index 850be5c..ecbfe04 100644
--- a/src/matrix-room-timeline.cpp
+++ b/src/matrix-room-timeline.cpp
@@ -1,104 +1,110 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-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 <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)
, m_room(room)
, m_timeline(m_room.timelineEvents())
, m_internalCount(0)
, m_localEchoes(m_room.localEchoes())
+ , m_timelineGaps(m_room.timelineGaps())
, LAGER_QT(count)(lager::with(
m_timeline.xform(containerSize),
m_localEchoes.xform(containerSize)
).map([](const auto &tlSize, const auto &localSize) { return tlSize + localSize; }))
, LAGER_QT(eventIds)(m_timeline.xform(zug::map(
[](auto container) {
return zug::into(
QStringList{},
zug::map([](Kazv::Event event) {
return QString::fromStdString(event.id());
}),
std::move(container));
})))
{
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()); });
}
MatrixRoomTimeline::~MatrixRoomTimeline() = default;
MatrixEvent *MatrixRoomTimeline::at(int index) const
{
return new MatrixEvent(lager::with(m_timeline, m_localEchoes, LAGER_QT(count))
.map([index](const auto &tl, const auto &localEchoes, const auto &count) -> std::variant<Kazv::Event, Kazv::LocalEchoDesc> {
auto rIndex = count - index - 1;
if (static_cast<unsigned int>(rIndex) < tl.size()) {
return tl[rIndex];
} else {
auto remainingIndex = rIndex - tl.size();
if (remainingIndex < localEchoes.size()) {
return localEchoes[remainingIndex];
} else {
return Event();
}
}
}));
}
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();
}
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();
}
}
+
+bool MatrixRoomTimeline::isGapped(QString eventId) const
+{
+ return m_timelineGaps.get().count(eventId.toStdString()) > 0;
+}
diff --git a/src/matrix-room-timeline.hpp b/src/matrix-room-timeline.hpp
index 3416a01..9c86f64 100644
--- a/src/matrix-room-timeline.hpp
+++ b/src/matrix-room-timeline.hpp
@@ -1,48 +1,51 @@
/*
* 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 <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include <QObject>
#include <QQmlEngine>
#include <QAbstractListModel>
#include <lager/extra/qt.hpp>
#include <client/room/room.hpp>
class MatrixEvent;
class MatrixRoomTimeline : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Kazv::Room m_room;
lager::reader<Kazv::EventList> m_timeline;
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(QStringList, eventIds);
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 bool isGapped(QString eventId) const;
+
private Q_SLOTS:
void updateInternalCount();
};
diff --git a/src/matrix-room.cpp b/src/matrix-room.cpp
index 44abbdf..fe78eb0 100644
--- a/src/matrix-room.cpp
+++ b/src/matrix-room.cpp
@@ -1,177 +1,182 @@
/*
* This file is part of kazv.
* SPDX-FileCopyrightText: 2020-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 <lager/setter.hpp>
#include <nlohmann/json.hpp>
#include <event.hpp>
#include <QFile>
#include <QMimeDatabase>
#include "kazv-log.hpp"
#include "matrix-room.hpp"
#include "matrix-room-timeline.hpp"
#include "matrix-room-member.hpp"
#include "matrix-room-member-list-model.hpp"
#include "matrix-promise.hpp"
#include "qfunctionutils.hpp"
#include "helper.hpp"
using namespace Kazv;
static const int typingDebounceMs = 500;
static const int typingTimeoutMs = 1000;
MatrixRoom::MatrixRoom(Kazv::Room room, lager::reader<std::string> selfUserId, QObject *parent)
: QObject(parent)
, m_room(room)
, m_selfUserId(selfUserId)
, m_memberNames(m_room.members())
, LAGER_QT(roomId)(m_room.roomId().xform(strToQt))
, LAGER_QT(name)(m_room.nameOpt()[lager::lenses::or_default].xform(strToQt))
, LAGER_QT(heroNames)(m_room.heroDisplayNames().xform(strListToQt))
, LAGER_QT(avatarMxcUri)(m_room.avatarMxcUri().xform(strToQt))
, LAGER_QT(roomOrHeroAvatarMxcUri)(lager::with(m_room.heroMemberEvents(), m_room.avatarMxcUri())
.map([](const auto &heroes, const auto &roomAvatar) {
if (!roomAvatar.empty()) {
return roomAvatar;
}
if (heroes.size() == 1) {
return zug::reduce(roomMemberToAvatar(zug::last), std::string(), heroes);
}
return std::string();
})
.xform(strToQt))
, LAGER_QT(localDraft)(lager::with_setter(
m_room.localDraft().xform(strToQt).make(),
[this](QString localDraft) {
m_room.setLocalDraft(localDraft.toStdString());
},
lager::automatic_tag{}))
, LAGER_QT(memberNames)(m_memberNames.xform(strListToQt))
, LAGER_QT(tagIds)(m_room.tags().map([](const auto &tagsMap) {
return zug::into(
QStringList(),
zug::map([](const auto pair) {
return QString::fromStdString(pair.first);
}),
tagsMap);
}))
, m_setTypingThrottled(QFunctionUtils::Throttle([self=QPointer<MatrixRoom>(this)]() {
if (self) {
self->setTypingImpl();
}
}, typingDebounceMs))
{
}
MatrixRoom::~MatrixRoom() = default;
MatrixRoomTimeline *MatrixRoom::timeline() const
{
return new MatrixRoomTimeline(m_room);
}
void MatrixRoom::sendTextMessage(QString text) const
{
m_room.sendTextMessage(text.toStdString());
}
void MatrixRoom::sendMediaFileMessage(QString fileName, QString mimeType, qint64 fileSize, QString mxcUri) const
{
auto j = makeMediaFileMessageJson(fileName, mimeType, fileSize, mxcUri);
Kazv::Event e{j};
m_room.sendMessage(e);
}
void MatrixRoom::resendMessage(QString txnId) const
{
m_room.resendMessage(txnId.toStdString());
}
nlohmann::json MatrixRoom::makeMediaFileMessageJson(QString fileName, QString mimeType, qint64 fileSize, QString mxcUri) const
{
static auto available_msgtype = std::array<std::string, 3>{"m.audio", "m.video", "m.image"};
auto try_msgtype = std::find(available_msgtype.begin(), available_msgtype.end(),
QStringLiteral("m.").append(mimeType.split(QChar('/'))[0]).toStdString());
std::string msgtype;
if (try_msgtype == available_msgtype.end()) {
msgtype = "m.file";
} else {
msgtype = *try_msgtype;
}
return nlohmann::json {
{"type", "m.room.message"},
{"content", {
{"msgtype", msgtype},
{"body", fileName.toStdString()},
{"url", mxcUri.toStdString()},
{"info", {
{"size", fileSize},
{"mimetype", mimeType.toStdString()}
}}
}}
};
}
MatrixPromise *MatrixRoom::redactEvent(QString eventId, QString reason) const
{
qCInfo(kazvLog) << "redactEvent(" << eventId << ", " << reason << ")";
return new MatrixPromise(m_room.redactEvent(
eventId.toStdString(),
reason.isEmpty() ? std::nullopt : std::optional<std::string>(reason.toStdString())
));
}
MatrixPromise *MatrixRoom::addOrSetTag(QString tagId) const
{
return new MatrixPromise(m_room.addOrSetTag(tagId.toStdString()));
}
MatrixPromise *MatrixRoom::removeTag(QString tagId) const
{
return new MatrixPromise(m_room.removeTag(tagId.toStdString()));
}
+MatrixPromise *MatrixRoom::paginateBackFrom(QString eventId) const
+{
+ return new MatrixPromise(m_room.paginateBackFromEvent(eventId.toStdString()));
+}
+
MatrixRoomMember *MatrixRoom::memberAt(int index) const
{
return new MatrixRoomMember(m_room.memberEventByCursor(
m_memberNames[index][lager::lenses::or_default]));
}
MatrixRoomMember *MatrixRoom::member(QString userId) const
{
return new MatrixRoomMember(m_room.memberEventFor(userId.toStdString()));
}
MatrixRoomMemberListModel *MatrixRoom::typingUsers() const
{
return new MatrixRoomMemberListModel(
m_room.typingMemberEvents()
.xform(containerMap(EventList{}, zug::filter([self=m_selfUserId.make().get()](const auto &e) {
return e.stateKey() != self;
})))
);
}
void MatrixRoom::setTyping(bool typing)
{
if (typing) {
m_setTypingThrottled();
} else {
m_room.setTyping(false, std::nullopt);
}
}
void MatrixRoom::setTypingImpl()
{
m_room.setTyping(true, typingTimeoutMs);
}
diff --git a/src/matrix-room.hpp b/src/matrix-room.hpp
index 332c9e2..075ab2c 100644
--- a/src/matrix-room.hpp
+++ b/src/matrix-room.hpp
@@ -1,82 +1,84 @@
/*
* 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 <libkazv-config.hpp>
#include <immer/config.hpp> // https://github.com/arximboldi/immer/issues/168
#include <QObject>
#include <QQmlEngine>
#include <QUrl>
#include <QTimer>
#include <lager/extra/qt.hpp>
#include <client/room/room.hpp>
class MatrixRoomTimeline;
class MatrixRoomMember;
class MatrixPromise;
class MatrixRoomMemberListModel;
class MatrixRoom : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
Kazv::Room m_room;
lager::reader<std::string> m_selfUserId;
lager::reader<immer::flex_vector<std::string>> m_memberNames;
public:
explicit MatrixRoom(Kazv::Room room, lager::reader<std::string> selfUserId, QObject *parent = 0);
~MatrixRoom() override;
LAGER_QT_READER(QString, roomId);
LAGER_QT_READER(QString, name);
LAGER_QT_READER(QStringList, heroNames);
LAGER_QT_READER(QString, avatarMxcUri);
LAGER_QT_READER(QString, roomOrHeroAvatarMxcUri);
LAGER_QT_CURSOR(QString, localDraft);
LAGER_QT_READER(QStringList, memberNames);
LAGER_QT_READER(QStringList, tagIds);
Q_INVOKABLE MatrixRoomMember *memberAt(int index) const;
Q_INVOKABLE MatrixRoomMember *member(QString userId) const;
Q_INVOKABLE MatrixRoomTimeline *timeline() const;
Q_INVOKABLE void sendTextMessage(QString text) const;
Q_INVOKABLE void sendMediaFileMessage(QString fileName, QString mimeType,
qint64 fileSize, QString mxcUri) const;
Q_INVOKABLE void resendMessage(QString txnId) const;
Q_INVOKABLE MatrixPromise *redactEvent(QString eventId, QString reason) const;
Q_INVOKABLE MatrixRoomMemberListModel *typingUsers() const;
Q_INVOKABLE void setTyping(bool typing);
Q_INVOKABLE MatrixPromise *addOrSetTag(QString tagId) const;
Q_INVOKABLE MatrixPromise *removeTag(QString tagId) const;
+ Q_INVOKABLE MatrixPromise *paginateBackFrom(QString eventId) const;
+
protected:
nlohmann::json makeMediaFileMessageJson(QString fileName, QString mimeType,
qint64 fileSize, QString mxcUri) const;
private Q_SLOTS:
void setTypingImpl();
private:
std::function<void()> m_setTypingThrottled;
};

File Metadata

Mime Type
text/x-diff
Expires
Sun, Jan 19, 4:26 PM (1 d, 4 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
55323
Default Alt Text
(29 KB)

Event Timeline