Mark encrypted messages with a lock icon

This commit is contained in:
Konstantinos Sideris 2018-07-01 19:40:53 +03:00
parent 95ce2ef920
commit ccc6cd8dab
13 changed files with 200 additions and 102 deletions

View file

@ -97,6 +97,7 @@ constexpr int headerLeftMargin = 15;
namespace fonts {
constexpr int timestamp = 13;
constexpr int indicator = timestamp - 2;
constexpr int dateSeparator = conf::fontSize;
} // namespace fonts
} // namespace timeline

View file

@ -22,6 +22,7 @@
#include <QDateTime>
#include <QHBoxLayout>
#include <QLabel>
#include <QLayout>
#include <QPainter>
#include <QSettings>
#include <QStyle>
@ -43,6 +44,46 @@ class VideoItem;
class FileItem;
class Avatar;
enum class StatusIndicatorState
{
//! The encrypted message was received by the server.
Encrypted,
//! The plaintext message was received by the server.
Received,
//! The client sent the message. Not yet received.
Sent,
//! When the message is loaded from cache or backfill.
Empty,
};
//!
//! Used to notify the user about the status of a message.
//!
class StatusIndicator : public QWidget
{
Q_OBJECT
public:
explicit StatusIndicator(QWidget *parent);
void setState(StatusIndicatorState state);
protected:
void paintEvent(QPaintEvent *event) override;
private:
void paintIcon(QPainter &p, QIcon &icon);
QIcon lockIcon_;
QIcon clockIcon_;
QIcon checkmarkIcon_;
QColor iconColor_ = QColor("#999");
StatusIndicatorState state_ = StatusIndicatorState::Empty;
static constexpr int MaxWidth = 24;
};
class TextLabel : public QTextBrowser
{
Q_OBJECT
@ -192,7 +233,8 @@ public:
DescInfo descriptionMessage() const { return descriptionMsg_; }
QString eventId() const { return event_id_; }
void setEventId(const QString &event_id) { event_id_ = event_id; }
void markReceived();
void markReceived(bool isEncrypted);
void markSent();
bool isReceived() { return isReceived_; };
void setRoomId(QString room_id) { room_id_ = room_id; }
void sendReadReceipt() const;
@ -228,6 +270,9 @@ private:
void setupAvatarLayout(const QString &userName);
void setupSimpleLayout();
void adjustMessageLayout();
void adjustMessageLayoutForWidget();
//! Whether or not the event associated with the widget
//! has been acknowledged by the server.
bool isReceived_ = false;
@ -247,7 +292,6 @@ private:
QHBoxLayout *topLayout_ = nullptr;
QHBoxLayout *messageLayout_ = nullptr;
QVBoxLayout *mainLayout_ = nullptr;
QVBoxLayout *headerLayout_ = nullptr;
QHBoxLayout *widgetLayout_ = nullptr;
Avatar *userAvatar_;
@ -255,8 +299,9 @@ private:
QFont font_;
QFont usernameFont_;
StatusIndicator *statusIndicator_;
QLabel *timestamp_;
QLabel *checkmark_;
QLabel *userName_;
TextLabel *body_;
};
@ -285,20 +330,13 @@ TimelineItem::setupLocalWidgetLayout(Widget *widget, const QString &userid, bool
generateBody(userid, displayName, "");
setupAvatarLayout(displayName);
headerLayout_->addLayout(widgetLayout_);
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();
messageLayout_->addLayout(widgetLayout_, 1);
}
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
mainLayout_->addLayout(messageLayout_);
adjustMessageLayoutForWidget();
}
template<class Event, class Widget>
@ -331,18 +369,11 @@ TimelineItem::setupWidgetLayout(Widget *widget, const Event &event, bool withSen
generateBody(sender, displayName, "");
setupAvatarLayout(displayName);
headerLayout_->addLayout(widgetLayout_);
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
setupSimpleLayout();
messageLayout_->addLayout(widgetLayout_, 1);
}
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
mainLayout_->addLayout(messageLayout_);
adjustMessageLayoutForWidget();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
resources/icons/ui/lock.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 810 B

View file

@ -1,5 +1,11 @@
<RCC>
<qresource prefix="/icons">
<file>icons/ui/lock.png</file>
<file>icons/ui/lock@2x.png</file>
<file>icons/ui/clock.png</file>
<file>icons/ui/clock@2x.png</file>
<file>icons/ui/checkmark.png</file>
<file>icons/ui/checkmark@2x.png</file>
<file>icons/ui/cursor.png</file>
<file>icons/ui/cursor@2x.png</file>
<file>icons/ui/settings.png</file>

View file

