summary refs log tree commit diff
path: root/src/timeline/TimelineModel.h
diff options
context:
space:
mode:
Diffstat (limited to 'src/timeline/TimelineModel.h')
-rw-r--r--src/timeline/TimelineModel.h265
1 files changed, 265 insertions, 0 deletions
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
new file mode 100644

index 00000000..f06de5d9 --- /dev/null +++ b/src/timeline/TimelineModel.h
@@ -0,0 +1,265 @@ +#pragma once + +#include <QAbstractListModel> +#include <QColor> +#include <QDate> +#include <QHash> +#include <QSet> + +#include <mtxclient/http/errors.hpp> + +#include "CacheCryptoStructs.h" + +namespace mtx::http { +using RequestErr = const std::optional<mtx::http::ClientError> &; +} +namespace mtx::responses { +struct Timeline; +struct Messages; +struct ClaimKeys; +} + +namespace qml_mtx_events { +Q_NAMESPACE + +enum EventType +{ + // Unsupported event + Unsupported, + /// m.room_key_request + KeyRequest, + /// m.room.aliases + Aliases, + /// m.room.avatar + Avatar, + /// m.room.canonical_alias + CanonicalAlias, + /// m.room.create + Create, + /// m.room.encrypted. + Encrypted, + /// m.room.encryption. + Encryption, + /// m.room.guest_access + GuestAccess, + /// m.room.history_visibility + HistoryVisibility, + /// m.room.join_rules + JoinRules, + /// m.room.member + Member, + /// m.room.name + Name, + /// m.room.power_levels + PowerLevels, + /// m.room.tombstone + Tombstone, + /// m.room.topic + Topic, + /// m.room.redaction + Redaction, + /// m.room.pinned_events + PinnedEvents, + // m.sticker + Sticker, + // m.tag + Tag, + /// m.room.message + AudioMessage, + EmoteMessage, + FileMessage, + ImageMessage, + LocationMessage, + NoticeMessage, + TextMessage, + VideoMessage, + Redacted, + UnknownMessage, +}; +Q_ENUM_NS(EventType) + +enum EventState +{ + //! The plaintext message was received by the server. + Received, + //! At least one of the participants has read the message. + Read, + //! The client sent the message. Not yet received. + Sent, + //! When the message is loaded from cache or backfill. + Empty, + //! When the message failed to send + Failed, +}; +Q_ENUM_NS(EventState) +} + +class StateKeeper +{ +public: + StateKeeper(std::function<void()> &&fn) + : fn_(std::move(fn)) + {} + + ~StateKeeper() { fn_(); } + +private: + std::function<void()> fn_; +}; + +struct DecryptionResult +{ + //! The decrypted content as a normal plaintext event. + mtx::events::collections::TimelineEvents event; + //! Whether or not the decryption was successful. + bool isDecrypted = false; +}; + +class TimelineViewManager; + +class TimelineModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY( + int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) + Q_PROPERTY(std::vector<QString> typingUsers READ typingUsers WRITE updateTypingUsers NOTIFY + typingUsersChanged) + +public: + explicit TimelineModel(TimelineViewManager *manager, + QString room_id, + QObject *parent = nullptr); + + enum Roles + { + Section, + Type, + TypeString, + Body, + FormattedBody, + UserId, + UserName, + Timestamp, + Url, + ThumbnailUrl, + Filename, + Filesize, + MimeType, + Height, + Width, + ProportionalHeight, + Id, + State, + IsEncrypted, + ReplyTo, + RoomName, + RoomTopic, + Dump, + }; + + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QVariant data(const QString &id, int role) const; + + bool canFetchMore(const QModelIndex &) const override; + void fetchMore(const QModelIndex &) override; + + Q_INVOKABLE QString displayName(QString id) const; + Q_INVOKABLE QString avatarUrl(QString id) const; + Q_INVOKABLE QString formatDateSeparator(QDate date) const; + Q_INVOKABLE QString formatTypingUsers(const std::vector<QString> &users, QColor bg); + Q_INVOKABLE QString formatMemberEvent(QString id); + + Q_INVOKABLE QString escapeEmoji(QString str) const; + Q_INVOKABLE void viewRawMessage(QString id) const; + Q_INVOKABLE void openUserProfile(QString userid) const; + Q_INVOKABLE void replyAction(QString id); + Q_INVOKABLE void readReceiptsAction(QString id) const; + Q_INVOKABLE void redactEvent(QString id); + Q_INVOKABLE int idToIndex(QString id) const; + Q_INVOKABLE QString indexToId(int index) const; + Q_INVOKABLE void cacheMedia(QString eventId); + Q_INVOKABLE void saveMedia(QString eventId) const; + + void addEvents(const mtx::responses::Timeline &events); + template<class T> + void sendMessage(const T &msg); + +public slots: + void setCurrentIndex(int index); + int currentIndex() const { return idToIndex(currentId); } + void markEventsAsRead(const std::vector<QString> &event_ids); + QVariantMap getDump(QString eventId) const; + void updateTypingUsers(const std::vector<QString> &users) + { + if (this->typingUsers_ != users) { + this->typingUsers_ = users; + emit typingUsersChanged(typingUsers_); + } + } + std::vector<QString> typingUsers() const { return typingUsers_; } + +private slots: + // Add old events at the top of the timeline. + void addBackwardsEvents(const mtx::responses::Messages &msgs); + void processOnePendingMessage(); + void addPendingMessage(mtx::events::collections::TimelineEvents event); + +signals: + void oldMessagesRetrieved(const mtx::responses::Messages &res); + void messageFailed(QString txn_id); + void messageSent(QString txn_id, QString event_id); + void currentIndexChanged(int index); + void redactionFailed(QString id); + void eventRedacted(QString id); + void nextPendingMessage(); + void newMessageToSend(mtx::events::collections::TimelineEvents event); + void mediaCached(QString mxcUrl, QString cacheUrl); + void newEncryptedImage(mtx::crypto::EncryptedFile encryptionInfo); + void eventFetched(QString requestingEvent, mtx::events::collections::TimelineEvents event); + void typingUsersChanged(std::vector<QString> users); + +private: + DecryptionResult decryptEvent( + const mtx::events::EncryptedEvent<mtx::events::msg::Encrypted> &e) const; + std::vector<QString> internalAddEvents( + const std::vector<mtx::events::collections::TimelineEvents> &timeline); + void sendEncryptedMessage(const std::string &txn_id, nlohmann::json content); + void handleClaimedKeys(std::shared_ptr<StateKeeper> keeper, + const std::map<std::string, std::string> &room_key, + const std::map<std::string, DevicePublicKeys> &pks, + const std::string &user_id, + const mtx::responses::ClaimKeys &res, + mtx::http::RequestErr err); + void updateLastMessage(); + void readEvent(const std::string &id); + + QHash<QString, mtx::events::collections::TimelineEvents> events; + QSet<QString> failed, read; + QList<QString> pending; + std::vector<QString> eventOrder; + + QString room_id_; + QString prev_batch_token_; + + bool isInitialSync = true; + bool paginationInProgress = false; + bool isProcessingPending = false; + + QString currentId; + std::vector<QString> typingUsers_; + + TimelineViewManager *manager_; + + friend struct SendMessageVisitor; +}; + +template<class T> +void +TimelineModel::sendMessage(const T &msg) +{ + mtx::events::RoomEvent<T> msgCopy = {}; + msgCopy.content = msg; + emit newMessageToSend(msgCopy); +}