Allow replying with an image

This commit is contained in:
Nicolas Werner 2020-01-12 16:39:01 +01:00
parent 4727f1c2bb
commit 4ca8da9a89
7 changed files with 133 additions and 118 deletions

4
deps/CMakeLists.txt vendored
View file

@ -46,10 +46,10 @@ set(BOOST_SHA256
set( set(
MTXCLIENT_URL MTXCLIENT_URL
https://github.com/Nheko-Reborn/mtxclient/archive/9fda08b222dc4f9f59da18ed8370698643131cdd.zip https://github.com/Nheko-Reborn/mtxclient/archive/6d2a02b6079c9d888c28cd24504618aaadb7fa97.zip
) )
set(MTXCLIENT_HASH set(MTXCLIENT_HASH
9a9da7a9e0ede51d36238b54be03782892d47233a1ba2ce7d36614ad2ed6a6c1) 30811e076ee1fee22ba5d5d92c94a5425ff714a7ccb245ff4ac64fecb04dc539)
set( set(
TWEENY_URL TWEENY_URL
https://github.com/mobius3/tweeny/archive/b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf.tar.gz https://github.com/mobius3/tweeny/archive/b94ce07cfb02a0eb8ac8aaf66137dabdaea857cf.tar.gz

View file

@ -56,6 +56,7 @@ constexpr int RETRY_TIMEOUT = 5'000;
constexpr size_t MAX_ONETIME_KEYS = 50; constexpr size_t MAX_ONETIME_KEYS = 50;
Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>) Q_DECLARE_METATYPE(std::optional<mtx::crypto::EncryptedFile>)
Q_DECLARE_METATYPE(std::optional<RelatedInfo>)
ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent) ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: QWidget(parent) : QWidget(parent)
@ -65,8 +66,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
{ {
setObjectName("chatPage"); setObjectName("chatPage");
qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>( qRegisterMetaType<std::optional<mtx::crypto::EncryptedFile>>();
"std::optional<mtx::crypto::EncryptedFile>"); qRegisterMetaType<std::optional<RelatedInfo>>();
topLayout_ = new QHBoxLayout(this); topLayout_ = new QHBoxLayout(this);
topLayout_->setSpacing(0); topLayout_->setSpacing(0);
@ -287,19 +288,14 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
SLOT(showUnreadMessageNotification(int))); SLOT(showUnreadMessageNotification(int)));
connect(text_input_, connect(text_input_,
SIGNAL(sendTextMessage(const QString &)), &TextInputWidget::sendTextMessage,
view_manager_, view_manager_,
SLOT(queueTextMessage(const QString &))); &TimelineViewManager::queueTextMessage);
connect(text_input_, connect(text_input_,
SIGNAL(sendReplyMessage(const QString &, const RelatedInfo &)), &TextInputWidget::sendEmoteMessage,
view_manager_, view_manager_,
SLOT(queueReplyMessage(const QString &, const RelatedInfo &))); &TimelineViewManager::queueEmoteMessage);
connect(text_input_,
SIGNAL(sendEmoteMessage(const QString &)),
view_manager_,
SLOT(queueEmoteMessage(const QString &)));
connect(text_input_, &TextInputWidget::sendJoinRoomRequest, this, &ChatPage::joinRoom); connect(text_input_, &TextInputWidget::sendJoinRoomRequest, this, &ChatPage::joinRoom);
@ -307,7 +303,10 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
text_input_, text_input_,
&TextInputWidget::uploadMedia, &TextInputWidget::uploadMedia,
this, this,
[this](QSharedPointer<QIODevice> dev, QString mimeClass, const QString &fn) { [this](QSharedPointer<QIODevice> dev,
QString mimeClass,
const QString &fn,
const std::optional<RelatedInfo> &related) {
QMimeDatabase db; QMimeDatabase db;
QMimeType mime = db.mimeTypeForData(dev.data()); QMimeType mime = db.mimeTypeForData(dev.data());
@ -341,7 +340,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
mimeClass, mimeClass,
mime = mime.name(), mime = mime.name(),
size = payload.size(), size = payload.size(),
dimensions](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) { dimensions,
related](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
if (err) { if (err) {
emit uploadFailed( emit uploadFailed(
tr("Failed to upload media. Please try again.")); tr("Failed to upload media. Please try again."));
@ -359,7 +359,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
mimeClass, mimeClass,
mime, mime,
size, size,
dimensions); dimensions,
related);
}); });
}); });
@ -367,35 +368,37 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
text_input_->hideUploadSpinner(); text_input_->hideUploadSpinner();
emit showNotification(msg); emit showNotification(msg);
}); });
connect(this, connect(
&ChatPage::mediaUploaded, this,
this, &ChatPage::mediaUploaded,
[this](QString roomid, this,
QString filename, [this](QString roomid,
std::optional<mtx::crypto::EncryptedFile> encryptedFile, QString filename,
QString url, std::optional<mtx::crypto::EncryptedFile> encryptedFile,
QString mimeClass, QString url,
QString mime, QString mimeClass,
qint64 dsize, QString mime,
QSize dimensions) { qint64 dsize,
text_input_->hideUploadSpinner(); QSize dimensions,
const std::optional<RelatedInfo> &related) {
text_input_->hideUploadSpinner();
if (encryptedFile) if (encryptedFile)
encryptedFile->url = url.toStdString(); encryptedFile->url = url.toStdString();
if (mimeClass == "image") if (mimeClass == "image")
view_manager_->queueImageMessage( view_manager_->queueImageMessage(
roomid, filename, encryptedFile, url, mime, dsize, dimensions); roomid, filename, encryptedFile, url, mime, dsize, dimensions, related);
else if (mimeClass == "audio") else if (mimeClass == "audio")
view_manager_->queueAudioMessage( view_manager_->queueAudioMessage(
roomid, filename, encryptedFile, url, mime, dsize); roomid, filename, encryptedFile, url, mime, dsize, related);
else if (mimeClass == "video") else if (mimeClass == "video")
view_manager_->queueVideoMessage( view_manager_->queueVideoMessage(
roomid, filename, encryptedFile, url, mime, dsize); roomid, filename, encryptedFile, url, mime, dsize, related);
else else
view_manager_->queueFileMessage( view_manager_->queueFileMessage(
roomid, filename, encryptedFile, url, mime, dsize); roomid, filename, encryptedFile, url, mime, dsize, related);
}); });
connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar); connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);

