Send read receipts

Automatically dismiss unread notifications when the window regains
focus.

fixes #111
fixes #68
This commit is contained in:
Konstantinos Sideris 2017-11-24 00:10:58 +02:00
parent c6cf6c2b41
commit 0f363b5f44
11 changed files with 126 additions and 4 deletions

View file

@ -59,6 +59,7 @@ public:
void leaveRoom(const QString &roomId); void leaveRoom(const QString &roomId);
void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000); void sendTypingNotification(const QString &roomid, int timeoutInMillis = 20000);
void removeTypingNotification(const QString &roomid); void removeTypingNotification(const QString &roomid);
void readEvent(const QString &room_id, const QString &event_id);
QUrl getHomeServer() { return server_; }; QUrl getHomeServer() { return server_; };
int transactionId() { return txn_id_; }; int transactionId() { return txn_id_; };

View file

@ -66,6 +66,7 @@ public slots:
void closeJoinRoomDialog(bool isJoining, QString roomAlias); void closeJoinRoomDialog(bool isJoining, QString roomAlias);
void openLeaveRoomDialog(const QString &room_id); void openLeaveRoomDialog(const QString &room_id);
void closeLeaveRoomDialog(bool leaving, const QString &room_id); void closeLeaveRoomDialog(bool leaving, const QString &room_id);
void clearRoomMessageCount(const QString &room_id);
private: private:
void calculateUnreadMessageCount(); void calculateUnreadMessageCount();

View file

@ -66,7 +66,8 @@ public:
QWidget *parent); QWidget *parent);
void setUserAvatar(const QImage &pixmap); void setUserAvatar(const QImage &pixmap);
DescInfo descriptionMessage() const { return descriptionMsg_; }; DescInfo descriptionMessage() const { return descriptionMsg_; }
QString eventId() const { return event_id_; }
~TimelineItem(); ~TimelineItem();
@ -85,6 +86,7 @@ private:
void setupSimpleLayout(); void setupSimpleLayout();
QString replaceEmoji(const QString &body); QString replaceEmoji(const QString &body);
QString event_id_;
DescInfo descriptionMsg_; DescInfo descriptionMsg_;

View file

@ -121,15 +121,20 @@ private slots:
signals: signals:
void updateLastTimelineMessage(const QString &user, const DescInfo &info); void updateLastTimelineMessage(const QString &user, const DescInfo &info);
void clearUnreadMessageCount(const QString &room_id);
protected: protected:
void paintEvent(QPaintEvent *event) override; void paintEvent(QPaintEvent *event) override;
void showEvent(QShowEvent *event) override;
bool event(QEvent *event) override;
private: private:
void init(); void init();
void addTimelineItem(TimelineItem *item, TimelineDirection direction); void addTimelineItem(TimelineItem *item, TimelineDirection direction);
void updateLastSender(const QString &user_id, TimelineDirection direction); void updateLastSender(const QString &user_id, TimelineDirection direction);
void notifyForLastEvent(); void notifyForLastEvent();
void readLastEvent() const;
QString getLastEventId() const;
// Used to determine whether or not we should prefix a message with the // Used to determine whether or not we should prefix a message with the
// sender's name. // sender's name.

View file

@ -58,6 +58,7 @@ public:
static QMap<QString, QString> DISPLAY_NAMES; static QMap<QString, QString> DISPLAY_NAMES;
signals: signals:
void clearRoomMessageCount(QString roomid);
void unreadMessages(QString roomid, int count); void unreadMessages(QString roomid, int count);
void updateRoomsLastMessage(const QString &user, const DescInfo &info); void updateRoomsLastMessage(const QString &user, const DescInfo &info);

View file

@ -125,6 +125,11 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
connect( connect(
room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView); room_list_, &RoomList::roomChanged, view_manager_, &TimelineViewManager::setHistoryView);
connect(view_manager_,
&TimelineViewManager::clearRoomMessageCount,
room_list_,
&RoomList::clearRoomMessageCount);
connect(view_manager_, connect(view_manager_,
&TimelineViewManager::unreadMessages, &TimelineViewManager::unreadMessages,
this, this,

View file

@ -847,3 +847,31 @@ MatrixClient::removeTypingNotification(const QString &roomid)
put(request, QJsonDocument(body).toJson(QJsonDocument::Compact)); 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;
}
});
}

