From 146aaa87465765cd9d3153d95b0791c587902d62 Mon Sep 17 00:00:00 2001
From: Konstantinos Sideris <sideris.konstantin@gmail.com>
Date: Sun, 6 Aug 2017 18:53:31 +0300
Subject: [PATCH] Display the most recent message instead of the topic

closes #11
---
 include/RoomInfoListItem.h    | 16 ++++++++++++++--
 include/RoomList.h            |  1 +
 include/TimelineItem.h        | 10 ++++++++++
 include/TimelineView.h        |  5 +++++
 include/TimelineViewManager.h |  2 ++
 src/ChatPage.cc               |  5 +++++
 src/RoomInfoListItem.cc       | 28 ++++++++++++++++++++++++++--
 src/RoomList.cc               | 15 ++++++++++++---
 src/TimelineItem.cc           | 35 +++++++++++++++++++++++++++++++++++
 src/TimelineView.cc           | 19 +++++++++++++++++++
 src/TimelineViewManager.cc    | 10 ++++++++++
 11 files changed, 139 insertions(+), 7 deletions(-)

diff --git a/include/RoomInfoListItem.h b/include/RoomInfoListItem.h
index 8ead930be..af02126a6 100644
--- a/include/RoomInfoListItem.h
+++ b/include/RoomInfoListItem.h
@@ -26,6 +26,12 @@
 #include "RoomSettings.h"
 #include "RoomState.h"
 
+struct DescInfo {
+	QString username;
+	QString body;
+	QString timestamp;
+};
+
 class RoomInfoListItem : public QWidget
 {
 	Q_OBJECT
@@ -46,6 +52,7 @@ public:
 	inline RoomState state() const;
 	inline void setAvatar(const QImage &avatar_image);
 	inline int unreadMessageCount() const;
+	inline void setDescriptionMessage(const DescInfo &info);
 
 signals:
 	void clicked(const QString &room_id);
@@ -71,8 +78,8 @@ private:
 
 	QString roomId_;
 	QString roomName_;
-	QString lastMessage_;
-	QString lastTimestamp_;
+
+	DescInfo lastMsgInfo_;
 
 	QPixmap roomAvatar_;
 
@@ -107,3 +114,8 @@ inline void RoomInfoListItem::setAvatar(const QImage &img)
 	roomAvatar_ = QPixmap::fromImage(img.scaled(IconSize, IconSize, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
 	update();
 }
+
+inline void RoomInfoListItem::setDescriptionMessage(const DescInfo &info)
+{
+	lastMsgInfo_ = info;
+}
diff --git a/include/RoomList.h b/include/RoomList.h
index 417ed3636..f548400d1 100644
--- a/include/RoomList.h
+++ b/include/RoomList.h
@@ -49,6 +49,7 @@ public slots:
 	void updateRoomAvatar(const QString &roomid, const QPixmap &img);
 	void highlightSelectedRoom(const QString &room_id);
 	void updateUnreadMessageCount(const QString &roomid, int count);
+	void updateRoomDescription(const QString &roomid, const DescInfo &info);
 
 private:
 	void calculateUnreadMessageCount();
diff --git a/include/TimelineItem.h b/include/TimelineItem.h
index 50a21df37..cc5b3da73 100644
--- a/include/TimelineItem.h
+++ b/include/TimelineItem.h
@@ -28,6 +28,7 @@
 #include "Image.h"
 #include "MessageEvent.h"
 #include "Notice.h"
+#include "RoomInfoListItem.h"
 #include "Text.h"
 
 namespace events = matrix::events;
@@ -48,6 +49,7 @@ public:
 	TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, QWidget *parent);
 
 	void setUserAvatar(const QImage &pixmap);
+	inline DescInfo descriptionMessage() const;
 
 	~TimelineItem();
 
@@ -57,12 +59,15 @@ private:
 	void generateBody(const QString &body);
 	void generateBody(const QString &userid, const QString &color, const QString &body);
 	void generateTimestamp(const QDateTime &time);
+	QString descriptiveTime(const QDateTime &then);
 
 	void setupAvatarLayout(const QString &userName);
 	void setupSimpleLayout();
 
 	QString replaceEmoji(const QString &body);
 
+	DescInfo descriptionMsg_;
+
 	QHBoxLayout *topLayout_;
 	QVBoxLayout *sideLayout_;  // Avatar or Timestamp
 	QVBoxLayout *mainLayout_;  // Header & Message body
@@ -77,3 +82,8 @@ private:
 	QLabel *userName_;
 	QLabel *body_;
 };