View file

@ -109,7 +109,8 @@ signals:
const QString &mimeClass, const QString &mimeClass,
const QString &mime, const QString &mime,
qint64 dsize, qint64 dsize,
const QSize &dimensions); const QSize &dimensions,
const std::optional<RelatedInfo> &related);
void contentLoaded(); void contentLoaded();
void closing(); void closing();

View file

@ -419,33 +419,25 @@ FilteredTextEdit::submit()
auto name = text.mid(1, command_end - 1); auto name = text.mid(1, command_end - 1);
auto args = text.mid(command_end + 1); auto args = text.mid(command_end + 1);
if (name.isEmpty() || name == "/") { if (name.isEmpty() || name == "/") {
if (!related_.related_event.empty()) { message(args, related);
reply(args, related_);
} else {
message(args);
}
} else { } else {
command(name, args); command(name, args);
} }
} else { } else {
if (!related_.related_event.empty()) { message(std::move(text), std::move(related));
reply(std::move(text), std::move(related_));
} else {
message(std::move(text));
}
} }
related_ = {}; related = {};
clear(); clear();
} }
void void
FilteredTextEdit::showReplyPopup(const RelatedInfo &related) FilteredTextEdit::showReplyPopup(const RelatedInfo &related_)
{ {
QPoint pos = viewport()->mapToGlobal(this->pos()); QPoint pos = viewport()->mapToGlobal(this->pos());
replyPopup_.setReplyContent(related); replyPopup_.setReplyContent(related_);
replyPopup_.move(pos.x(), pos.y() - replyPopup_.height() - 10); replyPopup_.move(pos.x(), pos.y() - replyPopup_.height() - 10);
replyPopup_.setFixedWidth(this->parentWidget()->width()); replyPopup_.setFixedWidth(this->parentWidget()->width());
replyPopup_.show(); replyPopup_.show();
@ -467,7 +459,9 @@ FilteredTextEdit::uploadData(const QByteArray data,
emit startedUpload(); emit startedUpload();
emit media(buffer, mediaType, filename); emit media(buffer, mediaType, filename, related);
related = {};
closeReply();
} }
void void
@ -573,7 +567,6 @@ TextInputWidget::TextInputWidget(QWidget *parent)
connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit); connect(sendMessageBtn_, &FlatButton::clicked, input_, &FilteredTextEdit::submit);
connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection())); connect(sendFileBtn_, SIGNAL(clicked()), this, SLOT(openFileSelection()));
connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage); connect(input_, &FilteredTextEdit::message, this, &TextInputWidget::sendTextMessage);
connect(input_, &FilteredTextEdit::reply, this, &TextInputWidget::sendReplyMessage);
connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command); connect(input_, &FilteredTextEdit::command, this, &TextInputWidget::command);
connect(input_, &FilteredTextEdit::media, this, &TextInputWidget::uploadMedia); connect(input_, &FilteredTextEdit::media, this, &TextInputWidget::uploadMedia);
connect(emojiBtn_, connect(emojiBtn_,
@ -609,14 +602,16 @@ void
TextInputWidget::command(QString command, QString args) TextInputWidget::command(QString command, QString args)
{ {
if (command == "me") { if (command == "me") {
sendEmoteMessage(args); sendEmoteMessage(args, input_->related);
} else if (command == "join") { } else if (command == "join") {
sendJoinRoomRequest(args); sendJoinRoomRequest(args);
} else if (command == "shrug") { } else if (command == "shrug") {
sendTextMessage("¯\\_(ツ)_/¯"); sendTextMessage("¯\\_(ツ)_/¯", input_->related);
} else if (command == "fliptable") { } else if (command == "fliptable") {
sendTextMessage("(╯°□°)╯︵ ┻━┻"); sendTextMessage("(╯°□°)╯︵ ┻━┻", input_->related);
} }
input_->related = std::nullopt;
} }
void void
@ -635,7 +630,9 @@ TextInputWidget::openFileSelection()
QSharedPointer<QFile> file{new QFile{fileName, this}}; QSharedPointer<QFile> file{new QFile{fileName, this}};
emit uploadMedia(file, format, fileName); emit uploadMedia(file, format, fileName, input_->related);
input_->related = {};
input_->closeReply();
showUploadSpinner(); showUploadSpinner();
} }
@ -691,5 +688,5 @@ TextInputWidget::addReply(const RelatedInfo &related)
auto cursor = input_->textCursor(); auto cursor = input_->textCursor();
cursor.movePosition(QTextCursor::End); cursor.movePosition(QTextCursor::End);
input_->setTextCursor(cursor); input_->setTextCursor(cursor);
input_->setRelated(related); input_->related = related;
} }