View file

@ -203,6 +203,18 @@ RoomList::sync(const QMap<QString, RoomState> &states,
} }
} }
void
RoomList::clearRoomMessageCount(const QString &room_id)
{
if (!rooms_.contains(room_id))
return;
auto room = rooms_[room_id];
room->clearUnreadMessageCount();
calculateUnreadMessageCount();
}
void void
RoomList::highlightSelectedRoom(const QString &room_id) RoomList::highlightSelectedRoom(const QString &room_id)
{ {
@ -213,9 +225,7 @@ RoomList::highlightSelectedRoom(const QString &room_id)
return; return;
} }
// TODO: Send a read receipt for the last event. clearRoomMessageCount(room_id);
auto room = rooms_[room_id];
room->clearUnreadMessageCount();
calculateUnreadMessageCount(); calculateUnreadMessageCount();

View file

@ -154,6 +154,8 @@ TimelineItem::TimelineItem(ImageItem *image,
{ {
init(); init();
event_id_ = event.eventId();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender()); auto displayName = TimelineViewManager::displayName(event.sender());
@ -193,6 +195,9 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event,
: QWidget(parent) : QWidget(parent)
{ {
init(); init();
event_id_ = event.eventId();
descriptionMsg_ = {TimelineViewManager::displayName(event.sender()), descriptionMsg_ = {TimelineViewManager::displayName(event.sender()),
event.sender(), event.sender(),
" sent a notification", " sent a notification",
@ -234,6 +239,8 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Emote> &event,
{ {
init(); init();
event_id_ = event.eventId();
auto body = event.content().body().trimmed(); auto body = event.content().body().trimmed();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender()); auto displayName = TimelineViewManager::displayName(event.sender());
@ -273,6 +280,8 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event,
{ {
init(); init();
event_id_ = event.eventId();
auto body = event.content().body().trimmed(); auto body = event.content().body().trimmed();
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp()); auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
auto displayName = TimelineViewManager::displayName(event.sender()); auto displayName = TimelineViewManager::displayName(event.sender());

View file

@ -379,6 +379,9 @@ TimelineView::addEvents(const Timeline &timeline)
if (!timeline.events().isEmpty() && scroll_layout_->count() > 1) if (!timeline.events().isEmpty() && scroll_layout_->count() > 1)
notifyForLastEvent(); notifyForLastEvent();
if (isActiveWindow() && isVisible() && timeline.events().size() > 0)
readLastEvent();
return message_count; return message_count;
} }
@ -648,3 +651,52 @@ TimelineView::paintEvent(QPaintEvent *)
QPainter p(this); QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &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<TimelineItem *>(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);
}

View file

@ -131,6 +131,10 @@ TimelineViewManager::addRoom(const JoinedRoom &room, const QString &room_id)
&TimelineView::updateLastTimelineMessage, &TimelineView::updateLastTimelineMessage,
this, this,
&TimelineViewManager::updateRoomsLastMessage); &TimelineViewManager::updateRoomsLastMessage);
connect(view,
&TimelineView::clearUnreadMessageCount,
this,
&TimelineViewManager::clearRoomMessageCount);
// Add the view in the widget stack. // Add the view in the widget stack.
addWidget(view); addWidget(view);
@ -147,6 +151,10 @@ TimelineViewManager::addRoom(const QString &room_id)
&TimelineView::updateLastTimelineMessage, &TimelineView::updateLastTimelineMessage,
this, this,
&TimelineViewManager::updateRoomsLastMessage); &TimelineViewManager::updateRoomsLastMessage);
connect(view,
&TimelineView::clearUnreadMessageCount,
this,
&TimelineViewManager::clearRoomMessageCount);
// Add the view in the widget stack. // Add the view in the widget stack.
addWidget(view); addWidget(view);