+
+inline DescInfo TimelineItem::descriptionMessage() const
+{
+	return descriptionMsg_;
+}
diff --git a/include/TimelineView.h b/include/TimelineView.h
index f1860dbe4..628b3e0d0 100644
--- a/include/TimelineView.h
+++ b/include/TimelineView.h
@@ -29,6 +29,7 @@
 
 #include "Image.h"
 #include "Notice.h"
+#include "RoomInfoListItem.h"
 #include "Text.h"
 
 namespace msgs = matrix::events::messages;
@@ -83,11 +84,15 @@ public slots:
 	// Add old events at the top of the timeline.
 	void addBackwardsEvents(const QString &room_id, const RoomMessages &msgs);
 
+signals:
+	void updateLastTimelineMessage(const QString &user, const DescInfo &info);
+
 private:
 	void init();
 	void removePendingMessage(const events::MessageEvent<msgs::Text> &e);
 	void addTimelineItem(TimelineItem *item, TimelineDirection direction);
 	void updateLastSender(const QString &user_id, TimelineDirection direction);
+	void notifyForLastEvent();
 
 	// Used to determine whether or not we should prefix a message with the sender's name.
 	bool isSenderRendered(const QString &user_id, TimelineDirection direction);
diff --git a/include/TimelineViewManager.h b/include/TimelineViewManager.h
index c5b37542b..64140e3ad 100644
--- a/include/TimelineViewManager.h
+++ b/include/TimelineViewManager.h
@@ -23,6 +23,7 @@
 #include <QWidget>
 
 #include "MatrixClient.h"
+#include "RoomInfoListItem.h"
 #include "Sync.h"
 #include "TimelineView.h"
 
@@ -50,6 +51,7 @@ public:
 
 signals:
 	void unreadMessages(QString roomid, int count);
+	void updateRoomsLastMessage(const QString &user, const DescInfo &info);
 
 public slots:
 	void setHistoryView(const QString &room_id);
diff --git a/src/ChatPage.cc b/src/ChatPage.cc
index 5862e5593..9f20d54fb 100644
--- a/src/ChatPage.cc
+++ b/src/ChatPage.cc
@@ -138,6 +138,11 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
 			room_list_->updateUnreadMessageCount(roomid, count);
 	});
 
+	connect(view_manager_,
+		&TimelineViewManager::updateRoomsLastMessage,
+		room_list_,
+		&RoomList::updateRoomDescription);
+
 	connect(room_list_,
 		SIGNAL(totalUnreadMessageCountUpdated(int)),
 		this,
diff --git a/src/RoomInfoListItem.cc b/src/RoomInfoListItem.cc
index 379e40003..c9d7338f9 100644
--- a/src/RoomInfoListItem.cc
+++ b/src/RoomInfoListItem.cc
@@ -135,10 +135,34 @@ void RoomInfoListItem::paintEvent(QPaintEvent *event)
 		font.setPixelSize(conf::fontSize);
 		p.setFont(font);
 
-		auto description = metrics.elidedText(state_.getTopic(), Qt::ElideRight, width() * descPercentage - 2 * Padding - IconSize);
-		p.drawText(QPoint(2 * Padding + IconSize, bottom_y), description);
+		auto msgStampWidth = QFontMetrics(font).width(lastMsgInfo_.timestamp) + 5;
+
+		// The limit is the space between the end of the avatar and the start of the timestamp.
+		int usernameLimit = std::max(0, width() - 3 * Padding - msgStampWidth - IconSize - 20);
+		auto userName = metrics.elidedText(lastMsgInfo_.username, Qt::ElideRight, usernameLimit);
+
+		font.setBold(true);
+		p.setFont(font);
+		p.drawText(QPoint(2 * Padding + IconSize, bottom_y), userName);
+
+		int nameWidth = QFontMetrics(font).width(userName);
+
+		font.setBold(false);
+		p.setFont(font);
+
+		// The limit is the space between the end of the username and the start of the timestamp.
+		int descriptionLimit = std::max(0, width() - 3 * Padding - msgStampWidth - IconSize - nameWidth - 5);
+		auto description = metrics.elidedText(lastMsgInfo_.body, Qt::ElideRight, descriptionLimit);
+		p.drawText(QPoint(2 * Padding + IconSize + nameWidth, bottom_y), description);
+
+		// We either show the bubble or the last message timestamp.
+		if (unreadMsgCount_ == 0) {
+			font.setBold(true);
+			p.drawText(QPoint(width() - Padding - msgStampWidth, bottom_y), lastMsgInfo_.timestamp);
+		}
 	}
 
