diff options
-rw-r--r-- | include/ChatPage.h | 2 | ||||
-rw-r--r-- | include/HistoryView.h | 15 | ||||
-rw-r--r-- | include/HistoryViewItem.h | 12 | ||||
-rw-r--r-- | include/HistoryViewManager.h | 11 | ||||
-rw-r--r-- | include/MatrixClient.h | 8 | ||||
-rw-r--r-- | src/ChatPage.cc | 22 | ||||
-rw-r--r-- | src/HistoryView.cc | 88 | ||||
-rw-r--r-- | src/HistoryViewItem.cc | 87 | ||||
-rw-r--r-- | src/HistoryViewManager.cc | 41 | ||||
-rw-r--r-- | src/MatrixClient.cc | 6 |
10 files changed, 244 insertions, 48 deletions
diff --git a/include/ChatPage.h b/include/ChatPage.h index d0edac3a..fd0b63dc 100644 --- a/include/ChatPage.h +++ b/include/ChatPage.h @@ -57,8 +57,6 @@ private slots: void syncCompleted(const SyncResponse &response); void syncFailed(const QString &msg); void changeTopRoomInfo(const RoomInfo &info); - void sendTextMessage(const QString &msg); - void messageSent(const QString event_id, int txn_id); void startSync(); void logout(); diff --git a/include/HistoryView.h b/include/HistoryView.h index 9eb0783f..ba267ad4 100644 --- a/include/HistoryView.h +++ b/include/HistoryView.h @@ -27,6 +27,15 @@ #include "HistoryViewItem.h" #include "Sync.h" +// Contains info about a message shown in the history view +// but not yet confirmed by the homeserver through sync. +struct PendingMessage { + int txn_id; + QString body; + QString event_id; + HistoryViewItem *widget; +}; + class HistoryView : public QWidget { Q_OBJECT @@ -38,6 +47,8 @@ public: void addHistoryItem(const Event &event, const QString &color, bool with_sender); void addEvents(const QList<Event> &events); + void addUserTextMessage(const QString &msg, int txn_id); + void updatePendingMessage(int txn_id, QString event_id); void clear(); public slots: @@ -45,6 +56,8 @@ public slots: private: void init(); + void removePendingMessage(const Event &event); + bool isPendingMessage(const Event &event, const QString &userid); QVBoxLayout *top_layout_; QVBoxLayout *scroll_layout_; @@ -53,6 +66,8 @@ private: QWidget *scroll_widget_; QString last_sender_; + + QList<PendingMessage> pending_msgs_; }; #endif // HISTORY_VIEW_H diff --git a/include/HistoryViewItem.h b/include/HistoryViewItem.h index 2d93e451..e1ed3374 100644 --- a/include/HistoryViewItem.h +++ b/include/HistoryViewItem.h @@ -28,10 +28,22 @@ class HistoryViewItem : public QWidget { Q_OBJECT public: + // For remote messages. HistoryViewItem(const Event &event, bool with_sender, const QString &color, QWidget *parent = 0); + + // For local messages. + HistoryViewItem(const QString &userid, const QString &color, const QString &body, QWidget *parent = 0); + HistoryViewItem(const QString &body, QWidget *parent = 0); + ~HistoryViewItem(); private: + void generateBody(const QString &body); + void generateBody(const QString &userid, const QString &color, const QString &body); + void generateTimestamp(const QDateTime &time); + + void setupLayout(); + QHBoxLayout *top_layout_; QLabel *time_label_; diff --git a/include/HistoryViewManager.h b/include/HistoryViewManager.h index cdb20e98..9f07f064 100644 --- a/include/HistoryViewManager.h +++ b/include/HistoryViewManager.h @@ -19,10 +19,12 @@ #define HISTORY_VIEW_MANAGER_H #include <QDebug> +#include <QSharedPointer> #include <QStackedWidget> #include <QWidget> #include "HistoryView.h" +#include "MatrixClient.h" #include "RoomInfo.h" #include "Sync.h" @@ -31,7 +33,7 @@ class HistoryViewManager : public QStackedWidget Q_OBJECT public: - HistoryViewManager(QWidget *parent); + HistoryViewManager(QSharedPointer<MatrixClient> client, QWidget *parent); ~HistoryViewManager(); void initialize(const Rooms &rooms); @@ -39,14 +41,21 @@ public: void clearAll(); static QString chooseRandomColor(); + static QString getUserColor(const QString &userid); static QMap<QString, QString> NICK_COLORS; static const QList<QString> COLORS; public slots: void setHistoryView(const RoomInfo &info); + void sendTextMessage(const QString &msg); + +private slots: + void messageSent(const QString &eventid, const QString &roomid, int txnid); private: + RoomInfo active_room_; QMap<QString, HistoryView *> views_; + QSharedPointer<MatrixClient> client_; }; #endif diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 021a2594..8d517b9a 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -44,6 +44,7 @@ public: void fetchOwnAvatar(const QUrl &avatar_url); inline QString getHomeServer(); + inline int transactionId(); inline void incrementTransactionId(); void reset() noexcept; @@ -73,7 +74,7 @@ signals: void initialSyncCompleted(const SyncResponse &response); void syncCompleted(const SyncResponse &response); void syncFailed(const QString &msg); - void messageSent(const QString &event_id, const int txn_id); + void messageSent(const QString &event_id, const QString &roomid, const int txn_id); private slots: void onResponse(QNetworkReply *reply); @@ -126,6 +127,11 @@ inline QString MatrixClient::getHomeServer() return server_; } +inline int MatrixClient::transactionId() +{ + return txn_id_; +} + inline void MatrixClient::setServer(const QString &server) { server_ = "https://" + server; diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 33f98af3..dfe1505e 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -39,7 +39,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent) top_bar_ = new TopRoomBar(this); ui->topBarLayout->addWidget(top_bar_); - view_manager_ = new HistoryViewManager(this); + view_manager_ = new HistoryViewManager(client, this); ui->mainContentLayout->addWidget(view_manager_); text_input_ = new TextInputWidget(this); @@ -66,7 +66,7 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent) connect(text_input_, SIGNAL(sendTextMessage(const QString &)), - this, + view_manager_, SLOT(sendTextMessage(const QString &))); connect(client_.data(), @@ -94,10 +94,6 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent) SIGNAL(ownAvatarRetrieved(const QPixmap &)), this, SLOT(setOwnAvatar(const QPixmap &))); - connect(client_.data(), - SIGNAL(messageSent(QString, int)), - this, - SLOT(messageSent(QString, int))); } void ChatPage::logout() @@ -123,20 +119,6 @@ void ChatPage::logout() emit close(); } -void ChatPage::messageSent(QString event_id, int txn_id) -{ - Q_UNUSED(event_id); - - QSettings settings; - settings.setValue("client/transaction_id", txn_id + 1); -} - -void ChatPage::sendTextMessage(const QString &msg) -{ - auto room = current_room_; - client_->sendTextMessage(current_room_.id(), msg); -} - void ChatPage::bootstrap(QString userid, QString homeserver, QString token) { Q_UNUSED(userid); diff --git a/src/HistoryView.cc b/src/HistoryView.cc index ce2e2f6c..f1e19aec 100644 --- a/src/HistoryView.cc +++ b/src/HistoryView.cc @@ -17,6 +17,7 @@ #include <QDebug> #include <QScrollBar> +#include <QSettings> #include <QtWidgets/QLabel> #include <QtWidgets/QSpacerItem> @@ -51,18 +52,21 @@ void HistoryView::sliderRangeChanged(int min, int max) void HistoryView::addEvents(const QList<Event> &events) { + QSettings settings; + auto local_user = settings.value("auth/user_id").toString(); + for (const auto &event : events) { if (event.type() == "m.room.message") { auto msg_type = event.content().value("msgtype").toString(); + if (isPendingMessage(event, local_user)) { + removePendingMessage(event); + continue; + } + if (msg_type == "m.text" || msg_type == "m.notice") { auto with_sender = last_sender_ != event.sender(); - auto color = HistoryViewManager::NICK_COLORS.value(event.sender()); - - if (color.isEmpty()) { - color = HistoryViewManager::chooseRandomColor(); - HistoryViewManager::NICK_COLORS.insert(event.sender(), color); - } + auto color = HistoryViewManager::getUserColor(event.sender()); addHistoryItem(event, color, with_sender); last_sender_ = event.sender(); @@ -103,11 +107,81 @@ void HistoryView::init() void HistoryView::addHistoryItem(const Event &event, const QString &color, bool with_sender) { - // TODO: Probably create another function instead of passing the flag. HistoryViewItem *item = new HistoryViewItem(event, with_sender, color, scroll_widget_); scroll_layout_->addWidget(item); } +void HistoryView::updatePendingMessage(int txn_id, QString event_id) +{ + for (auto &msg : pending_msgs_) { + if (msg.txn_id == txn_id) { + msg.event_id = event_id; + break; + } + } +} + +bool HistoryView::isPendingMessage(const Event &event, const QString &userid) +{ + if (event.sender() != userid || event.type() != "m.room.message") + return false; + + auto msgtype = event.content().value("msgtype").toString(); + auto body = event.content().value("body").toString(); + + // FIXME: should contain more checks later on for other types of messages. + if (msgtype != "m.text") + return false; + + for (const auto &msg : pending_msgs_) { + if (msg.event_id == event.eventId() || msg.body == body) + return true; + } + + return false; +} + +void HistoryView::removePendingMessage(const Event &event) +{ + auto body = event.content().value("body").toString(); + + for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); it++) { + int index = std::distance(pending_msgs_.begin(), it); + + if (it->event_id == event.eventId() || it->body == body) { + pending_msgs_.removeAt(index); + break; + } + } +} + +void HistoryView::addUserTextMessage(const QString &body, int txn_id) +{ + QSettings settings; + auto user_id = settings.value("auth/user_id").toString(); + + auto with_sender = last_sender_ != user_id; + auto color = HistoryViewManager::getUserColor(user_id); + + HistoryViewItem *view_item; + + if (with_sender) + view_item = new HistoryViewItem(user_id, color, body, scroll_widget_); + else + view_item = new HistoryViewItem(body, scroll_widget_); + + scroll_layout_->addWidget(view_item); + + last_sender_ = user_id; + + PendingMessage message = {}; + message.txn_id = txn_id; + message.body = body; + message.widget = view_item; + + pending_msgs_.push_back(message); +} + HistoryView::~HistoryView() { } diff --git a/src/HistoryViewItem.cc b/src/HistoryViewItem.cc index 8eb92372..83d3cf85 100644 --- a/src/HistoryViewItem.cc +++ b/src/HistoryViewItem.cc @@ -20,35 +20,63 @@ #include "HistoryViewItem.h" -HistoryViewItem::HistoryViewItem(const Event &event, bool with_sender, const QString &color, QWidget *parent) +HistoryViewItem::HistoryViewItem(const QString &userid, const QString &color, const QString &body, QWidget *parent) : QWidget(parent) { - QString sender = ""; + generateTimestamp(QDateTime::currentDateTime()); + generateBody(userid, color, body); + setupLayout(); +} - if (with_sender) - sender = event.sender().split(":")[0].split("@")[1]; +HistoryViewItem::HistoryViewItem(const QString &body, QWidget *parent) + : QWidget(parent) +{ + generateTimestamp(QDateTime::currentDateTime()); + generateBody(body); + setupLayout(); +} - auto body = event.content().value("body").toString().trimmed(); +HistoryViewItem::HistoryViewItem(const Event &event, bool with_sender, const QString &color, QWidget *parent) + : QWidget(parent) +{ + auto body = event.content().value("body").toString().trimmed().toHtmlEscaped(); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); - auto local_time = timestamp.toString("HH:mm"); + + generateTimestamp(timestamp); if (event.content().value("msgtype").toString() == "m.notice") body = "<i style=\"color: #565E5E\">" + body + "</i>"; - time_label_ = new QLabel(this); - time_label_->setWordWrap(true); - QString msg( + if (with_sender) + generateBody(event.sender(), color, body); + else + generateBody(body); + + setupLayout(); +} + +void HistoryViewItem::generateBody(const QString &body) +{ + content_label_ = new QLabel(this); + content_label_->setWordWrap(true); + content_label_->setAlignment(Qt::AlignTop); + content_label_->setStyleSheet("margin: 0;"); + QString content( "<html>" "<head/>" "<body>" - " <span style=\"font-size: 7pt; color: #5d6565;\">" + " <span style=\"font-size: 10pt; color: #B1AEA5;\">" " %1" " </span>" "</body>" "</html>"); - time_label_->setText(msg.arg(local_time)); - time_label_->setStyleSheet("margin-left: 7px; margin-right: 7px; margin-top: 0;"); - time_label_->setAlignment(Qt::AlignTop); + content_label_->setText(content.arg(body)); + content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse); +} + +void HistoryViewItem::generateBody(const QString &userid, const QString &color, const QString &body) +{ + auto sender = userid.split(":")[0].split("@")[1]; content_label_ = new QLabel(this); content_label_->setWordWrap(true); @@ -68,10 +96,41 @@ HistoryViewItem::HistoryViewItem(const Event &event, bool with_sender, const QSt "</html>"); content_label_->setText(content.arg(color).arg(sender).arg(body)); content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse); +} + +void HistoryViewItem::generateTimestamp(const QDateTime &time) +{ + auto local_time = time.toString("HH:mm"); + + time_label_ = new QLabel(this); + QString msg( + "<html>" + "<head/>" + "<body>" + " <span style=\"font-size: 7pt; color: #5d6565;\">" + " %1" + " </span>" + "</body>" + "</html>"); + time_label_->setText(msg.arg(local_time)); + time_label_->setStyleSheet("margin-left: 7px; margin-right: 7px; margin-top: 0;"); + time_label_->setAlignment(Qt::AlignTop); +} + +void HistoryViewItem::setupLayout() +{ + if (time_label_ == nullptr) { + qWarning() << "HistoryViewItem: Time label is not initialized"; + return; + } + + if (content_label_ == nullptr) { + qWarning() << "HistoryViewItem: Content label is not initialized"; + return; + } top_layout_ = new QHBoxLayout(); top_layout_->setMargin(0); - top_layout_->addWidget(time_label_); top_layout_->addWidget(content_label_, 1); diff --git a/src/HistoryViewManager.cc b/src/HistoryViewManager.cc index a91dcc4f..706a42f1 100644 --- a/src/HistoryViewManager.cc +++ b/src/HistoryViewManager.cc @@ -18,14 +18,16 @@ #include <random> #include <QDebug> +#include <QSettings> #include <QStackedWidget> #include <QWidget> #include "HistoryView.h" #include "HistoryViewManager.h" -HistoryViewManager::HistoryViewManager(QWidget *parent) +HistoryViewManager::HistoryViewManager(QSharedPointer<MatrixClient> client, QWidget *parent) : QStackedWidget(parent) + , client_(client) { setStyleSheet( "QWidget {background: #171919; color: #ebebeb;}" @@ -33,12 +35,36 @@ HistoryViewManager::HistoryViewManager(QWidget *parent) "QScrollBar::handle:vertical { border-radius : 50px; background-color : #1c3133; }" "QScrollBar::add-line:vertical { border: none; background: none; }" "QScrollBar::sub-line:vertical { border: none; background: none; }"); + + connect(client_.data(), + SIGNAL(messageSent(const QString &, const QString &, int)), + this, + SLOT(messageSent(const QString &, const QString &, int))); } HistoryViewManager::~HistoryViewManager() { } +void HistoryViewManager::messageSent(const QString &event_id, const QString &roomid, int txn_id) +{ + // We save the latest valid transaction ID for later use. + QSettings settings; + settings.setValue("client/transaction_id", txn_id + 1); + + auto view = views_[roomid]; + view->updatePendingMessage(txn_id, event_id); +} + +void HistoryViewManager::sendTextMessage(const QString &msg) +{ + auto room = active_room_; + auto view = views_[room.id()]; + + view->addUserTextMessage(msg, client_->transactionId()); + client_->sendTextMessage(room.id(), msg); +} + void HistoryViewManager::clearAll() { NICK_COLORS.clear(); @@ -92,6 +118,7 @@ void HistoryViewManager::setHistoryView(const RoomInfo &info) return; } + active_room_ = info; auto widget = views_.value(info.id()); setCurrentWidget(widget); @@ -132,3 +159,15 @@ QString HistoryViewManager::chooseRandomColor() return HistoryViewManager::COLORS[dist(engine)]; } + +QString HistoryViewManager::getUserColor(const QString &userid) +{ + auto color = NICK_COLORS.value(userid); + + if (color.isEmpty()) { + color = chooseRandomColor(); + NICK_COLORS.insert(userid, color); + } + + return color; +} diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 7b45646c..9299c7eb 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -258,9 +258,8 @@ void MatrixClient::onSendTextMessageResponse(QNetworkReply *reply) } emit messageSent(object.value("event_id").toString(), + reply->property("roomid").toString(), reply->property("txn_id").toInt()); - - incrementTransactionId(); } void MatrixClient::onRoomAvatarResponse(QNetworkReply *reply) @@ -446,6 +445,9 @@ void MatrixClient::sendTextMessage(const QString &roomid, const QString &msg) no reply->setProperty("endpoint", Endpoint::SendTextMessage); reply->setProperty("txn_id", txn_id_); + reply->setProperty("roomid", roomid); + + incrementTransactionId(); } void MatrixClient::initialSync() noexcept |