From 0f363b5f4424cc4cdf0e36d7aa5b62b8e8ea52bc Mon Sep 17 00:00:00 2001 From: Konstantinos Sideris Date: Fri, 24 Nov 2017 00:10:58 +0200 Subject: [PATCH] Send read receipts Automatically dismiss unread notifications when the window regains focus. fixes #111 fixes #68 --- include/MatrixClient.h | 1 + include/RoomList.h | 1 + include/TimelineItem.h | 4 ++- include/TimelineView.h | 5 ++++ include/TimelineViewManager.h | 1 + src/ChatPage.cc | 5 ++++ src/MatrixClient.cc | 28 +++++++++++++++++++ src/RoomList.cc | 16 +++++++++-- src/TimelineItem.cc | 9 ++++++ src/TimelineView.cc | 52 +++++++++++++++++++++++++++++++++++ src/TimelineViewManager.cc | 8 ++++++ 11 files changed, 126 insertions(+), 4 deletions(-) diff --git a/include/MatrixClient.h b/include/MatrixClient.h index 422e4cfa..999fbe47 100644 --- a/include/MatrixClient.h +++ b/include/MatrixClient.h @@ -59,6 +59,7 @@ public: void leaveRoom(const QString &roomId); void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000); void removeTypingNotification(const QString &roomid); + void readEvent(const QString &room_id, const QString &event_id); QUrl getHomeServer() { return server_; }; int transactionId() { return txn_id_; }; diff --git a/include/RoomList.h b/include/RoomList.h index f1653a38..7a48f7bd 100644 --- a/include/RoomList.h +++ b/include/RoomList.h @@ -66,6 +66,7 @@ public slots: void closeJoinRoomDialog(bool isJoining, QString roomAlias); void openLeaveRoomDialog(const QString &room_id); void closeLeaveRoomDialog(bool leaving, const QString &room_id); + void clearRoomMessageCount(const QString &room_id); private: void calculateUnreadMessageCount(); diff --git a/include/TimelineItem.h b/include/TimelineItem.h index d90810d5..cd522308 100644 --- a/include/TimelineItem.h +++ b/include/TimelineItem.h @@ -66,7 +66,8 @@ public: QWidget *parent); void setUserAvatar(const QImage &pixmap); - DescInfo descriptionMessage() const { return descriptionMsg_; }; + DescInfo descriptionMessage() const { return descriptionMsg_; } + QString eventId() const { return event_id_; } ~TimelineItem(); @@ -85,6 +86,7 @@ private: void setupSimpleLayout(); QString replaceEmoji(const QString &body); + QString event_id_; DescInfo descriptionMsg_; diff --git a/include/TimelineView.h b/include/TimelineView.h index 78c31e8e..3f506002 100644 --- a/include/TimelineView.h +++ b/include/TimelineView.h @@ -121,15 +121,20 @@ private slots: signals: void updateLastTimelineMessage(const QString &user, const DescInfo &info); + void clearUnreadMessageCount(const QString &room_id); protected: void paintEvent(QPaintEvent *event) override; + void showEvent(QShowEvent *event) override; + bool event(QEvent *event) override; private: void init(); void addTimelineItem(TimelineItem *item, TimelineDirection direction); void updateLastSender(const QString &user_id, TimelineDirection direction); void notifyForLastEvent(); + void readLastEvent() const; + QString getLastEventId() const; // Used to determine whether or not we should prefix a message with the // sender's name. diff --git a/include/TimelineViewManager.h b/include/TimelineViewManager.h index 5bd3054f..d9fb730e 100644 --- a/include/TimelineViewManager.h +++ b/include/TimelineViewManager.h @@ -58,6 +58,7 @@ public: static QMap DISPLAY_NAMES; signals: + void clearRoomMessageCount(QString roomid); void unreadMessages(QString roomid, int count); void updateRoomsLastMessage(const QString &user, const DescInfo &info); diff --git a/src/ChatPage.cc b/src/ChatPage.cc index 4dbda90d..82e694a1 100644 --- a/src/ChatPage.cc +++ b/src/ChatPage.cc @@ -125,6 +125,11 @@ ChatPage::ChatPage(QSharedPointer client, QWidget *parent) connect( room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView); + connect(view_manager_, + &TimelineViewManager::clearRoomMessageCount, + room_list_, + &RoomList::clearRoomMessageCount); + connect(view_manager_, &TimelineViewManager::unreadMessages, this, diff --git a/src/MatrixClient.cc b/src/MatrixClient.cc index 5589bdc7..dcf241a6 100644 --- a/src/MatrixClient.cc +++ b/src/MatrixClient.cc @@ -847,3 +847,31 @@ MatrixClient::removeTypingNotification(const QString &roomid) put(request, QJsonDocument(body).toJson(QJsonDocument::Compact)); } + +void +MatrixClient::readEvent(const QString &room_id, const QString &event_id) +{ + QUrlQuery query; + query.addQueryItem("access_token", token_); + + QUrl endpoint(server_); + endpoint.setPath(clientApiUrl_ + + QString("/rooms/%1/receipt/m.read/%2").arg(room_id).arg(event_id)); + endpoint.setQuery(query); + + QNetworkRequest request(QString(endpoint.toEncoded())); + request.setHeader(QNetworkRequest::KnownHeaders::ContentTypeHeader, "application/json"); + + auto reply = post(request, "{}"); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + reply->deleteLater(); + + int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); + + if (status == 0 || status >= 400) { + qWarning() << reply->errorString(); + return; + } + }); +} diff --git a/src/RoomList.cc b/src/RoomList.cc index b1d3a9ca..402633c3 100644 --- a/src/RoomList.cc +++ b/src/RoomList.cc @@ -203,6 +203,18 @@ RoomList::sync(const QMap &states, } } +void +RoomList::clearRoomMessageCount(const QString &room_id) +{ + if (!rooms_.contains(room_id)) + return; + + auto room = rooms_[room_id]; + room->clearUnreadMessageCount(); + + calculateUnreadMessageCount(); +} + void RoomList::highlightSelectedRoom(const QString &room_id) { @@ -213,9 +225,7 @@ RoomList::highlightSelectedRoom(const QString &room_id) return; } - // TODO: Send a read receipt for the last event. - auto room = rooms_[room_id]; - room->clearUnreadMessageCount(); + clearRoomMessageCount(room_id); calculateUnreadMessageCount(); diff --git a/src/TimelineItem.cc b/src/TimelineItem.cc index 8c21e61d..263eb70d 100644 --- a/src/TimelineItem.cc +++ b/src/TimelineItem.cc @@ -154,6 +154,8 @@ TimelineItem::TimelineItem(ImageItem *image, { init(); + event_id_ = event.eventId(); + auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto displayName = TimelineViewManager::displayName(event.sender()); @@ -193,6 +195,9 @@ TimelineItem::TimelineItem(const events::MessageEvent &event, : QWidget(parent) { init(); + + event_id_ = event.eventId(); + descriptionMsg_ = {TimelineViewManager::displayName(event.sender()), event.sender(), " sent a notification", @@ -234,6 +239,8 @@ TimelineItem::TimelineItem(const events::MessageEvent &event, { init(); + event_id_ = event.eventId(); + auto body = event.content().body().trimmed(); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto displayName = TimelineViewManager::displayName(event.sender()); @@ -273,6 +280,8 @@ TimelineItem::TimelineItem(const events::MessageEvent &event, { init(); + event_id_ = event.eventId(); + auto body = event.content().body().trimmed(); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto displayName = TimelineViewManager::displayName(event.sender()); diff --git a/src/TimelineView.cc b/src/TimelineView.cc index 267fbbff..44f3b9d8 100644 --- a/src/TimelineView.cc +++ b/src/TimelineView.cc @@ -379,6 +379,9 @@ TimelineView::addEvents(const Timeline &timeline) if (!timeline.events().isEmpty() && scroll_layout_->count() > 1) notifyForLastEvent(); + if (isActiveWindow() && isVisible() && timeline.events().size() > 0) + readLastEvent(); + return message_count; } @@ -648,3 +651,52 @@ TimelineView::paintEvent(QPaintEvent *) QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); } + +void +TimelineView::readLastEvent() const +{ + const auto eventId = getLastEventId(); + + if (!eventId.isEmpty()) + client_->readEvent(room_id_, eventId); +} + +QString +TimelineView::getLastEventId() const +{ + auto index = scroll_layout_->count(); + + // Search backwards for the first event that has a valid event id. + while (index > 0) { + --index; + + auto lastItem = scroll_layout_->itemAt(index); + auto *lastTimelineItem = qobject_cast(lastItem->widget()); + + if (lastTimelineItem && !lastTimelineItem->eventId().isEmpty()) + return lastTimelineItem->eventId(); + } + + return QString(""); +} + +void +TimelineView::showEvent(QShowEvent *event) +{ + readLastEvent(); + + QWidget::showEvent(event); +} + +bool +TimelineView::event(QEvent *event) +{ + if (event->type() == QEvent::WindowActivate) { + QTimer::singleShot(1000, this, [=]() { + emit clearUnreadMessageCount(room_id_); + readLastEvent(); + }); + } + + return QWidget::event(event); +} diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc index ec7b8446..1f047d7c 100644 --- a/src/TimelineViewManager.cc +++ b/src/TimelineViewManager.cc @@ -131,6 +131,10 @@ TimelineViewManager::addRoom(const JoinedRoom &room, const QString &room_id) &TimelineView::updateLastTimelineMessage, this, &TimelineViewManager::updateRoomsLastMessage); + connect(view, + &TimelineView::clearUnreadMessageCount, + this, + &TimelineViewManager::clearRoomMessageCount); // Add the view in the widget stack. addWidget(view); @@ -147,6 +151,10 @@ TimelineViewManager::addRoom(const QString &room_id) &TimelineView::updateLastTimelineMessage, this, &TimelineViewManager::updateRoomsLastMessage); + connect(view, + &TimelineView::clearUnreadMessageCount, + this, + &TimelineViewManager::clearRoomMessageCount); // Add the view in the widget stack. addWidget(view);