+	font.setBold(false);
 	p.setPen(Qt::NoPen);
 
 	// We using the first letter of room's name.
diff --git a/src/RoomList.cc b/src/RoomList.cc
index 55c71b199..042391a25 100644
--- a/src/RoomList.cc
+++ b/src/RoomList.cc
@@ -174,10 +174,19 @@ void RoomList::highlightSelectedRoom(const QString &room_id)
 void RoomList::updateRoomAvatar(const QString &roomid, const QPixmap &img)
 {
 	if (!rooms_.contains(roomid)) {
-		qDebug() << "Avatar update on non existent room" << roomid;
+		qWarning() << "Avatar update on non existent room" << roomid;
 		return;
 	}
 
-	auto list_item = rooms_.value(roomid);
-	list_item->setAvatar(img.toImage());
+	rooms_.value(roomid)->setAvatar(img.toImage());
+}
+
+void RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info)
+{
+	if (!rooms_.contains(roomid)) {
+		qWarning() << "Description update on non existent room" << roomid << info.body;
+		return;
+	}
+
+	rooms_.value(roomid)->setDescriptionMessage(info);
 }
diff --git a/src/TimelineItem.cc b/src/TimelineItem.cc
index 24b1c90f8..ee2377ef0 100644
--- a/src/TimelineItem.cc
+++ b/src/TimelineItem.cc
@@ -72,6 +72,7 @@ TimelineItem::TimelineItem(const QString &userid, const QString &color, QString
     : QWidget(parent)
 {
 	init();
+	descriptionMsg_ = {"You: ", body, descriptiveTime(QDateTime::currentDateTime())};
 
 	body.replace(URL_REGEX, URL_HTML);
 	auto displayName = TimelineViewManager::displayName(userid);
@@ -94,6 +95,7 @@ TimelineItem::TimelineItem(QString body, QWidget *parent)
     : QWidget(parent)
 {
 	init();
+	descriptionMsg_ = {"You: ", body, descriptiveTime(QDateTime::currentDateTime())};
 
 	body.replace(URL_REGEX, URL_HTML);
 
@@ -119,6 +121,10 @@ TimelineItem::TimelineItem(ImageItem *image,
 	auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
 	auto displayName = TimelineViewManager::displayName(event.sender());
 
+	descriptionMsg_ = {displayName,
+			   " sent an image",
+			   descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
+
 	generateTimestamp(timestamp);
 	generateBody(displayName, color, "");
 
@@ -141,6 +147,9 @@ TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Im
     : QWidget(parent)
 {
 	init();
+	descriptionMsg_ = {TimelineViewManager::displayName(event.sender()),
+			   " sent an image",
+			   descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
 
 	auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
 	generateTimestamp(timestamp);
@@ -162,6 +171,10 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event, bool
     : QWidget(parent)
 {
 	init();
+	descriptionMsg_ = {
+		TimelineViewManager::displayName(event.sender()),
+		" sent a notification",
+		descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
 
 	auto body = event.content().body().trimmed().toHtmlEscaped();
 	auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
@@ -199,6 +212,11 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event, bool w
 	auto body = event.content().body().trimmed().toHtmlEscaped();
 	auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
 
+	descriptionMsg_ = {
+		TimelineViewManager::displayName(event.sender()),
+		QString(": %1").arg(body),
+		descriptiveTime(QDateTime::fromMSecsSinceEpoch(event.timestamp()))};
+
 	generateTimestamp(timestamp);
 
 	body.replace(URL_REGEX, URL_HTML);
@@ -352,6 +370,23 @@ void TimelineItem::setUserAvatar(const QImage &avatar)
 	userAvatar_->setImage(avatar);
 }
 
+QString TimelineItem::descriptiveTime(const QDateTime &then)
+{
+	auto now = QDateTime::currentDateTime();
+
+	auto days = then.daysTo(now);
+
+	if (days == 0) {
+		return then.toString("HH:mm");
+	} else if (days < 2) {
+		return QString("Yesterday");
+	} else if (days < 365) {
+		return then.toString("dd/MM");
+	}
+
+	return then.toString("dd/MM/yy");
+}
+
 TimelineItem::~TimelineItem()
 {
 }
diff --git a/src/TimelineView.cc b/src/TimelineView.cc
index 5968f9cf5..be2fc0675 100644
--- a/src/TimelineView.cc
+++ b/src/TimelineView.cc
@@ -179,6 +179,10 @@ void TimelineView::addBackwardsEvents(const QString &room_id, const RoomMessages
 	prev_batch_token_ = msgs.end();
 	isPaginationInProgress_ = false;
 	isPaginationScrollPending_ = true;
+
+	// Exclude the top stretch.
+	if (!msgs.chunk().isEmpty() && scroll_layout_->count() > 1)
+		notifyForLastEvent();
 }
 
 TimelineItem *TimelineView::parseMessageEvent(const QJsonObject &event, TimelineDirection direction)
@@ -295,6 +299,10 @@ int TimelineView::addEvents(const Timeline &timeline)
 		client_->messages(room_id_, prev_batch_token_);
 	}
 
+	// Exclude the top stretch.
+	if (!timeline.events().isEmpty() && scroll_layout_->count() > 1)
+		notifyForLastEvent();
+
 	return message_count;
 }
 
@@ -441,3 +449,14 @@ void TimelineView::addUserTextMessage(const QString &body, int txn_id)
 
 	pending_msgs_.push_back(message);
 }
+
+void TimelineView::notifyForLastEvent()
+{
+	auto lastItem = scroll_layout_->itemAt(scroll_layout_->count() - 1);
+	auto *lastTimelineItem = qobject_cast<TimelineItem *>(lastItem->widget());
+
+	if (lastTimelineItem)
+		emit updateLastTimelineMessage(room_id_, lastTimelineItem->descriptionMessage());
+	else
+		qWarning() << "Cast to TimelineView failed" << room_id_;
+}
diff --git a/src/TimelineViewManager.cc b/src/TimelineViewManager.cc
index 3715d1b63..455335f73 100644
--- a/src/TimelineViewManager.cc
+++ b/src/TimelineViewManager.cc
@@ -80,6 +80,11 @@ void TimelineViewManager::initialize(const Rooms &rooms)
 		TimelineView *view = new TimelineView(it.value().timeline(), client_, it.key());
 		views_.insert(it.key(), QSharedPointer<TimelineView>(view));
 
+		connect(view,
+			&TimelineView::updateLastTimelineMessage,
+			this,
+			&TimelineViewManager::updateRoomsLastMessage);
+
 		// Add the view in the widget stack.
 		addWidget(view);
 	}
@@ -92,6 +97,11 @@ void TimelineViewManager::initialize(const QList<QString> &rooms)
 		TimelineView *view = new TimelineView(client_, roomid);
 		views_.insert(roomid, QSharedPointer<TimelineView>(view));
 
+		connect(view,
+			&TimelineView::updateLastTimelineMessage,
+			this,
+			&TimelineViewManager::updateRoomsLastMessage);
+
 		// Add the view in the widget stack.
 		addWidget(view);
 	}
-- 
GitLab