@ -48,7 +48,7 @@ CommunitiesList > * {
}
FlatButton {
qproperty-foregroundColor: #14272d;
qproperty-foregroundColor: #495057;
}
FileItem {

View file

@ -38,6 +38,36 @@
#include "RunGuard.h"
#include "version.hpp"
#if defined(Q_OS_LINUX)
#include <boost/stacktrace.hpp>
#include <signal.h>
void
stacktraceHandler(int signum)
{
auto dir = QStandardPaths::writableLocation(QStandardPaths::CacheLocation);
std::signal(signum, SIG_DFL);
boost::stacktrace::safe_dump_to(dir.toStdString() + "/backtrace.dump");
std::raise(SIGABRT);
}
void
registerSignalHandlers()
{
std::signal(SIGSEGV, &stacktraceHandler);
std::signal(SIGABRT, &stacktraceHandler);
}
#else
// No implementation for systems with no stacktrace support.
void
registerSignalHandlers()
{}
#endif
QPoint
screenCenter(int width, int height)
{
@ -126,6 +156,8 @@ main(int argc, char *argv[])
createCacheDirectory();
registerSignalHandlers();
try {
nhlog::init(QString("%1/nheko.log")
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation))

View file

@ -24,6 +24,7 @@
#include "ChatPage.h"
#include "Config.h"
#include "Logging.hpp"
#include "Painter.h"
#include "timeline/TimelineItem.h"
#include "timeline/widgets/AudioItem.h"
@ -31,11 +32,90 @@
#include "timeline/widgets/ImageItem.h"
#include "timeline/widgets/VideoItem.h"
constexpr const static char *CHECKMARK = "";
constexpr int MSG_RIGHT_MARGIN = 7;
constexpr int MSG_PADDING = 20;
StatusIndicator::StatusIndicator(QWidget *parent)
: QWidget(parent)
{
lockIcon_.addFile(":/icons/icons/ui/lock.png");
clockIcon_.addFile(":/icons/icons/ui/clock.png");
checkmarkIcon_.addFile(":/icons/icons/ui/checkmark.png");
}
void
StatusIndicator::paintIcon(QPainter &p, QIcon &icon)
{
auto pixmap = icon.pixmap(width());
QPainter painter(&pixmap);
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
painter.fillRect(pixmap.rect(), p.pen().color());
QIcon(pixmap).paint(&p, rect(), Qt::AlignCenter, QIcon::Normal);
}
void
StatusIndicator::paintEvent(QPaintEvent *)
{
if (state_ == StatusIndicatorState::Empty)
return;
Painter p(this);
PainterHighQualityEnabler hq(p);
p.setPen(iconColor_);
switch (state_) {
case StatusIndicatorState::Sent: {
paintIcon(p, clockIcon_);
break;
}
case StatusIndicatorState::Encrypted:
paintIcon(p, lockIcon_);
break;
case StatusIndicatorState::Received: {
paintIcon(p, checkmarkIcon_);
break;
}
case StatusIndicatorState::Empty:
break;
}
}
void
StatusIndicator::setState(StatusIndicatorState state)
{
state_ = state;
update();
}
void
TimelineItem::adjustMessageLayoutForWidget()
{
messageLayout_->addLayout(widgetLayout_, 1);
messageLayout_->addWidget(statusIndicator_);
messageLayout_->addWidget(timestamp_);
messageLayout_->setAlignment(statusIndicator_, Qt::AlignTop);
messageLayout_->setAlignment(timestamp_, Qt::AlignTop);
mainLayout_->addLayout(messageLayout_);
}
void
TimelineItem::adjustMessageLayout()
{
messageLayout_->addWidget(body_, 1);
messageLayout_->addWidget(statusIndicator_);
messageLayout_->addWidget(timestamp_);
messageLayout_->setAlignment(statusIndicator_, Qt::AlignTop);
messageLayout_->setAlignment(timestamp_, Qt::AlignTop);
mainLayout_->addLayout(messageLayout_);
}
void
TimelineItem::init()
{
@ -102,14 +182,13 @@ TimelineItem::init()
mainLayout_->setContentsMargins(conf::timeline::headerLeftMargin, 0, 0, 0);
mainLayout_->setSpacing(0);
QFont checkmarkFont;
checkmarkFont.setPixelSize(conf::timeline::fonts::timestamp);
QFont timestampFont;
timestampFont.setPixelSize(conf::timeline::fonts::indicator);
QFontMetrics tsFm(timestampFont);
// Setting fixed width for checkmark because systems may have a differing width for a
// space and the Unicode checkmark.
checkmark_ = new QLabel(this);
checkmark_->setFont(checkmarkFont);
checkmark_->setFixedWidth(QFontMetrics{checkmarkFont}.width(CHECKMARK));
statusIndicator_ = new StatusIndicator(this);
statusIndicator_->setFixedWidth(tsFm.height() - tsFm.leading());
statusIndicator_->setFixedHeight(tsFm.height() - tsFm.leading());
}
/*
@ -147,20 +226,14 @@ TimelineItem::TimelineItem(mtx::events::MessageType ty,
generateBody(userid, displayName, body);
setupAvatarLayout(displayName);
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
messageLayout_->addWidget(body_, 1);
}
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
mainLayout_->addLayout(messageLayout_);
adjustMessageLayout();
}
TimelineItem::TimelineItem(ImageItem *image,
@ -316,20 +389,14 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Notice
generateBody(sender, displayName, body);
setupAvatarLayout(displayName);
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
messageLayout_->addWidget(body_, 1);
}
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
mainLayout_->addLayout(messageLayout_);
adjustMessageLayout();
}
/*
@ -364,20 +431,14 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Emote>
generateBody(sender, displayName, emoteMsg);
setupAvatarLayout(displayName);
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(emoteMsg);
setupSimpleLayout();
messageLayout_->addWidget(body_, 1);
}
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
mainLayout_->addLayout(messageLayout_);
adjustMessageLayout();
}
/*
@ -417,28 +478,31 @@ TimelineItem::TimelineItem(const mtx::events::RoomEvent<mtx::events::msg::Text>
generateBody(sender, displayName, body);
setupAvatarLayout(displayName);
messageLayout_->addLayout(headerLayout_, 1);
AvatarProvider::resolve(
room_id_, sender, this, [this](const QImage &img) { setUserAvatar(img); });
} else {
generateBody(body);
setupSimpleLayout();
messageLayout_->addWidget(body_, 1);
}
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
mainLayout_->addLayout(messageLayout_);
adjustMessageLayout();
}
void
TimelineItem::markReceived()
TimelineItem::markSent()
{
statusIndicator_->setState(StatusIndicatorState::Sent);
}
void
TimelineItem::markReceived(bool isEncrypted)
{
isReceived_ = true;
checkmark_->setText(CHECKMARK);
checkmark_->setAlignment(Qt::AlignTop);
if (isEncrypted)
statusIndicator_->setState(StatusIndicatorState::Encrypted);
else
statusIndicator_->setState(StatusIndicatorState::Received);
sendReadReceipt();
}
@ -506,17 +570,10 @@ TimelineItem::generateTimestamp(const QDateTime &time)
QFont timestampFont;
timestampFont.setPixelSize(conf::timeline::fonts::timestamp);
QFontMetrics fm(timestampFont);
int topMargin = QFontMetrics(font_).ascent() - fm.ascent();
timestamp_ = new QLabel(this);
timestamp_->setAlignment(Qt::AlignTop);
timestamp_->setFont(timestampFont);
timestamp_->setText(
QString("<span style=\"color: #999\"> %1 </span>").arg(time.toString("HH:mm")));
timestamp_->setContentsMargins(0, topMargin, 0, 0);
timestamp_->setStyleSheet(
QString("font-size: %1px;").arg(conf::timeline::fonts::timestamp));
}
QString
@ -557,15 +614,8 @@ TimelineItem::setupAvatarLayout(const QString &userName)
topLayout_->insertWidget(0, userAvatar_);
topLayout_->setAlignment(userAvatar_, Qt::AlignTop);
headerLayout_ = new QVBoxLayout;
headerLayout_->setMargin(0);
headerLayout_->setSpacing(conf::timeline::headerSpacing);
if (userName_)
headerLayout_->addWidget(userName_);
if (body_)
headerLayout_->addWidget(body_);
mainLayout_->insertWidget(0, userName_);
}
void
@ -647,33 +697,8 @@ TimelineItem::addAvatar()
userName_->setFont(usernameFont_);
userName_->setText(fm.elidedText(displayName, Qt::ElideRight, 500));
QWidget *widget = nullptr;
// Extract the widget before we delete its layout.
if (widgetLayout_)
widget = widgetLayout_->itemAt(0)->widget();
// Remove all items from the layout.
QLayoutItem *item;
while ((item = messageLayout_->takeAt(0)) != 0)
delete item;
setupAvatarLayout(displayName);
// Restore widget's layout.
if (widget) {
widgetLayout_ = new QHBoxLayout();
widgetLayout_->setContentsMargins(0, 2, 0, 2);
widgetLayout_->addWidget(widget);
widgetLayout_->addStretch(1);
headerLayout_->addLayout(widgetLayout_);
}
messageLayout_->addLayout(headerLayout_, 1);
messageLayout_->addWidget(checkmark_);
messageLayout_->addWidget(timestamp_);
AvatarProvider::resolve(
room_id_, userid, this, [this](const QImage &img) { setUserAvatar(img); });
}

View file

@ -625,7 +625,7 @@ TimelineView::updatePendingMessage(const std::string &txn_id, const QString &eve
// If the response comes after we have received the event from sync
// we've already marked the widget as received.
if (!msg.widget->isReceived()) {
msg.widget->markReceived();
msg.widget->markReceived(msg.is_encrypted);
pending_sent_msgs_.append(msg);
}
} else {
@ -690,6 +690,9 @@ TimelineView::sendNextPendingMessage()
nhlog::ui()->info("[{}] sending next queued message", m.txn_id);
if (m.widget)
m.widget->markSent();
if (m.is_encrypted) {
nhlog::ui()->info("[{}] sending encrypted event", m.txn_id);
prepareEncryptedMessage(std::move(m));
@ -835,7 +838,7 @@ TimelineView::removePendingMessage(const std::string &txn_id)
for (auto it = pending_msgs_.begin(); it != pending_msgs_.end(); ++it) {
if (it->txn_id == txn_id) {
if (it->widget)
it->widget->markReceived();
it->widget->markReceived(it->is_encrypted);
nhlog::ui()->info("[{}] received sync before message response", txn_id);
return;