View file

@ -20,6 +20,7 @@
#include <deque> #include <deque>
#include <iterator> #include <iterator>
#include <map> #include <map>
#include <optional>
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
@ -52,18 +53,27 @@ public:
QSize minimumSizeHint() const override; QSize minimumSizeHint() const override;
void submit(); void submit();
void setRelated(const RelatedInfo &related) { related_ = related; } void showReplyPopup(const RelatedInfo &related_);
void showReplyPopup(const RelatedInfo &related); void closeReply()
{
replyPopup_.hide();
related = {};
}
// Used for replies
std::optional<RelatedInfo> related;
signals: signals:
void heightChanged(int height); void heightChanged(int height);
void startedTyping(); void startedTyping();
void stoppedTyping(); void stoppedTyping();
void startedUpload(); void startedUpload();
void message(QString); void message(QString, const std::optional<RelatedInfo> &);
void reply(QString, const RelatedInfo &);
void command(QString name, QString args); void command(QString name, QString args);
void media(QSharedPointer<QIODevice> data, QString mimeClass, const QString &filename); void media(QSharedPointer<QIODevice> data,
QString mimeClass,
const QString &filename,
const std::optional<RelatedInfo> &related);
//! Trigger the suggestion popup. //! Trigger the suggestion popup.
void showSuggestions(const QString &query); void showSuggestions(const QString &query);
@ -93,9 +103,6 @@ private:
SuggestionsPopup suggestionsPopup_; SuggestionsPopup suggestionsPopup_;
ReplyPopup replyPopup_; ReplyPopup replyPopup_;
// Used for replies
RelatedInfo related_;
enum class AnchorType enum class AnchorType
{ {
Tab = 0, Tab = 0,
@ -107,11 +114,6 @@ private:
int anchorWidth(AnchorType anchor) { return static_cast<int>(anchor); } int anchorWidth(AnchorType anchor) { return static_cast<int>(anchor); }
void closeSuggestions() { suggestionsPopup_.hide(); } void closeSuggestions() { suggestionsPopup_.hide(); }
void closeReply()
{
replyPopup_.hide();
related_ = {};
}
void resetAnchor() { atTriggerPosition_ = -1; } void resetAnchor() { atTriggerPosition_ = -1; }
bool isAnchorValid() { return atTriggerPosition_ != -1; } bool isAnchorValid() { return atTriggerPosition_ != -1; }
bool hasAnchor(int pos, AnchorType anchor) bool hasAnchor(int pos, AnchorType anchor)
@ -171,14 +173,14 @@ private slots:
void addSelectedEmoji(const QString &emoji); void addSelectedEmoji(const QString &emoji);
signals: signals:
void sendTextMessage(QString msg); void sendTextMessage(const QString &msg, const std::optional<RelatedInfo> &related);
void sendReplyMessage(QString msg, const RelatedInfo &related); void sendEmoteMessage(QString msg, const std::optional<RelatedInfo> &related);
void sendEmoteMessage(QString msg);
void heightChanged(int height); void heightChanged(int height);
void uploadMedia(const QSharedPointer<QIODevice> data, void uploadMedia(const QSharedPointer<QIODevice> data,
QString mimeClass, QString mimeClass,
const QString &filename); const QString &filename,
const std::optional<RelatedInfo> &related);
void sendJoinRoomRequest(const QString &room); void sendJoinRoomRequest(const QString &room);
@ -196,9 +198,6 @@ private:
QHBoxLayout *topLayout_; QHBoxLayout *topLayout_;
FilteredTextEdit *input_; FilteredTextEdit *input_;
// Used for replies
QString related_event_;
LoadingIndicator *spinner_; LoadingIndicator *spinner_;
FlatButton *sendFileBtn_; FlatButton *sendFileBtn_;

View file

@ -170,38 +170,30 @@ TimelineViewManager::initWithMessages(const std::map<QString, mtx::responses::Ti
} }
void void
TimelineViewManager::queueTextMessage(const QString &msg) TimelineViewManager::queueTextMessage(const QString &msg, const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::Text text = {}; mtx::events::msg::Text text = {};
text.body = msg.trimmed().toStdString(); text.body = msg.trimmed().toStdString();
text.format = "org.matrix.custom.html"; text.format = "org.matrix.custom.html";
text.formatted_body = utils::markdownToHtml(msg).toStdString(); text.formatted_body = utils::markdownToHtml(msg).toStdString();
if (timeline_) if (related) {
timeline_->sendMessage(text); QString body;
} bool firstLine = true;
for (const auto &line : related->quoted_body.split("\n")) {
void if (firstLine) {
TimelineViewManager::queueReplyMessage(const QString &reply, const RelatedInfo &related) firstLine = false;
{ body = QString("> <%1> %2\n").arg(related->quoted_user).arg(line);
mtx::events::msg::Text text = {}; } else {
body = QString("%1\n> %2\n").arg(body).arg(line);
QString body; }
bool firstLine = true;
for (const auto &line : related.quoted_body.split("\n")) {
if (firstLine) {
firstLine = false;
body = QString("> <%1> %2\n").arg(related.quoted_user).arg(line);
} else {
body = QString("%1\n> %2\n").arg(body).arg(line);
} }
}
text.body = QString("%1\n%2").arg(body).arg(reply).toStdString(); text.body = QString("%1\n%2").arg(body).arg(msg).toStdString();
text.format = "org.matrix.custom.html"; text.formatted_body =
text.formatted_body = utils::getFormattedQuoteBody(*related, utils::markdownToHtml(msg)).toStdString();
utils::getFormattedQuoteBody(related, utils::markdownToHtml(reply)).toStdString(); text.relates_to.in_reply_to.event_id = related->related_event;
text.relates_to.in_reply_to.event_id = related.related_event; }
if (timeline_) if (timeline_)
timeline_->sendMessage(text); timeline_->sendMessage(text);
@ -229,7 +221,8 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize,
const QSize &dimensions) const QSize &dimensions,
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::Image image; mtx::events::msg::Image image;
image.info.mimetype = mime.toStdString(); image.info.mimetype = mime.toStdString();
@ -239,6 +232,10 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
image.info.h = dimensions.height(); image.info.h = dimensions.height();
image.info.w = dimensions.width(); image.info.w = dimensions.width();
image.file = file; image.file = file;
if (related)
image.relates_to.in_reply_to.event_id = related->related_event;
models.value(roomid)->sendMessage(image); models.value(roomid)->sendMessage(image);
} }
@ -249,7 +246,8 @@ TimelineViewManager::queueFileMessage(
const std::optional<mtx::crypto::EncryptedFile> &encryptedFile, const std::optional<mtx::crypto::EncryptedFile> &encryptedFile,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize) uint64_t dsize,
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::File file; mtx::events::msg::File file;
file.info.mimetype = mime.toStdString(); file.info.mimetype = mime.toStdString();
@ -257,6 +255,10 @@ TimelineViewManager::queueFileMessage(
file.body = filename.toStdString(); file.body = filename.toStdString();
file.url = url.toStdString(); file.url = url.toStdString();
file.file = encryptedFile; file.file = encryptedFile;
if (related)
file.relates_to.in_reply_to.event_id = related->related_event;
models.value(roomid)->sendMessage(file); models.value(roomid)->sendMessage(file);
} }
@ -266,7 +268,8 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize) uint64_t dsize,
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::Audio audio; mtx::events::msg::Audio audio;
audio.info.mimetype = mime.toStdString(); audio.info.mimetype = mime.toStdString();
@ -274,6 +277,10 @@ TimelineViewManager::queueAudioMessage(const QString &roomid,
audio.body = filename.toStdString(); audio.body = filename.toStdString();
audio.url = url.toStdString(); audio.url = url.toStdString();
audio.file = file; audio.file = file;
if (related)
audio.relates_to.in_reply_to.event_id = related->related_event;
models.value(roomid)->sendMessage(audio); models.value(roomid)->sendMessage(audio);
} }
@ -283,7 +290,8 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize) uint64_t dsize,
const std::optional<RelatedInfo> &related)
{ {
mtx::events::msg::Video video; mtx::events::msg::Video video;
video.info.mimetype = mime.toStdString(); video.info.mimetype = mime.toStdString();
@ -291,5 +299,9 @@ TimelineViewManager::queueVideoMessage(const QString &roomid,
video.body = filename.toStdString(); video.body = filename.toStdString();
video.url = url.toStdString(); video.url = url.toStdString();
video.file = file; video.file = file;
if (related)
video.relates_to.in_reply_to.event_id = related->related_event;
models.value(roomid)->sendMessage(video); models.value(roomid)->sendMessage(video);
} }

View file

@ -51,8 +51,7 @@ public slots:
void setHistoryView(const QString &room_id); void setHistoryView(const QString &room_id);
void updateColorPalette(); void updateColorPalette();
void queueTextMessage(const QString &msg); void queueTextMessage(const QString &msg, const std::optional<RelatedInfo> &related);
void queueReplyMessage(const QString &reply, const RelatedInfo &related);
void queueEmoteMessage(const QString &msg); void queueEmoteMessage(const QString &msg);
void queueImageMessage(const QString &roomid, void queueImageMessage(const QString &roomid,
const QString &filename, const QString &filename,
@ -60,25 +59,29 @@ public slots:
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize, uint64_t dsize,
const QSize &dimensions); const QSize &dimensions,
const std::optional<RelatedInfo> &related);
void queueFileMessage(const QString &roomid, void queueFileMessage(const QString &roomid,
const QString &filename, const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize); uint64_t dsize,
const std::optional<RelatedInfo> &related);
void queueAudioMessage(const QString &roomid, void queueAudioMessage(const QString &roomid,
const QString &filename, const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize); uint64_t dsize,
const std::optional<RelatedInfo> &related);
void queueVideoMessage(const QString &roomid, void queueVideoMessage(const QString &roomid,
const QString &filename, const QString &filename,
const std::optional<mtx::crypto::EncryptedFile> &file, const std::optional<mtx::crypto::EncryptedFile> &file,
const QString &url, const QString &url,
const QString &mime, const QString &mime,
uint64_t dsize); uint64_t dsize,
const std::optional<RelatedInfo> &related);
private: private:
#ifdef USE_QUICK_VIEW #ifdef USE_QUICK_VIEW