diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index b0747a7c..f17081e5 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -19,6 +19,7 @@
#include "Cache.h"
#include "ChatPage.h"
+#include "CombinedImagePackModel.h"
#include "CompletionProxyModel.h"
#include "Config.h"
#include "Logging.h"
@@ -502,6 +503,31 @@ InputBar::video(const QString &filename,
}
void
+InputBar::sticker(CombinedImagePackModel *model, int row)
+{
+ if (!model || row < 0)
+ return;
+
+ auto img = model->imageAt(row);
+
+ mtx::events::msg::StickerImage sticker{};
+ sticker.info = img.info.value_or(mtx::common::ImageInfo{});
+ sticker.url = img.url;
+ sticker.body = img.body;
+
+ if (!room->reply().isEmpty()) {
+ sticker.relations.relations.push_back(
+ {mtx::common::RelationType::InReplyTo, room->reply().toStdString()});
+ }
+ if (!room->edit().isEmpty()) {
+ sticker.relations.relations.push_back(
+ {mtx::common::RelationType::Replace, room->edit().toStdString()});
+ }
+
+ room->sendMessageEvent(sticker, mtx::events::EventType::Sticker);
+}
+
+void
InputBar::command(QString command, QString args)
{
if (command == "me") {
diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h
index c9728379..2e6fb5c0 100644
--- a/src/timeline/InputBar.h
+++ b/src/timeline/InputBar.h
@@ -12,6 +12,7 @@
#include <mtx/responses/messages.hpp>
class TimelineModel;
+class CombinedImagePackModel;
class QMimeData;
class QDropEvent;
class QStringList;
@@ -57,6 +58,7 @@ public slots:
MarkdownOverride useMarkdown = MarkdownOverride::NOT_SPECIFIED,
bool rainbowify = false);
void reaction(const QString &reactedEvent, const QString &reactionKey);
+ void sticker(CombinedImagePackModel *model, int row);
private slots:
void startTyping();
diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp
index 1eaab468..e4957045 100644
--- a/src/timeline/Permissions.cpp
+++ b/src/timeline/Permissions.cpp
@@ -8,9 +8,9 @@
#include "MatrixClient.h"
#include "TimelineModel.h"
-Permissions::Permissions(TimelineModel *parent)
+Permissions::Permissions(QString roomId, QObject *parent)
: QObject(parent)
- , room(parent)
+ , roomId_(roomId)
{
invalidate();
}
@@ -19,7 +19,7 @@ void
Permissions::invalidate()
{
pl = cache::client()
- ->getStateEvent<mtx::events::state::PowerLevels>(room->roomId().toStdString())
+ ->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content;
}
diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h
index f7e6f389..7aab1ddb 100644
--- a/src/timeline/Permissions.h
+++ b/src/timeline/Permissions.h
@@ -15,7 +15,7 @@ class Permissions : public QObject
Q_OBJECT
public:
- Permissions(TimelineModel *parent);
+ Permissions(QString roomId, QObject *parent = nullptr);
Q_INVOKABLE bool canInvite();
Q_INVOKABLE bool canBan();
@@ -28,6 +28,6 @@ public:
void invalidate();
private:
- TimelineModel *room;
+ QString roomId_;
mtx::events::state::PowerLevels pl;
};
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index ab11f99b..ee5564a5 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -25,6 +25,7 @@
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
+#include "MemberList.h"
#include "MxcImageProvider.h"
#include "Olm.h"
#include "TimelineViewManager.h"
@@ -317,6 +318,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
, events(room_id.toStdString(), this)
, room_id_(room_id)
, manager_(manager)
+ , permissions_{room_id}
{
lastMessage_.timestamp = 0;
@@ -325,6 +327,10 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
this->isSpace_ = create->content.type == mtx::events::state::room_type::space;
this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
+ // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it
+ // needs to be
+ connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged);
+
connect(
this,
&TimelineModel::redactionFailed,
@@ -344,6 +350,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
&EventStore::dataChanged,
this,
[this](int from, int to) {
+ relatedEventCacheBuster++;
nhlog::ui()->debug(
"data changed {} to {}", events.size() - to - 1, events.size() - from - 1);
emit dataChanged(index(events.size() - to - 1, 0),
@@ -443,6 +450,7 @@ TimelineModel::roleNames() const
{RoomTopic, "roomTopic"},
{CallType, "callType"},
{Dump, "dump"},
+ {RelatedEventCacheBuster, "relatedEventCacheBuster"},
};
}
int
@@ -676,6 +684,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r
return QVariant(m);
}
+ case RelatedEventCacheBuster:
+ return relatedEventCacheBuster;
default:
return QVariant();
}
@@ -710,6 +720,14 @@ TimelineModel::data(const QModelIndex &index, int role) const
return data(*event, role);
}
+QVariant
+TimelineModel::dataById(QString id, int role, QString relatedTo)
+{
+ if (auto event = events.get(id.toStdString(), relatedTo.toStdString()))
+ return data(*event, role);
+ return QVariant();
+}
+
bool
TimelineModel::canFetchMore(const QModelIndex &) const
{
@@ -1049,14 +1067,6 @@ TimelineModel::openUserProfile(QString userid)
}
void
-TimelineModel::openRoomSettings()
-{
- RoomSettings *settings = new RoomSettings(roomId(), this);
- connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged);
- openRoomSettingsDialog(settings);
-}
-
-void
TimelineModel::replyAction(QString id)
{
setReply(id);
@@ -1292,6 +1302,14 @@ struct SendMessageVisitor
sendRoomEvent<mtx::events::msg::KeyVerificationCancel,
mtx::events::EventType::KeyVerificationCancel>(msg);
}
+ void operator()(mtx::events::Sticker msg)
+ {
+ msg.type = mtx::events::EventType::Sticker;
+ if (cache::isRoomEncrypted(model_->room_id_.toStdString())) {
+ model_->sendEncryptedMessage(msg, mtx::events::EventType::Sticker);
+ } else
+ emit model_->addPendingMessageToStore(msg);
+ }
TimelineModel *model_;
};
@@ -1301,6 +1319,7 @@ TimelineModel::addPendingMessage(mtx::events::collections::TimelineEvents event)
{
std::visit(
[](auto &msg) {
+ // gets overwritten for reactions and stickers in SendMessageVisitor
msg.type = mtx::events::EventType::RoomMessage;
msg.event_id = "m" + http::client()->generate_txn_id();
msg.sender = http::client()->user_id().to_string();
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index a3c973d6..0e2ce153 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -17,6 +17,8 @@
#include "CacheStructs.h"
#include "EventStore.h"
#include "InputBar.h"
+#include "InviteesModel.h"
+#include "MemberList.h"
#include "Permissions.h"
#include "ui/RoomSettings.h"
#include "ui/UserProfile.h"
@@ -158,7 +160,9 @@ class TimelineModel : public QAbstractListModel
Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit)
Q_PROPERTY(
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
+ Q_PROPERTY(QString roomId READ roomId CONSTANT)
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
+ Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged)
Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
@@ -208,6 +212,7 @@ public:
RoomTopic,
CallType,
Dump,
+ RelatedEventCacheBuster,
};
Q_ENUM(Roles);
@@ -215,10 +220,7 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
- Q_INVOKABLE QVariant dataById(QString id, int role)
- {
- return data(index(idToIndex(id)), role);
- }
+ Q_INVOKABLE QVariant dataById(QString id, int role, QString relatedTo);
bool canFetchMore(const QModelIndex &) const override;
void fetchMore(const QModelIndex &) override;
@@ -237,7 +239,6 @@ public:
Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
Q_INVOKABLE void openUserProfile(QString userid);
- Q_INVOKABLE void openRoomSettings();
Q_INVOKABLE void editAction(QString id);
Q_INVOKABLE void replyAction(QString id);
Q_INVOKABLE void readReceiptsAction(QString id) const;
@@ -354,14 +355,13 @@ signals:
void lastMessageChanged();
void notificationsChanged();
- void openRoomSettingsDialog(RoomSettings *settings);
-
void newMessageToSend(mtx::events::collections::TimelineEvents event);
void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
void updateFlowEventId(std::string event_id);
void encryptionChanged();
void roomNameChanged();
+ void plainRoomNameChanged();
void roomTopicChanged();
void roomAvatarUrlChanged();
void roomMemberCountChanged();
@@ -391,7 +391,7 @@ private:
TimelineViewManager *manager_;
InputBar input_{this};
- Permissions permissions_{this};
+ Permissions permissions_;
QTimer showEventTimer{this};
QString eventIdToShow;
@@ -403,6 +403,8 @@ private:
int notification_count = 0, highlight_count = 0;
+ unsigned int relatedEventCacheBuster = 0;
+
bool decryptDescription = true;
bool m_paginationInProgress = false;
bool isSpace_ = false;
@@ -413,10 +415,17 @@ template<class T>
void
TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventType)
{
- mtx::events::RoomEvent<T> msgCopy = {};
- msgCopy.content = content;
- msgCopy.type = eventType;
- emit newMessageToSend(msgCopy);
+ if constexpr (std::is_same_v<T, mtx::events::msg::StickerImage>) {
+ mtx::events::Sticker msgCopy = {};
+ msgCopy.content = content;
+ msgCopy.type = eventType;
+ emit newMessageToSend(msgCopy);
+ } else {
+ mtx::events::RoomEvent<T> msgCopy = {};
+ msgCopy.content = content;
+ msgCopy.type = eventType;
+ emit newMessageToSend(msgCopy);
+ }
resetReply();
resetEdit();
}
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index b39ef615..a6922be7 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -15,15 +15,19 @@
#include "ChatPage.h"
#include "Clipboard.h"
#include "ColorImageProvider.h"
+#include "CombinedImagePackModel.h"
#include "CompletionProxyModel.h"
#include "DelegateChooser.h"
#include "DeviceVerificationFlow.h"
#include "EventAccessors.h"
+#include "ImagePackListModel.h"
+#include "InviteesModel.h"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
#include "MxcImageProvider.h"
#include "RoomsModel.h"
+#include "SingleImagePackModel.h"
#include "UserSettingsPage.h"
#include "UsersModel.h"
#include "dialogs/ImageOverlay.h"
@@ -144,6 +148,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
qRegisterMetaType<mtx::events::msg::KeyVerificationReady>();
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
+ qRegisterMetaType<CombinedImagePackModel *>();
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
"im.nheko",
@@ -172,6 +177,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
0,
"UserProfileModel",
"UserProfile needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<MemberList>(
+ "im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side");
qmlRegisterUncreatableType<RoomSettings>(
"im.nheko",
1,
@@ -180,6 +187,24 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
"Room Settings needs to be instantiated on the C++ side");
qmlRegisterUncreatableType<TimelineModel>(
"im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<ImagePackListModel>(
+ "im.nheko",
+ 1,
+ 0,
+ "ImagePackListModel",
+ "ImagePackListModel needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<SingleImagePackModel>(
+ "im.nheko",
+ 1,
+ 0,
+ "SingleImagePackModel",
+ "SingleImagePackModel needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<InviteesModel>(
+ "im.nheko",
+ 1,
+ 0,
+ "InviteesModel",
+ "InviteesModel needs to be instantiated on the C++ side");
static auto self = this;
qmlRegisterSingletonType<MainWindow>(
@@ -341,6 +366,41 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
}
void
+TimelineViewManager::openRoomMembers(QString room_id)
+{
+ MemberList *memberList = new MemberList(room_id, this);
+ emit openRoomMembersDialog(memberList);
+}
+
+void
+TimelineViewManager::openRoomSettings(QString room_id)
+{
+ RoomSettings *settings = new RoomSettings(room_id, this);
+ connect(rooms_->getRoomById(room_id).data(),
+ &TimelineModel::roomAvatarUrlChanged,
+ settings,
+ &RoomSettings::avatarChanged);
+ emit openRoomSettingsDialog(settings);
+}
+
+void
+TimelineViewManager::openInviteUsers(QString roomId)
+{
+ InviteesModel *model = new InviteesModel{this};
+ connect(model, &InviteesModel::accept, this, [this, model, roomId]() {
+ emit inviteUsers(roomId, model->mxids());
+ });
+ emit openInviteUsersDialog(model);
+}
+
+void
+TimelineViewManager::openGlobalUserProfile(QString userId)
+{
+ UserProfile *profile = new UserProfile{QString{}, userId, this};
+ emit openProfile(profile);
+}
+
+void
TimelineViewManager::setVideoCallItem()
{
WebRTCSession::instance().setVideoItem(
@@ -398,6 +458,12 @@ TimelineViewManager::openImageOverlay(QString mxcUrl, QString eventId)
}
void
+TimelineViewManager::openImagePackSettings(QString roomid)
+{
+ emit showImagePackSettings(new ImagePackListModel(roomid.toStdString(), this));
+}
+
+void
TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
{
auto pixmap = QPixmap::fromImage(img);
@@ -420,17 +486,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
}
void
-TimelineViewManager::openInviteUsersDialog()
-{
- MainWindow::instance()->openInviteUsersDialog(
- [this](const QStringList &invitees) { emit inviteUsers(invitees); });
-}
-void
-TimelineViewManager::openMemberListDialog(QString roomid) const
-{
- MainWindow::instance()->openMemberListDialog(roomid);
-}
-void
TimelineViewManager::openLeaveRoomDialog(QString roomid) const
{
MainWindow::instance()->openLeaveRoomDialog(roomid);
@@ -593,6 +648,11 @@ TimelineViewManager::completerFor(QString completerName, QString roomId)
auto proxy = new CompletionProxyModel(roomModel);
roomModel->setParent(proxy);
return proxy;
+ } else if (completerName == "stickers") {
+ auto stickerModel = new CombinedImagePackModel(roomId.toStdString(), true);
+ auto proxy = new CompletionProxyModel(stickerModel, 1, static_cast<size_t>(-1) / 4);
+ stickerModel->setParent(proxy);
+ return proxy;
}
return nullptr;
}
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 15b4f523..54e3a935 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -33,6 +33,7 @@ class ColorImageProvider;
class UserSettings;
class ChatPage;
class DeviceVerificationFlow;
+class ImagePackListModel;
class TimelineViewManager : public QObject
{
@@ -57,6 +58,7 @@ public:
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
bool isWindowFocused() const { return isWindowFocused_; }
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId);
+ Q_INVOKABLE void openImagePackSettings(QString roomid);
Q_INVOKABLE QColor userColor(QString id, QColor background);
Q_INVOKABLE QString escapeEmoji(QString str) const;
Q_INVOKABLE QString htmlEscape(QString str) const { return str.toHtmlEscaped(); }
@@ -64,9 +66,12 @@ public:
Q_INVOKABLE QString userPresence(QString id) const;
Q_INVOKABLE QString userStatus(QString id) const;
+ Q_INVOKABLE void openRoomMembers(QString room_id);
+ Q_INVOKABLE void openRoomSettings(QString room_id);
+ Q_INVOKABLE void openInviteUsers(QString roomId);
+ Q_INVOKABLE void openGlobalUserProfile(QString userId);
+
Q_INVOKABLE void focusMessageInput();
- Q_INVOKABLE void openInviteUsersDialog();
- Q_INVOKABLE void openMemberListDialog(QString roomid) const;
Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const;
Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
@@ -81,11 +86,17 @@ signals:
void replyingEventChanged(QString replyingEvent);
void replyClosed();
void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
- void inviteUsers(QStringList users);
+ void inviteUsers(QString roomId, QStringList users);
+ void showRoomList();
+ void narrowViewChanged();
void focusChanged();
void focusInput();
void openImageOverlayInternalCb(QString eventId, QImage img);
+ void openRoomMembersDialog(MemberList *members);
+ void openRoomSettingsDialog(RoomSettings *settings);
+ void openInviteUsersDialog(InviteesModel *invitees);
void openProfile(UserProfile *profile);
+ void showImagePackSettings(ImagePackListModel *packlist);
public slots:
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
|