diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index f7dbf7ca..2f1a22b7 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -35,6 +35,7 @@
#include "TopRoomBar.h"
#include "TypingDisplay.h"
#include "UserInfoWidget.h"
+#include "UserMentionsWidget.h"
#include "UserSettingsPage.h"
#include "Utils.h"
#include "ui/OverlayModal.h"
@@ -43,6 +44,7 @@
#include "notifications/Manager.h"
#include "dialogs/ReadReceipts.h"
+#include "dialogs/UserMentions.h"
#include "timeline/TimelineViewManager.h"
// TODO: Needs to be updated with an actual secret.
@@ -89,10 +91,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
connect(sidebarActions_, &SideBarActions::createRoom, this, &ChatPage::createRoom);
user_info_widget_ = new UserInfoWidget(sideBar_);
+ user_mentions_widget_ = new UserMentionsWidget(sideBar_);
room_list_ = new RoomList(sideBar_);
connect(room_list_, &RoomList::joinRoom, this, &ChatPage::joinRoom);
sideBarLayout_->addWidget(user_info_widget_);
+ sideBarLayout_->addWidget(user_mentions_widget_);
sideBarLayout_->addWidget(room_list_);
sideBarLayout_->addWidget(sidebarActions_);
@@ -150,6 +154,25 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
trySync();
});
+ connect(user_mentions_widget_, &UserMentionsWidget::clicked, this, [this]() {
+ http::client()->notifications(
+ 1000,
+ "",
+ "highlight",
+ [this](const mtx::responses::Notifications &res,
+ mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn(
+ "failed to retrieve notifications: {} ({})",
+ err->matrix_error.error,
+ static_cast<int>(err->status_code));
+ return;
+ }
+
+ emit highlightedNotifsRetrieved(std::move(res));
+ });
+ });
+
connectivityTimer_.setInterval(CHECK_CONNECTIVITY_INTERVAL);
connect(&connectivityTimer_, &QTimer::timeout, this, [=]() {
if (http::client()->access_token().empty()) {
@@ -497,6 +520,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendDesktopNotifications);
+ connect(this, &ChatPage::highlightedNotifsRetrieved, this, &ChatPage::showNotificationsDialog);
connect(communitiesList_,
&CommunitiesList::communityChanged,
@@ -562,6 +586,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
if (hasNotifications && userSettings_->hasDesktopNotifications())
http::client()->notifications(
5,
+ "",
+ "",
[this](const mtx::responses::Notifications &res,
mtx::http::RequestErr err) {
if (err) {
@@ -961,6 +987,32 @@ ChatPage::sendDesktopNotifications(const mtx::responses::Notifications &res)
}
void
+ChatPage::showNotificationsDialog(const mtx::responses::Notifications &res)
+{
+ // TODO: This should NOT BE A DIALOG. Make the TimelineView support
+ // creating a timeline view from notifications (similarly to how it can show history views)
+ auto notifDialog = new dialogs::UserMentions();
+ for (const auto &item : res.notifications) {
+ const auto event_id = QString::fromStdString(utils::event_id(item.event));
+
+ try {
+ const auto room_id = QString::fromStdString(item.room_id);
+ const auto user_id = utils::event_sender(item.event);
+ const auto body = utils::event_body(item.event);
+
+ notifDialog->pushItem(event_id, user_id, body, room_id);
+
+ } catch (const lmdb::error &e) {
+ nhlog::db()->warn("error while sending desktop notification: {}", e.what());
+ }
+ }
+ notifDialog->setFixedWidth(width());
+ notifDialog->setFixedHeight(height());
+ notifDialog->raise();
+ notifDialog->show();
+}
+
+void
ChatPage::tryInitialSync()
{
nhlog::crypto()->info("ed25519 : {}", olm::client()->identity_keys().ed25519);
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 6e6f5aed..bb06e0eb 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -43,6 +43,7 @@ class TimelineViewManager;
class TopRoomBar;
class TypingDisplay;
class UserInfoWidget;
+class UserMentionsWidget;
class UserSettings;
class NotificationsManager;
@@ -87,6 +88,8 @@ signals:
void messageReply(const RelatedInfo &related);
void notificationsRetrieved(const mtx::responses::Notifications &);
+ void highlightedNotifsRetrieved(const mtx::responses::Notifications &);
+
void uploadFailed(const QString &msg);
void imageUploaded(const QString &roomid,
@@ -204,6 +207,9 @@ private:
//! Send desktop notification for the received messages.
void sendDesktopNotifications(const mtx::responses::Notifications &);
+ void showNotificationsDialog(const mtx::responses::Notifications &);
+
+
QStringList generateTypingUsers(const QString &room_id,
const std::vector<std::string> &typing_users);
@@ -236,6 +242,8 @@ private:
UserInfoWidget *user_info_widget_;
+ UserMentionsWidget *user_mentions_widget_;
+
// Keeps track of the users currently typing on each room.
std::map<QString, QList<QString>> typingUsers_;
QTimer *typingRefresher_;
diff --git a/src/UserMentionsWidget.cpp b/src/UserMentionsWidget.cpp
new file mode 100644
index 00000000..b7b24ad2
--- /dev/null
+++ b/src/UserMentionsWidget.cpp
@@ -0,0 +1,308 @@
+#include <QDateTime>
+#include <QDebug>
+#include <QMouseEvent>
+#include <QPainter>
+#include <QtGlobal>
+
+#include "MainWindow.h"
+#include "UserMentionsWidget.h"
+#include "Utils.h"
+#include "ui/Ripple.h"
+#include "ui/RippleOverlay.h"
+
+constexpr int MaxUnreadCountDisplayed = 99;
+
+struct WMetrics
+{
+ int maxHeight;
+ int iconSize;
+ int padding;
+ int unit;
+
+ int unreadLineWidth;
+ int unreadLineOffset;
+
+ int inviteBtnX;
+ int inviteBtnY;
+};
+
+WMetrics
+getWMetrics(const QFont &font)
+{
+ WMetrics m;
+
+ const int height = QFontMetrics(font).lineSpacing();
+
+ m.unit = height;
+ m.maxHeight = std::ceil((double)height * 3.8);
+ m.iconSize = std::ceil((double)height * 2.8);
+ m.padding = std::ceil((double)height / 2.0);
+ m.unreadLineWidth = m.padding - m.padding / 3;
+ m.unreadLineOffset = m.padding - m.padding / 4;
+
+ m.inviteBtnX = m.iconSize + 2 * m.padding;
+ m.inviteBtnX = m.iconSize / 2.0 + m.padding + m.padding / 3.0;
+
+ return m;
+}
+
+UserMentionsWidget::UserMentionsWidget(QWidget *parent)
+ : QWidget(parent)
+ , isPressed_(false)
+ , unreadMsgCount_(0)
+{
+ init(parent);
+
+ QFont f;
+ f.setPointSizeF(f.pointSizeF());
+
+ const int fontHeight = QFontMetrics(f).height();
+ const int widgetMargin = fontHeight / 3;
+ const int contentHeight = fontHeight * 3;
+
+ setFixedHeight(contentHeight + widgetMargin);
+
+ topLayout_ = new QHBoxLayout(this);
+ topLayout_->setSpacing(0);
+ topLayout_->setMargin(widgetMargin);
+}
+
+void
+UserMentionsWidget::init(QWidget *parent)
+{
+ setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
+ setMouseTracking(true);
+ setAttribute(Qt::WA_Hover);
+
+ setFixedHeight(getWMetrics(QFont{}).maxHeight);
+
+ QPainterPath path;
+ path.addRect(0, 0, parent->width(), height());
+
+ ripple_overlay_ = new RippleOverlay(this);
+ ripple_overlay_->setClipPath(path);
+ ripple_overlay_->setClipping(true);
+
+ unreadCountFont_.setPointSizeF(unreadCountFont_.pointSizeF() * 0.8);
+ unreadCountFont_.setBold(true);
+
+ bubbleDiameter_ = QFontMetrics(unreadCountFont_).averageCharWidth() * 3;
+}
+
+// void
+// UserMentionsWidget::resizeEvent(QResizeEvent *event)
+// {
+// Q_UNUSED(event);
+
+// const auto sz = utils::calculateSidebarSizes(QFont{});
+
+// if (width() <= sz.small) {
+// topLayout_->setContentsMargins(0, 0, logoutButtonSize_, 0);
+
+// } else {
+// topLayout_->setMargin(5);
+// }
+
+// QWidget::resizeEvent(event);
+// }
+
+void
+UserMentionsWidget::setPressedState(bool state)
+{
+ if (isPressed_ != state) {
+ isPressed_ = state;
+ update();
+ }
+}
+
+void
+UserMentionsWidget::resizeEvent(QResizeEvent *)
+{
+ // Update ripple's clipping path.
+ QPainterPath path;
+ path.addRect(0, 0, width(), height());
+
+ const auto sidebarSizes = utils::calculateSidebarSizes(QFont{});
+
+ if (width() > sidebarSizes.small)
+ setToolTip("");
+ else
+ setToolTip("");
+
+ ripple_overlay_->setClipPath(path);
+ ripple_overlay_->setClipping(true);
+}
+
+void
+UserMentionsWidget::mousePressEvent(QMouseEvent *event)
+{
+ if (event->buttons() == Qt::RightButton) {
+ QWidget::mousePressEvent(event);
+ return;
+ }
+
+ emit clicked();
+
+ setPressedState(true);
+
+ // Ripple on mouse position by default.
+ QPoint pos = event->pos();
+ qreal radiusEndValue = static_cast<qreal>(width()) / 3;
+
+ Ripple *ripple = new Ripple(pos);
+
+ ripple->setRadiusEndValue(radiusEndValue);
+ ripple->setOpacityStartValue(0.15);
+ ripple->setColor(QColor("white"));
+ ripple->radiusAnimation()->setDuration(200);
+ ripple->opacityAnimation()->setDuration(400);
+
+ ripple_overlay_->addRipple(ripple);
+}
+
+void
+UserMentionsWidget::paintEvent(QPaintEvent *event)
+{
+ Q_UNUSED(event);
+
+ QPainter p(this);
+ p.setRenderHint(QPainter::TextAntialiasing);
+ p.setRenderHint(QPainter::SmoothPixmapTransform);
+ p.setRenderHint(QPainter::Antialiasing);
+
+ auto wm = getWMetrics(QFont{});
+
+ QPen titlePen(titleColor_);
+ QPen subtitlePen(subtitleColor_);
+
+ QFontMetrics metrics(QFont{});
+
+ if (isPressed_) {
+ p.fillRect(rect(), highlightedBackgroundColor_);
+ titlePen.setColor(highlightedTitleColor_);
+ subtitlePen.setColor(highlightedSubtitleColor_);
+ } else if (underMouse()) {
+ p.fillRect(rect(), hoverBackgroundColor_);
+ titlePen.setColor(hoverTitleColor_);
+ subtitlePen.setColor(hoverSubtitleColor_);
+ } else {
+ p.fillRect(rect(), backgroundColor_);
+ titlePen.setColor(titleColor_);
+ subtitlePen.setColor(subtitleColor_);
+ }
+
+ // Description line with the default font.
+ int bottom_y = wm.maxHeight - wm.padding - metrics.ascent() / 2;
+
+ const auto sidebarSizes = utils::calculateSidebarSizes(QFont{});
+
+ if (width() > sidebarSizes.small) {
+ QFont headingFont;
+ headingFont.setWeight(QFont::Medium);
+ p.setFont(headingFont);
+ p.setPen(titlePen);
+
+ QFont tsFont;
+ tsFont.setPointSizeF(tsFont.pointSizeF() * 0.9);
+#if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
+ const int msgStampWidth = QFontMetrics(tsFont).width("timestamp") + 4;
+#else
+ const int msgStampWidth = QFontMetrics(tsFont).horizontalAdvance("timestamp") + 4;
+#endif
+ // We use the full width of the widget if there is no unread msg bubble.
+ //const int bottomLineWidthLimit = (unreadMsgCount_ > 0) ? msgStampWidth : 0;
+
+ // Name line.
+ QFontMetrics fontNameMetrics(headingFont);
+ int top_y = 2 * wm.padding + fontNameMetrics.ascent() / 2;
+
+ const auto name = metrics.elidedText(
+ "Mentions",
+ Qt::ElideRight,
+ (width() - wm.iconSize - 2 * wm.padding - msgStampWidth) * 0.8);
+ p.drawText(QPoint(2 * wm.padding + wm.iconSize, top_y), name);
+
+ p.setFont(QFont{});
+ p.setPen(subtitlePen);
+
+ // The limit is the space between the end of the avatar and the start of the
+ // timestamp.
+ int usernameLimit =
+ std::max(0, width() - 3 * wm.padding - msgStampWidth - wm.iconSize - 20);
+ auto userName = metrics.elidedText("Show Mentioned Messages", Qt::ElideRight, usernameLimit);
+
+ p.setFont(QFont{});
+ p.drawText(QPoint(2 * wm.padding + wm.iconSize, bottom_y), userName);
+
+ // We show the last message timestamp.
+ p.save();
+ if (isPressed_) {
+ p.setPen(QPen(highlightedTimestampColor_));
+ } else if (underMouse()) {
+ p.setPen(QPen(hoverTimestampColor_));
+ } else {
+ p.setPen(QPen(timestampColor_));
+ }
+
+ // p.setFont(tsFont);
+ // p.drawText(QPoint(width() - wm.padding - msgStampWidth, top_y), "timestamp");
+ p.restore();
+ }
+
+ p.setPen(Qt::NoPen);
+
+ if (unreadMsgCount_ > 0) {
+ QBrush brush;
+ brush.setStyle(Qt::SolidPattern);
+
+ brush.setColor(mentionedColor());
+
+ if (isPressed_)
+ brush.setColor(bubbleFgColor());
+
+ p.setBrush(brush);
+ p.setPen(Qt::NoPen);
+ p.setFont(unreadCountFont_);
+
+ // Extra space on the x-axis to accomodate the extra character space
+ // inside the bubble.
+ const int x_width = unreadMsgCount_ > MaxUnreadCountDisplayed
+ ? QFontMetrics(p.font()).averageCharWidth()
+ : 0;
+
+ QRectF r(width() - bubbleDiameter_ - wm.padding - x_width,
+ bottom_y - bubbleDiameter_ / 2 - 5,
+ bubbleDiameter_ + x_width,
+ bubbleDiameter_);
+
+ if (width() == sidebarSizes.small)
+ r = QRectF(width() - bubbleDiameter_ - 5,
+ height() - bubbleDiameter_ - 5,
+ bubbleDiameter_ + x_width,
+ bubbleDiameter_);
+
+ p.setPen(Qt::NoPen);
+ p.drawEllipse(r);
+
+ p.setPen(QPen(bubbleFgColor()));
+
+ if (isPressed_)
+ p.setPen(QPen(bubbleBgColor()));
+
+ auto countTxt = unreadMsgCount_ > MaxUnreadCountDisplayed
+ ? QString("99+")
+ : QString::number(unreadMsgCount_);
+
+ p.setBrush(Qt::NoBrush);
+ p.drawText(r.translated(0, -0.5), Qt::AlignCenter, countTxt);
+ }
+
+ if (!isPressed_ && hasUnreadMessages_) {
+ QPen pen;
+ pen.setWidth(wm.unreadLineWidth);
+ pen.setColor(highlightedBackgroundColor_);
+
+ p.setPen(pen);
+ p.drawLine(0, wm.unreadLineOffset, 0, height() - wm.unreadLineOffset);
+ }
+}
\ No newline at end of file
diff --git a/src/UserMentionsWidget.h b/src/UserMentionsWidget.h
new file mode 100644
index 00000000..179f0026
--- /dev/null
+++ b/src/UserMentionsWidget.h
@@ -0,0 +1,164 @@
+#pragma once
+
+#include <QColor>
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QLayout>
+#include <QWidget>
+
+class FlatButton;
+class RippleOverlay;
+
+class UserMentionsWidget : public QWidget
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QColor borderColor READ borderColor WRITE setBorderColor)
+
+ Q_PROPERTY(QColor highlightedBackgroundColor READ highlightedBackgroundColor WRITE
+ setHighlightedBackgroundColor)
+ Q_PROPERTY(
+ QColor hoverBackgroundColor READ hoverBackgroundColor WRITE setHoverBackgroundColor)
+ Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setBackgroundColor)
+
+ Q_PROPERTY(QColor avatarBgColor READ avatarBgColor WRITE setAvatarBgColor)
+ Q_PROPERTY(QColor avatarFgColor READ avatarFgColor WRITE setAvatarFgColor)
+
+ Q_PROPERTY(QColor bubbleBgColor READ bubbleBgColor WRITE setBubbleBgColor)
+ Q_PROPERTY(QColor bubbleFgColor READ bubbleFgColor WRITE setBubbleFgColor)
+
+ Q_PROPERTY(QColor titleColor READ titleColor WRITE setTitleColor)
+ Q_PROPERTY(QColor subtitleColor READ subtitleColor WRITE setSubtitleColor)
+
+ Q_PROPERTY(QColor timestampColor READ timestampColor WRITE setTimestampColor)
+ Q_PROPERTY(QColor highlightedTimestampColor READ highlightedTimestampColor WRITE
+ setHighlightedTimestampColor)
+ Q_PROPERTY(QColor hoverTimestampColor READ hoverTimestampColor WRITE setHoverTimestampColor)
+
+ Q_PROPERTY(
+ QColor highlightedTitleColor READ highlightedTitleColor WRITE setHighlightedTitleColor)
+ Q_PROPERTY(QColor highlightedSubtitleColor READ highlightedSubtitleColor WRITE
+ setHighlightedSubtitleColor)
+
+ Q_PROPERTY(QColor hoverTitleColor READ hoverTitleColor WRITE setHoverTitleColor)
+ Q_PROPERTY(QColor hoverSubtitleColor READ hoverSubtitleColor WRITE setHoverSubtitleColor)
+
+ Q_PROPERTY(QColor mentionedColor READ mentionedColor WRITE setMentionedColor)
+ Q_PROPERTY(QColor btnColor READ btnColor WRITE setBtnColor)
+ Q_PROPERTY(QColor btnTextColor READ btnTextColor WRITE setBtnTextColor)
+
+public:
+ UserMentionsWidget(QWidget *parent = 0);
+
+ void updateUnreadMessageCount(int count);
+ void clearUnreadMessageCount() { updateUnreadMessageCount(0); };
+ bool isPressed() const { return isPressed_; }
+ int unreadMessageCount() const { return unreadMsgCount_; }
+ QColor borderColor() const { return borderColor_; }
+ void setBorderColor(QColor &color) { borderColor_ = color; }
+ QColor highlightedBackgroundColor() const { return highlightedBackgroundColor_; }
+ QColor hoverBackgroundColor() const { return hoverBackgroundColor_; }
+ QColor hoverTitleColor() const { return hoverTitleColor_; }
+ QColor hoverSubtitleColor() const { return hoverSubtitleColor_; }
+ QColor hoverTimestampColor() const { return hoverTimestampColor_; }
+ QColor backgroundColor() const { return backgroundColor_; }
+ QColor avatarBgColor() const { return avatarBgColor_; }
+ QColor avatarFgColor() const { return avatarFgColor_; }
+
+ QColor highlightedTitleColor() const { return highlightedTitleColor_; }
+ QColor highlightedSubtitleColor() const { return highlightedSubtitleColor_; }
+ QColor highlightedTimestampColor() const { return highlightedTimestampColor_; }
+
+ QColor titleColor() const { return titleColor_; }
+ QColor subtitleColor() const { return subtitleColor_; }
+ QColor timestampColor() const { return timestampColor_; }
+ QColor btnColor() const { return btnColor_; }
+ QColor btnTextColor() const { return btnTextColor_; }
+
+ QColor bubbleFgColor() const { return bubbleFgColor_; }
+ QColor bubbleBgColor() const { return bubbleBgColor_; }
+ QColor mentionedColor() const { return mentionedFontColor_; }
+
+ void setHighlightedBackgroundColor(QColor &color) { highlightedBackgroundColor_ = color; }
+ void setHoverBackgroundColor(QColor &color) { hoverBackgroundColor_ = color; }
+ void setHoverSubtitleColor(QColor &color) { hoverSubtitleColor_ = color; }
+ void setHoverTitleColor(QColor &color) { hoverTitleColor_ = color; }
+ void setHoverTimestampColor(QColor &color) { hoverTimestampColor_ = color; }
+ void setBackgroundColor(QColor &color) { backgroundColor_ = color; }
+ void setTimestampColor(QColor &color) { timestampColor_ = color; }
+ void setAvatarFgColor(QColor &color) { avatarFgColor_ = color; }
+ void setAvatarBgColor(QColor &color) { avatarBgColor_ = color; }
+
+ void setHighlightedTitleColor(QColor &color) { highlightedTitleColor_ = color; }
+ void setHighlightedSubtitleColor(QColor &color) { highlightedSubtitleColor_ = color; }
+ void setHighlightedTimestampColor(QColor &color) { highlightedTimestampColor_ = color; }
+
+ void setTitleColor(QColor &color) { titleColor_ = color; }
+ void setSubtitleColor(QColor &color) { subtitleColor_ = color; }
+
+ void setBtnColor(QColor &color) { btnColor_ = color; }
+ void setBtnTextColor(QColor &color) { btnTextColor_ = color; }
+
+ void setBubbleFgColor(QColor &color) { bubbleFgColor_ = color; }
+ void setBubbleBgColor(QColor &color) { bubbleBgColor_ = color; }
+ void setMentionedColor(QColor &color) { mentionedFontColor_ = color; }
+
+signals:
+ void clicked();
+
+public slots:
+ void setPressedState(bool state);
+
+protected:
+ void mousePressEvent(QMouseEvent *event) override;
+ void paintEvent(QPaintEvent *event) override;
+ void resizeEvent(QResizeEvent *event) override;
+
+private:
+ void init(QWidget *parent);
+
+ RippleOverlay *ripple_overlay_;
+
+ bool isPressed_ = false;
+
+ bool hasUnreadMessages_ = true;
+
+ int unreadMsgCount_ = 0;
+
+ QHBoxLayout *topLayout_;
+
+ QColor borderColor_;
+ QColor highlightedBackgroundColor_;
+ QColor hoverBackgroundColor_;
+ QColor backgroundColor_;
+
+ QColor highlightedTitleColor_;
+ QColor highlightedSubtitleColor_;
+
+ QColor titleColor_;
+ QColor subtitleColor_;
+
+ QColor hoverTitleColor_;
+ QColor hoverSubtitleColor_;
+
+ QColor btnColor_;
+ QColor btnTextColor_;
+
+ QRectF acceptBtnRegion_;
+ QRectF declineBtnRegion_;
+
+ // Fonts
+ QColor mentionedFontColor_;
+ QFont unreadCountFont_;
+ int bubbleDiameter_;
+
+ QColor timestampColor_;
+ QColor highlightedTimestampColor_;
+ QColor hoverTimestampColor_;
+
+ QColor avatarBgColor_;
+ QColor avatarFgColor_;
+
+ QColor bubbleBgColor_;
+ QColor bubbleFgColor_;
+};
\ No newline at end of file
diff --git a/src/dialogs/UserMentions.cpp b/src/dialogs/UserMentions.cpp
new file mode 100644
index 00000000..1a6c17e5
--- /dev/null
+++ b/src/dialogs/UserMentions.cpp
@@ -0,0 +1,59 @@
+#include <QTimer>
+
+#include "UserMentions.h"
+#include "timeline/TimelineItem.h"
+
+using namespace dialogs;
+
+UserMentions::UserMentions(QWidget *parent)
+ : QWidget{parent}
+{
+ top_layout_ = new QVBoxLayout(this);
+ top_layout_->setSpacing(0);
+ top_layout_->setMargin(0);
+
+ scroll_area_ = new QScrollArea(this);
+ scroll_area_->setWidgetResizable(true);
+ scroll_area_->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
+
+ scroll_widget_ = new QWidget(this);
+ scroll_widget_->setObjectName("scroll_widget");
+
+ // Height of the typing display.
+ QFont f;
+ f.setPointSizeF(f.pointSizeF() * 0.9);
+ const int bottomMargin = QFontMetrics(f).height() + 6;
+
+ scroll_layout_ = new QVBoxLayout(scroll_widget_);
+ scroll_layout_->setContentsMargins(4, 0, 15, bottomMargin);
+ scroll_layout_->setSpacing(0);
+ scroll_layout_->setObjectName("timelinescrollarea");
+
+ scroll_area_->setWidget(scroll_widget_);
+ scroll_area_->setAlignment(Qt::AlignBottom);
+
+ top_layout_->addWidget(scroll_area_);
+
+ setLayout(top_layout_);
+}
+
+void
+UserMentions::pushItem(const QString &event_id, const QString &user_id, const QString &body, const QString &room_id) {
+ TimelineItem *view_item =
+ new TimelineItem(mtx::events::MessageType::Text,
+ user_id,
+ body,
+ true,
+ room_id,
+ scroll_widget_);
+ view_item->setEventId(event_id);
+ setUpdatesEnabled(false);
+ view_item->hide();
+
+ scroll_layout_->addWidget(view_item);
+ QTimer::singleShot(0, this, [view_item, this]() {
+ view_item->show();
+ view_item->adjustSize();
+ setUpdatesEnabled(true);
+ });
+}
\ No newline at end of file
diff --git a/src/dialogs/UserMentions.h b/src/dialogs/UserMentions.h
new file mode 100644
index 00000000..ff68d8c8
--- /dev/null
+++ b/src/dialogs/UserMentions.h
@@ -0,0 +1,26 @@
+#pragma once
+
+#include <QWidget>
+#include <QVBoxLayout>
+#include <QScrollArea>
+#include <QScrollBar>
+
+namespace dialogs {
+
+class UserMentions : public QWidget
+{
+ Q_OBJECT
+public:
+ UserMentions(QWidget *parent = nullptr);
+ void pushItem(const QString &event_id, const QString &user_id, const QString &body, const QString &room_id);
+private:
+ QVBoxLayout *top_layout_;
+ QVBoxLayout *scroll_layout_;
+
+ QScrollArea *scroll_area_;
+ QWidget *scroll_widget_;
+
+
+};
+
+}
\ No newline at end of file
|