diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index 6326e98e..3ecd4c75 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -186,6 +186,26 @@ EventStore::addPending(mtx::events::collections::TimelineEvents event)
}
void
+EventStore::clearTimeline()
+{
+ emit beginResetModel();
+
+ cache::client()->clearTimeline(room_id_);
+ auto range = cache::client()->getTimelineRange(room_id_);
+ if (range) {
+ nhlog::db()->info("Range {} {}", range->last, range->first);
+ this->last = range->last;
+ this->first = range->first;
+ } else {
+ this->first = std::numeric_limits<uint64_t>::max();
+ this->last = std::numeric_limits<uint64_t>::max();
+ }
+ nhlog::ui()->info("Range {} {}", this->last, this->first);
+
+ emit endResetModel();
+}
+
+void
EventStore::handleSync(const mtx::responses::Timeline &events)
{
if (this->thread() != QThread::currentThread())
@@ -448,36 +468,89 @@ EventStore::decryptEvent(const IdIndex &idx,
index.session_id = e.content.session_id;
index.sender_key = e.content.sender_key;
- mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
- dummy.origin_server_ts = e.origin_server_ts;
- dummy.event_id = e.event_id;
- dummy.sender = e.sender;
- dummy.content.body =
- tr("-- Encrypted Event (No keys found for decryption) --",
- "Placeholder, when the message was not decrypted yet or can't be decrypted.")
- .toStdString();
-
auto asCacheEntry = [&idx](mtx::events::collections::TimelineEvents &&event) {
auto event_ptr = new mtx::events::collections::TimelineEvents(std::move(event));
decryptedEvents_.insert(idx, event_ptr);
return event_ptr;
};
- try {
- if (!cache::client()->inboundMegolmSessionExists(index)) {
+ auto decryptionResult = olm::decryptEvent(index, e);
+
+ mtx::events::RoomEvent<mtx::events::msg::Notice> dummy;
+ dummy.origin_server_ts = e.origin_server_ts;
+ dummy.event_id = e.event_id;
+ dummy.sender = e.sender;
+
+ if (decryptionResult.error) {
+ switch (*decryptionResult.error) {
+ case olm::DecryptionErrorCode::MissingSession:
+ dummy.content.body =
+ tr("-- Encrypted Event (No keys found for decryption) --",
+ "Placeholder, when the message was not decrypted yet or can't be "
+ "decrypted.")
+ .toStdString();
nhlog::crypto()->info("Could not find inbound megolm session ({}, {}, {})",
index.room_id,
index.session_id,
e.sender);
- // TODO: request megolm session_id & session_key from the sender.
- return asCacheEntry(std::move(dummy));
+ // TODO: Check if this actually works and look in key backup
+ olm::send_key_request_for(room_id_, e);
+ break;
+ case olm::DecryptionErrorCode::DbError:
+ nhlog::db()->critical(
+ "failed to retrieve megolm session with index ({}, {}, {})",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ decryptionResult.error_message.value_or(""));
+ dummy.content.body =
+ tr("-- Decryption Error (failed to retrieve megolm keys from db) --",
+ "Placeholder, when the message can't be decrypted, because the DB "
+ "access "
+ "failed.")
+ .toStdString();
+ break;
+ case olm::DecryptionErrorCode::DecryptionFailed:
+ nhlog::crypto()->critical(
+ "failed to decrypt message with index ({}, {}, {}): {}",
+ index.room_id,
+ index.session_id,
+ index.sender_key,
+ decryptionResult.error_message.value_or(""));
+ dummy.content.body =
+ tr("-- Decryption Error (%1) --",
+ "Placeholder, when the message can't be decrypted. In this case, the "
+ "Olm "
+ "decrytion returned an error, which is passed as %1.")
+ .arg(
+ QString::fromStdString(decryptionResult.error_message.value_or("")))
+ .toStdString();
+ break;
+ case olm::DecryptionErrorCode::ParsingFailed:
+ dummy.content.body =
+ tr("-- Encrypted Event (Unknown event type) --",
+ "Placeholder, when the message was decrypted, but we couldn't parse "
+ "it, because "
+ "Nheko/mtxclient don't support that event type yet.")
+ .toStdString();
+ break;
+ case olm::DecryptionErrorCode::ReplayAttack:
+ nhlog::crypto()->critical(
+ "Reply attack while decryptiong event {} in room {} from {}!",
+ e.event_id,
+ room_id_,
+ index.sender_key);
+ dummy.content.body =
+ tr("-- Reply attack! This message index was reused! --").toStdString();
+ break;
+ case olm::DecryptionErrorCode::UnknownFingerprint:
+ // TODO: don't fail, just show in UI.
+ nhlog::crypto()->critical("Message by unverified fingerprint {}",
+ index.sender_key);
+ dummy.content.body =
+ tr("-- Message by unverified device! --").toStdString();
+ break;
}
- } catch (const lmdb::error &e) {
- nhlog::db()->critical("failed to check megolm session's existence: {}", e.what());
- dummy.content.body = tr("-- Decryption Error (failed to communicate with DB) --",
- "Placeholder, when the message can't be decrypted, because "
- "the DB access failed when trying to lookup the session.")
- .toStdString();
return asCacheEntry(std::move(dummy));
}
@@ -547,6 +620,11 @@ EventStore::decryptEvent(const IdIndex &idx,
"Nheko/mtxclient don't support that event type yet.")
.toStdString();
return asCacheEntry(std::move(dummy));
+ auto encInfo = mtx::accessors::file(decryptionResult.event.value());
+ if (encInfo)
+ emit newEncryptedImage(encInfo.value());
+
+ return asCacheEntry(std::move(decryptionResult.event.value()));
}
mtx::events::collections::TimelineEvents *
@@ -608,6 +686,12 @@ EventStore::fetchMore()
http::client()->messages(
opts, [this, opts](const mtx::responses::Messages &res, mtx::http::RequestErr err) {
+ if (cache::client()->previousBatchToken(room_id_) != opts.from) {
+ nhlog::net()->warn("Cache cleared while fetching more messages, dropping "
+ "/messages response");
+ emit fetchedMore();
+ return;
+ }
if (err) {
nhlog::net()->error("failed to call /messages ({}): {} - {} - {}",
opts.room_id,
diff --git a/src/timeline/EventStore.h b/src/timeline/EventStore.h
index 55a66f49..4ff4fa3b 100644
--- a/src/timeline/EventStore.h
+++ b/src/timeline/EventStore.h
@@ -104,6 +104,7 @@ signals:
public slots:
void addPending(mtx::events::collections::TimelineEvents event);
+ void clearTimeline();
private:
mtx::events::collections::TimelineEvents *decryptEvent(
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index dc5eb8cc..aea2645a 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -160,6 +160,26 @@ struct RoomEventType
{
return qml_mtx_events::EventType::Redacted;
}
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallInvite> &)
+ {
+ return qml_mtx_events::EventType::CallInvite;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallAnswer> &)
+ {
+ return qml_mtx_events::EventType::CallAnswer;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallHangUp> &)
+ {
+ return qml_mtx_events::EventType::CallHangUp;
+ }
+ qml_mtx_events::EventType operator()(
+ const mtx::events::Event<mtx::events::msg::CallCandidates> &)
+ {
+ return qml_mtx_events::EventType::CallCandidates;
+ }
// ::EventType::Type operator()(const Event<mtx::events::msg::Location> &e) { return
// ::EventType::LocationMessage; }
};
@@ -271,6 +291,7 @@ TimelineModel::roleNames() const
{RoomId, "roomId"},
{RoomName, "roomName"},
{RoomTopic, "roomTopic"},
+ {CallType, "callType"},
{Dump, "dump"},
};
}
@@ -422,6 +443,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
return QVariant(QString::fromStdString(room_name(event)));
case RoomTopic:
return QVariant(QString::fromStdString(room_topic(event)));
+ case CallType:
+ return QVariant(QString::fromStdString(call_type(event)));
case Dump: {
QVariantMap m;
auto names = roleNames();
@@ -452,6 +475,7 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
m.insert(names[ReplyTo], data(event, static_cast<int>(ReplyTo)));
m.insert(names[RoomName], data(event, static_cast<int>(RoomName)));
m.insert(names[RoomTopic], data(event, static_cast<int>(RoomTopic)));
+ m.insert(names[CallType], data(event, static_cast<int>(CallType)));
return QVariant(m);
}
@@ -548,8 +572,32 @@ TimelineModel::addEvents(const mtx::responses::Timeline &timeline)
events.handleSync(timeline);
- if (!timeline.events.empty())
- updateLastMessage();
+ using namespace mtx::events;
+ for (auto e : timeline.events) {
+ if (auto encryptedEvent = std::get_if<EncryptedEvent<msg::Encrypted>>(&e)) {
+ MegolmSessionIndex index;
+ index.room_id = room_id_.toStdString();
+ index.session_id = encryptedEvent->content.session_id;
+ index.sender_key = encryptedEvent->content.sender_key;
+
+ auto result = olm::decryptEvent(index, *encryptedEvent);
+ if (result.event)
+ e = result.event.value();
+ }
+
+ if (std::holds_alternative<RoomEvent<msg::CallCandidates>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallInvite>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallAnswer>>(e) ||
+ std::holds_alternative<RoomEvent<msg::CallHangUp>>(e))
+ std::visit(
+ [this](auto &event) {
+ event.room_id = room_id_.toStdString();
+ if (event.sender != http::client()->user_id().to_string())
+ emit newCallEvent(event);
+ },
+ e);
+ }
+ updateLastMessage();
}
template<typename T>
@@ -574,6 +622,23 @@ isMessage(const mtx::events::EncryptedEvent<T> &)
return true;
}
+auto
+isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &)
+{
+ return true;
+}
+
+auto
+isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &)
+{
+ return true;
+}
+auto
+isMessage(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &)
+{
+ return true;
+}
+
// Workaround. We also want to see a room at the top, if we just joined it
auto
isYourJoin(const mtx::events::StateEvent<mtx::events::state::Member> &e)
@@ -806,15 +871,16 @@ TimelineModel::markEventsAsRead(const std::vector<QString> &event_ids)
template<typename T>
void
-TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent<T> msg)
+TimelineModel::sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType)
{
const auto room_id = room_id_.toStdString();
using namespace mtx::events;
using namespace mtx::identifiers;
- json doc = {
- {"type", to_string(msg.type)}, {"content", json(msg.content)}, {"room_id", room_id}};
+ json doc = {{"type", mtx::events::to_string(eventType)},
+ {"content", msg.content},
+ {"room_id", room_id}};
try {
// Check if we have already an outbound megolm session then we can use.
@@ -1093,69 +1159,115 @@ struct SendMessageVisitor
: model_(model)
{}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg)
+ template<typename T, mtx::events::EventType Event>
+ void sendRoomEvent(mtx::events::RoomEvent<T> msg)
{
- model_->sendEncryptedMessage(msg);
+ if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
+ auto encInfo = mtx::accessors::file(msg);
+ if (encInfo)
+ emit model_->newEncryptedImage(encInfo.value());
+
+ model_->sendEncryptedMessage(msg, Event);
+ } else {
+ msg.type = Event;
+ emit model_->addPendingMessageToStore(msg);
+ }
}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg)
+
+ // Do-nothing operator for all unhandled events
+ template<typename T>
+ void operator()(const mtx::events::Event<T> &)
+ {}
+
+ // Operator for m.room.message events that contain a msgtype in their content
+ template<typename T,
+ std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
+ void operator()(mtx::events::RoomEvent<T> msg)
{
- model_->sendEncryptedMessage(msg);
+ sendRoomEvent<T, mtx::events::EventType::RoomMessage>(msg);
}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg)
+
+ // Special operator for reactions, which are a type of m.room.message, but need to be
+ // handled distinctly for their differences from normal room messages. Specifically,
+ // reactions need to have the relation outside of ciphertext, or synapse / the homeserver
+ // cannot handle it correctly. See the MSC for more details:
+ // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption
+ void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg)
{
- model_->sendEncryptedMessage(msg);
+ msg.type = mtx::events::EventType::Reaction;
+ emit model_->addPendingMessageToStore(msg);
}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg)
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallInvite> &event)
{
- model_->sendEncryptedMessage(msg);
+ sendRoomEvent<mtx::events::msg::CallInvite, mtx::events::EventType::CallInvite>(
+ event);
}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg)
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallCandidates> &event)
{
- model_->sendEncryptedMessage(msg);
+ sendRoomEvent<mtx::events::msg::CallCandidates,
+ mtx::events::EventType::CallCandidates>(event);
}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg)
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallAnswer> &event)
{
- model_->sendEncryptedMessage(msg);
+ sendRoomEvent<mtx::events::msg::CallAnswer, mtx::events::EventType::CallAnswer>(
+ event);
}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg)
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::CallHangUp> &event)
{
- model_->sendEncryptedMessage(msg);
+ sendRoomEvent<mtx::events::msg::CallHangUp, mtx::events::EventType::CallHangUp>(
+ event);
}
- void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg)
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &msg)
{
- model_->sendEncryptedMessage(msg);
+ sendRoomEvent<mtx::events::msg::KeyVerificationRequest,
+ mtx::events::EventType::RoomMessage>(msg);
}
- // Do-nothing operator for all unhandled events
- template<typename T>
- void operator()(const mtx::events::Event<T> &)
- {}
- // Operator for m.room.message events that contain a msgtype in their content
- template<typename T,
- std::enable_if_t<std::is_same<decltype(T::msgtype), std::string>::value, int> = 0>
- void operator()(const mtx::events::RoomEvent<T> &msg)
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationReady,
+ mtx::events::EventType::KeyVerificationReady>(msg);
+ }
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart> &msg)
{
- if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
- auto encInfo = mtx::accessors::file(msg);
- if (encInfo)
- emit model_->newEncryptedImage(encInfo.value());
+ sendRoomEvent<mtx::events::msg::KeyVerificationStart,
+ mtx::events::EventType::KeyVerificationStart>(msg);
+ }
- model_->sendEncryptedMessage(msg);
- } else {
- emit model_->addPendingMessageToStore(msg);
- }
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationAccept,
+ mtx::events::EventType::KeyVerificationAccept>(msg);
}
- // Special operator for reactions, which are a type of m.room.message, but need to be
- // handled distinctly for their differences from normal room messages. Specifically,
- // reactions need to have the relation outside of ciphertext, or synapse / the homeserver
- // cannot handle it correctly. See the MSC for more details:
- // https://github.com/matrix-org/matrix-doc/blob/matthew/msc1849/proposals/1849-aggregations.md#end-to-end-encryption
- void operator()(mtx::events::RoomEvent<mtx::events::msg::Reaction> msg)
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac> &msg)
{
- msg.type = mtx::events::EventType::Reaction;
- emit model_->addPendingMessageToStore(msg);
+ sendRoomEvent<mtx::events::msg::KeyVerificationMac,
+ mtx::events::EventType::KeyVerificationMac>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationKey,
+ mtx::events::EventType::KeyVerificationKey>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationDone,
+ mtx::events::EventType::KeyVerificationDone>(msg);
+ }
+
+ void operator()(const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel> &msg)
+ {
+ sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
+ mtx::events::EventType::KeyVerificationCancel>(msg);
}
TimelineModel *model_;
@@ -1173,39 +1285,6 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
},
event);
- if (std::get_if<mtx::events::RoomEvent<mtx::events::msg::KeyVerificationReady>>(&event)) {
- std::visit(
- [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationReady; },
- event);
- }
- if (std::get_if<mtx::events::RoomEvent<mtx::events::msg::KeyVerificationStart>>(&event)) {
- std::visit(
- [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationStart; },
- event);
- }
- if (std::get_if<mtx::events::RoomEvent<mtx::events::msg::KeyVerificationKey>>(&event)) {
- std::visit([](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationKey; },
- event);
- }
- if (std::get_if<mtx::events::RoomEvent<mtx::events::msg::KeyVerificationMac>>(&event)) {
- std::visit([](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationMac; },
- event);
- }
- if (std::get_if<mtx::events::RoomEvent<mtx::events::msg::KeyVerificationDone>>(&event)) {
- std::visit(
- [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationDone; }, event);
- }
- if (std::get_if<mtx::events::RoomEvent<mtx::events::msg::KeyVerificationCancel>>(&event)) {
- std::visit(
- [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationCancel; },
- event);
- }
- if (std::get_if<mtx::events::RoomEvent<mtx::events::msg::KeyVerificationAccept>>(&event)) {
- std::visit(
- [](auto &msg) { msg.type = mtx::events::EventType::KeyVerificationAccept; },
- event);
- }
-
std::visit(SendMessageVisitor{this}, event);
}
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index fb9921d3..390fa1ed 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -37,6 +37,14 @@ enum EventType
Aliases,
/// m.room.avatar
Avatar,
+ /// m.call.invite
+ CallInvite,
+ /// m.call.answer
+ CallAnswer,
+ /// m.call.hangup
+ CallHangUp,
+ /// m.call.candidates
+ CallCandidates,
/// m.room.canonical_alias
CanonicalAlias,
/// m.room.create
@@ -173,6 +181,7 @@ public:
RoomId,
RoomName,
RoomTopic,
+ CallType,
Dump,
};
@@ -218,7 +227,7 @@ public:
void updateLastMessage();
void addEvents(const mtx::responses::Timeline &events);
template<class T>
- void sendMessage(const T &msg);
+ void sendMessageEvent(const T &content, mtx::events::EventType eventType);
RelatedInfo relatedInfo(QString id);
public slots:
@@ -251,6 +260,7 @@ public slots:
}
}
void setDecryptDescription(bool decrypt) { decryptDescription = decrypt; }
+ void clearTimeline() { events.clearTimeline(); }
private slots:
void addPendingMessage(mtx::events::collections::TimelineEvents event);
@@ -264,6 +274,7 @@ signals:
void typingUsersChanged(std::vector<QString> users);
void replyChanged(QString reply);
void paginationInProgressChanged(const bool);
+ void newCallEvent(const mtx::events::collections::TimelineEvents &event);
void openProfile(UserProfile *profile);
@@ -273,7 +284,7 @@ signals:
private:
template<typename T>
- void sendEncryptedMessage(mtx::events::RoomEvent<T> msg);
+ void sendEncryptedMessage(mtx::events::RoomEvent<T> msg, mtx::events::EventType eventType);
void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper,
const std::map<std::string, std::string> &room_key,
const std::map<std::string, DevicePublicKeys> &pks,
@@ -304,9 +315,10 @@ private:
template<class T>
void
-TimelineModel::sendMessage(const T &msg)
+TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
{
mtx::events::RoomEvent<T> msgCopy = {};
- msgCopy.content = msg;
+ msgCopy.content = content;
+ msgCopy.type = eventType;
emit newMessageToSend(msgCopy);
}
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index fb4a094e..f199578f 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -1,11 +1,14 @@
#include "TimelineViewManager.h"
+#include <QDesktopServices>
#include <QMetaType>
#include <QPalette>
#include <QQmlContext>
#include <QQmlEngine>
+#include <QString>
#include "BlurhashProvider.h"
+#include "CallManager.h"
#include "ChatPage.h"
#include "ColorImageProvider.h"
#include "DelegateChooser.h"
@@ -97,10 +100,13 @@ TimelineViewManager::userStatus(QString id) const
return QString::fromStdString(cache::statusMessage(id.toStdString()));
}
-TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent)
+TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings,
+ CallManager *callManager,
+ QWidget *parent)
: imgProvider(new MxcImageProvider())
, colorImgProvider(new ColorImageProvider())
, blurhashProvider(new BlurhashProvider())
+ , callManager_(callManager)
, settings(userSettings)
{
qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
@@ -285,6 +291,10 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
}
}
});
+ connect(dynamic_cast<ChatPage *>(parent), &ChatPage::loggedOut, this, [this]() {
+ isInitialSync_ = true;
+ emit initialSyncChanged(true);
+ });
}
void
@@ -294,7 +304,17 @@ TimelineViewManager::sync(const mtx::responses::Rooms &rooms)
// addRoom will only add the room, if it doesn't exist
addRoom(QString::fromStdString(room_id));
const auto &room_model = models.value(QString::fromStdString(room_id));
+ if (!isInitialSync_)
+ connect(room_model.data(),
+ &TimelineModel::newCallEvent,
+ callManager_,
+ &CallManager::syncEvent);
room_model->addEvents(room.timeline);
+ if (!isInitialSync_)
+ disconnect(room_model.data(),
+ &TimelineModel::newCallEvent,
+ callManager_,
+ &CallManager::syncEvent);
if (ChatPage::instance()->userSettings()->typingNotifications()) {
std::vector<QString> typing;
@@ -372,6 +392,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId) const
}
void
+TimelineViewManager::openLink(QString link) const
+{
+ QDesktopServices::openUrl(link);
+}
+
+void
TimelineViewManager::updateReadReceipts(const QString &room_id,
const std::vector<QString> &event_ids)
{
@@ -440,7 +466,7 @@ TimelineViewManager::queueTextMessage(const QString &msg)
timeline_->resetReply();
}
- timeline_->sendMessage(text);
+ timeline_->sendMessageEvent(text, mtx::events::EventType::RoomMessage);
}
void
@@ -462,7 +488,7 @@ TimelineViewManager::queueEmoteMessage(const QString &msg)
}
if (timeline_)
- timeline_->sendMessage(emote);
+ timeline_->sendMessageEvent(emote, mtx::events::EventType::RoomMessage);
}
void
@@ -491,7 +517,7 @@ TimelineViewManager::queueReactionMessage(const QString &reactedEvent, const QSt
reaction.relates_to.event_id = reactedEvent.toStdString();
reaction.relates_to.key = reactionKey.toStdString();
- timeline_->sendMessage(reaction);
+ timeline_->sendMessageEvent(reaction, mtx::events::EventType::Reaction);
// Otherwise, we have previously reacted and the reaction should be redacted
} else {
timeline_->redactEvent(selfReactedEvent);
@@ -527,7 +553,7 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
model->resetReply();
}
- model->sendMessage(image);
+ model->sendMessageEvent(image, mtx::events::EventType::RoomMessage);
}
void
@@ -555,7 +581,7 @@ TimelineViewManager::queueFileMessage(
model->resetReply();
}
- model->sendMessage(file);
+ model->sendMessageEvent(file, mtx::events::EventType::RoomMessage);
}
void
@@ -583,7 +609,7 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
model->resetReply();
}
- model->sendMessage(audio);
+ model->sendMessageEvent(audio, mtx::events::EventType::RoomMessage);
}
void
@@ -610,5 +636,34 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
model->resetReply();
}
- model->sendMessage(video);
+ model->sendMessageEvent(video, mtx::events::EventType::RoomMessage);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallInvite &callInvite)
+{
+ models.value(roomid)->sendMessageEvent(callInvite, mtx::events::EventType::CallInvite);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallCandidates &callCandidates)
+{
+ models.value(roomid)->sendMessageEvent(callCandidates,
+ mtx::events::EventType::CallCandidates);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallAnswer &callAnswer)
+{
+ models.value(roomid)->sendMessageEvent(callAnswer, mtx::events::EventType::CallAnswer);
+}
+
+void
+TimelineViewManager::queueCallMessage(const QString &roomid,
+ const mtx::events::msg::CallHangUp &callHangUp)
+{
+ models.value(roomid)->sendMessageEvent(callHangUp, mtx::events::EventType::CallHangUp);
}
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 031d07cc..19406872 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -19,6 +19,7 @@
class MxcImageProvider;
class BlurhashProvider;
+class CallManager;
class ColorImageProvider;
class UserSettings;
@@ -46,7 +47,9 @@ class TimelineViewManager : public QObject
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
public:
- TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent = nullptr);
+ TimelineViewManager(QSharedPointer<UserSettings> userSettings,
+ CallManager *callManager,
+ QWidget *parent = nullptr);
QWidget *getWidget() const { return container; }
void sync(const mtx::responses::Rooms &rooms);
@@ -62,6 +65,8 @@ public:
Q_INVOKABLE QString userPresence(QString id) const;
Q_INVOKABLE QString userStatus(QString id) const;
+ Q_INVOKABLE void openLink(QString link) const;
+
signals:
void clearRoomMessageCount(QString roomid);
void updateRoomsLastMessage(QString roomid, const DescInfo &info);
@@ -110,8 +115,19 @@ public slots:
const QString &url,
const QString &mime,
uint64_t dsize);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallInvite &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallCandidates &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallAnswer &);
+ void queueCallMessage(const QString &roomid, const mtx::events::msg::CallHangUp &);
+
void updateEncryptedDescriptions();
+ void clearCurrentRoomTimeline()
+ {
+ if (timeline_)
+ timeline_->clearTimeline();
+ }
+
private:
#ifdef USE_QUICK_VIEW
QQuickView *view;
@@ -125,7 +141,8 @@ private:
BlurhashProvider *blurhashProvider;
QHash<QString, QSharedPointer<TimelineModel>> models;
- TimelineModel *timeline_ = nullptr;
+ TimelineModel *timeline_ = nullptr;
+ CallManager *callManager_ = nullptr;
bool isInitialSync_ = true;
|