diff --git a/CMakeLists.txt b/CMakeLists.txt index 65ee0da7..495f80ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -170,6 +170,7 @@ set(SRC_FILES src/ui/Avatar.cc src/ui/Badge.cc src/ui/LoadingIndicator.cc + src/ui/InfoMessage.cpp src/ui/FlatButton.cc src/ui/FloatingButton.cc src/ui/Label.cc @@ -283,6 +284,7 @@ qt5_wrap_cpp(MOC_HEADERS include/ui/Avatar.h include/ui/Badge.h include/ui/LoadingIndicator.h + include/ui/InfoMessage.hpp include/ui/FlatButton.h include/ui/Label.h include/ui/FloatingButton.h diff --git a/include/timeline/TimelineView.h b/include/timeline/TimelineView.h index 81212c27..5af359c5 100644 --- a/include/timeline/TimelineView.h +++ b/include/timeline/TimelineView.h @@ -107,40 +107,6 @@ enum class TimelineDirection Bottom, }; -class DateSeparator : public QWidget -{ - Q_OBJECT - - Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) - Q_PROPERTY(QColor boxColor WRITE setBoxColor READ boxColor) - -public: - DateSeparator(QDateTime datetime, QWidget *parent = nullptr); - - void setTextColor(QColor color) { textColor_ = color; } - void setBoxColor(QColor color) { boxColor_ = color; } - - QColor textColor() const { return textColor_; } - QColor boxColor() const { return boxColor_; } - -protected: - void paintEvent(QPaintEvent *event) override; - -private: - static constexpr int VPadding = 6; - static constexpr int HPadding = 12; - static constexpr int HMargin = 20; - - int width_; - int height_; - - QString msg_; - QFont font_; - - QColor textColor_ = QColor("black"); - QColor boxColor_ = QColor("white"); -}; - class TimelineView : public QWidget { Q_OBJECT @@ -162,7 +128,6 @@ public: uint64_t size); void updatePendingMessage(const std::string &txn_id, const QString &event_id); void scrollDown(); - QLabel *createDateSeparator(QDateTime datetime); //! Remove an item from the timeline with the given Event ID. void removeEvent(const QString &event_id); @@ -220,7 +185,7 @@ private: void getMessages(); //! HACK: Fixing layout flickering when adding to the bottom //! of the timeline. - void pushTimelineItem(TimelineItem *item) + void pushTimelineItem(QWidget *item) { item->hide(); scroll_layout_->addWidget(item); @@ -230,7 +195,7 @@ private: //! Decides whether or not to show or hide the scroll down button. void toggleScrollDownButton(); void init(); - void addTimelineItem(TimelineItem *item, + void addTimelineItem(QWidget *item, TimelineDirection direction = TimelineDirection::Bottom); void updateLastSender(const QString &user_id, TimelineDirection direction); void notifyForLastEvent(); @@ -295,8 +260,8 @@ private: const QDateTime &second = QDateTime::currentDateTime()) const; // Return nullptr if the event couldn't be parsed. - TimelineItem *parseMessageEvent(const mtx::events::collections::TimelineEvents &event, - TimelineDirection direction); + QWidget *parseMessageEvent(const mtx::events::collections::TimelineEvents &event, + TimelineDirection direction); QVBoxLayout *top_layout_; QVBoxLayout *scroll_layout_; diff --git a/include/ui/InfoMessage.hpp b/include/ui/InfoMessage.hpp new file mode 100644 index 00000000..58f98b0c --- /dev/null +++ b/include/ui/InfoMessage.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +class InfoMessage : public QWidget +{ + Q_OBJECT + + Q_PROPERTY(QColor textColor WRITE setTextColor READ textColor) + Q_PROPERTY(QColor boxColor WRITE setBoxColor READ boxColor) + +public: + explicit InfoMessage(QWidget *parent = nullptr); + InfoMessage(QString msg, QWidget *parent = nullptr); + + void setTextColor(QColor color) { textColor_ = color; } + void setBoxColor(QColor color) { boxColor_ = color; } + void saveDatetime(QDateTime datetime) { datetime_ = datetime; } + + QColor textColor() const { return textColor_; } + QColor boxColor() const { return boxColor_; } + QDateTime datetime() const { return datetime_; } + +protected: + void paintEvent(QPaintEvent *event) override; + + int width_; + int height_; + + QString msg_; + QFont font_; + + QDateTime datetime_; + + QColor textColor_ = QColor("black"); + QColor boxColor_ = QColor("white"); +}; + +class DateSeparator : public InfoMessage +{ + Q_OBJECT + +public: + DateSeparator(QDateTime datetime, QWidget *parent = nullptr); +}; diff --git a/resources/styles/nheko-dark.qss b/resources/styles/nheko-dark.qss index 2e456d90..c95eca2f 100644 --- a/resources/styles/nheko-dark.qss +++ b/resources/styles/nheko-dark.qss @@ -23,7 +23,7 @@ QuickSwitcher { background-color: #202228; } -DateSeparator { +InfoMessage { qproperty-textColor: #caccd1; qproperty-boxColor: rgba(45, 49, 57, 120); } diff --git a/resources/styles/nheko.qss b/resources/styles/nheko.qss index 1cba4c82..674d1722 100644 --- a/resources/styles/nheko.qss +++ b/resources/styles/nheko.qss @@ -23,7 +23,7 @@ QuickSwitcher { background-color: white; } -DateSeparator { +InfoMessage { qproperty-textColor: #333; qproperty-boxColor: rgba(220, 220, 220, 120); } diff --git a/resources/styles/system.qss b/resources/styles/system.qss index 0921a832..e18b7e5c 100644 --- a/resources/styles/system.qss +++ b/resources/styles/system.qss @@ -25,7 +25,7 @@ QuickSwitcher { background-color: palette(window); } -DateSeparator { +InfoMessage { qproperty-textColor: palette(text); qproperty-boxColor: palette(window); } diff --git a/src/timeline/TimelineView.cc b/src/timeline/TimelineView.cc index e9c5b017..35140df0 100644 --- a/src/timeline/TimelineView.cc +++ b/src/timeline/TimelineView.cc @@ -23,6 +23,7 @@ #include "ChatPage.h" #include "Config.h" #include "FloatingButton.h" +#include "InfoMessage.hpp" #include "Logging.hpp" #include "Olm.hpp" #include "UserSettingsPage.h" @@ -36,55 +37,19 @@ using TimelineEvent = mtx::events::collections::TimelineEvents; -DateSeparator::DateSeparator(QDateTime datetime, QWidget *parent) - : QWidget{parent} +//! Retrieve the timestamp of the event represented by the given widget. +QDateTime +getDate(QWidget *widget) { - auto now = QDateTime::currentDateTime(); - auto days = now.daysTo(datetime); + auto item = qobject_cast(widget); + if (item) + return item->descriptionMessage().datetime; - font_.setWeight(60); - font_.setPixelSize(conf::timeline::fonts::dateSeparator); + auto infoMsg = qobject_cast(widget); + if (infoMsg) + return infoMsg->datetime(); - QString fmt; - - if (now.date().year() != datetime.date().year()) - fmt = QString("ddd d MMMM yy"); - else - fmt = QString("ddd d MMMM"); - - if (days == 0) - msg_ = tr("Today"); - else if (std::abs(days) == 1) - msg_ = tr("Yesterday"); - else - msg_ = datetime.toString(fmt); - - QFontMetrics fm{font_}; - width_ = fm.width(msg_) + HPadding * 2; - height_ = fm.ascent() + 2 * VPadding; - - setFixedHeight(height_ + 2 * HMargin); -} - -void -DateSeparator::paintEvent(QPaintEvent *) -{ - QPainter p(this); - p.setRenderHint(QPainter::Antialiasing); - p.setFont(font_); - - // Center the box horizontally & vertically. - auto textRegion = QRectF(width() / 2 - width_ / 2, HMargin, width_, height_); - - QPainterPath ppath; - ppath.addRoundedRect(textRegion, height_ / 2, height_ / 2); - - p.setPen(Qt::NoPen); - p.fillPath(ppath, boxColor()); - p.drawPath(ppath); - - p.setPen(QPen(textColor())); - p.drawText(textRegion, Qt::AlignCenter, msg_); + return QDateTime(); } TimelineView::TimelineView(const mtx::responses::Timeline &timeline, @@ -231,7 +196,7 @@ TimelineView::addBackwardsEvents(const mtx::responses::Messages &msgs) isPaginationInProgress_ = false; } -TimelineItem * +QWidget * TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents &event, TimelineDirection direction) { @@ -255,6 +220,12 @@ TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents & }); return nullptr; + } else if (mpark::holds_alternative>(event)) { + auto msg = mpark::get>(event); + auto item = new InfoMessage(tr("Encryption is enabled"), this); + item->saveDatetime(QDateTime::fromMSecsSinceEpoch(msg.origin_server_ts)); + + return item; } else if (mpark::holds_alternative>(event)) { auto audio = mpark::get>(event); return processMessageEvent(audio, direction); @@ -280,13 +251,18 @@ TimelineView::parseMessageEvent(const mtx::events::collections::TimelineEvents & return processMessageEvent(mpark::get(event), direction); } else if (mpark::holds_alternative>(event)) { - auto res = parseEncryptedEvent(mpark::get>(event)); - auto item = parseMessageEvent(res.event, direction); + auto res = parseEncryptedEvent(mpark::get>(event)); + auto widget = parseMessageEvent(res.event, direction); - if (item != nullptr && res.isDecrypted) + if (widget == nullptr) + return nullptr; + + auto item = qobject_cast(widget); + + if (item && res.isDecrypted) item->markReceived(true); - return item; + return widget; } return nullptr; @@ -374,7 +350,7 @@ TimelineView::renderBottomEvents(const std::vector &events) int counter = 0; for (const auto &event : events) { - TimelineItem *item = parseMessageEvent(event, TimelineDirection::Bottom); + QWidget *item = parseMessageEvent(event, TimelineDirection::Bottom); if (item != nullptr) { addTimelineItem(item, TimelineDirection::Bottom); @@ -395,7 +371,7 @@ TimelineView::renderBottomEvents(const std::vector &events) void TimelineView::renderTopEvents(const std::vector &events) { - std::vector items; + std::vector items; // Reset the sender of the first message in the timeline // cause we're about to insert a new one. @@ -408,7 +384,7 @@ TimelineView::renderTopEvents(const std::vector &events) while (ii != 0) { --ii; - TimelineItem *item = parseMessageEvent(events[ii], TimelineDirection::Top); + auto item = parseMessageEvent(events[ii], TimelineDirection::Top); if (item != nullptr) items.push_back(item); @@ -429,9 +405,16 @@ TimelineView::renderTopEvents(const std::vector &events) // If this batch is the first being rendered (i.e the first and the last // events originate from this batch), set the last sender. - if (lastSender_.isEmpty() && !items.empty()) - saveLastMessageInfo(items.at(0)->descriptionMessage().userid, - items.at(0)->descriptionMessage().datetime); + if (lastSender_.isEmpty() && !items.empty()) { + for (const auto &w : items) { + auto timelineItem = qobject_cast(w); + if (timelineItem) { + saveLastMessageInfo(timelineItem->descriptionMessage().userid, + timelineItem->descriptionMessage().datetime); + break; + } + } + } } void @@ -569,17 +552,16 @@ TimelineView::isSenderRendered(const QString &user_id, } void -TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction) +TimelineView::addTimelineItem(QWidget *item, TimelineDirection direction) { - const auto newDate = item->descriptionMessage().datetime; + const auto newDate = getDate(item); if (direction == TimelineDirection::Bottom) { const auto lastItemPosition = scroll_layout_->count() - 1; - auto lastItem = - qobject_cast(scroll_layout_->itemAt(lastItemPosition)->widget()); + const auto lastItem = scroll_layout_->itemAt(lastItemPosition)->widget(); if (lastItem) { - auto oldDate = lastItem->descriptionMessage().datetime; + const auto oldDate = getDate(lastItem); if (oldDate.daysTo(newDate) != 0) { auto separator = new DateSeparator(newDate, this); @@ -594,11 +576,10 @@ TimelineView::addTimelineItem(TimelineItem *item, TimelineDirection direction) // The first item (position 0) is a stretch widget that pushes // the widgets to the bottom of the page. if (scroll_layout_->count() > 1) { - auto firstItem = - qobject_cast(scroll_layout_->itemAt(1)->widget()); + const auto firstItem = scroll_layout_->itemAt(1)->widget(); if (firstItem) { - auto oldDate = firstItem->descriptionMessage().datetime; + const auto oldDate = getDate(firstItem); if (newDate.daysTo(oldDate) != 0) { auto separator = new DateSeparator(oldDate); diff --git a/src/ui/InfoMessage.cpp b/src/ui/InfoMessage.cpp new file mode 100644 index 00000000..b150e61b --- /dev/null +++ b/src/ui/InfoMessage.cpp @@ -0,0 +1,79 @@ +#include "Config.h" +#include "InfoMessage.hpp" + +#include +#include +#include + +constexpr int VPadding = 6; +constexpr int HPadding = 12; +constexpr int HMargin = 20; + +InfoMessage::InfoMessage(QWidget *parent) + : QWidget{parent} +{ + font_.setWeight(60); + font_.setPixelSize(conf::timeline::fonts::dateSeparator); +} + +InfoMessage::InfoMessage(QString msg, QWidget *parent) + : QWidget{parent} + , msg_{msg} +{ + font_.setWeight(60); + font_.setPixelSize(conf::timeline::fonts::dateSeparator); + + QFontMetrics fm{font_}; + width_ = fm.width(msg_) + HPadding * 2; + height_ = fm.ascent() + 2 * VPadding; + + setFixedHeight(height_ + 2 * HMargin); +} + +void +InfoMessage::paintEvent(QPaintEvent *) +{ + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + p.setFont(font_); + + // Center the box horizontally & vertically. + auto textRegion = QRectF(width() / 2 - width_ / 2, HMargin, width_, height_); + + QPainterPath ppath; + ppath.addRoundedRect(textRegion, height_ / 2, height_ / 2); + + p.setPen(Qt::NoPen); + p.fillPath(ppath, boxColor()); + p.drawPath(ppath); + + p.setPen(QPen(textColor())); + p.drawText(textRegion, Qt::AlignCenter, msg_); +} + +DateSeparator::DateSeparator(QDateTime datetime, QWidget *parent) + : InfoMessage{parent} +{ + auto now = QDateTime::currentDateTime(); + auto days = now.daysTo(datetime); + + QString fmt; + + if (now.date().year() != datetime.date().year()) + fmt = QString("ddd d MMMM yy"); + else + fmt = QString("ddd d MMMM"); + + if (days == 0) + msg_ = tr("Today"); + else if (std::abs(days) == 1) + msg_ = tr("Yesterday"); + else + msg_ = datetime.toString(fmt); + + QFontMetrics fm{font_}; + width_ = fm.width(msg_) + HPadding * 2; + height_ = fm.ascent() + 2 * VPadding; + + setFixedHeight(height_ + 2 * HMargin); +}