mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-29 06:08:48 +03:00
Experimental support for user avatars in timeline
This commit is contained in:
parent
b8c8fed655
commit
95c492bad8
9 changed files with 413 additions and 102 deletions
|
@ -78,6 +78,7 @@ if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
set(SRC_FILES
|
set(SRC_FILES
|
||||||
|
src/AvatarProvider.cc
|
||||||
src/ChatPage.cc
|
src/ChatPage.cc
|
||||||
src/Deserializable.cc
|
src/Deserializable.cc
|
||||||
src/EmojiCategory.cc
|
src/EmojiCategory.cc
|
||||||
|
@ -160,6 +161,7 @@ include_directories(include/events)
|
||||||
include_directories(include/events/messages)
|
include_directories(include/events/messages)
|
||||||
|
|
||||||
qt5_wrap_cpp(MOC_HEADERS
|
qt5_wrap_cpp(MOC_HEADERS
|
||||||
|
include/AvatarProvider.h
|
||||||
include/ChatPage.h
|
include/ChatPage.h
|
||||||
include/EmojiCategory.h
|
include/EmojiCategory.h
|
||||||
include/EmojiItemDelegate.h
|
include/EmojiItemDelegate.h
|
||||||
|
|
47
include/AvatarProvider.h
Normal file
47
include/AvatarProvider.h
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <QUrl>
|
||||||
|
|
||||||
|
#include "MatrixClient.h"
|
||||||
|
#include "TimelineItem.h"
|
||||||
|
|
||||||
|
class AvatarProvider : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void init(QSharedPointer<MatrixClient> client);
|
||||||
|
static void resolve(const QString &userId, TimelineItem *item);
|
||||||
|
static void setAvatarUrl(const QString &userId, const QUrl &url);
|
||||||
|
|
||||||
|
static void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
static void updateAvatar(const QString &uid, const QImage &img);
|
||||||
|
|
||||||
|
static QSharedPointer<MatrixClient> client_;
|
||||||
|
static QMap<QString, QList<TimelineItem *>> toBeResolved_;
|
||||||
|
|
||||||
|
static QMap<QString, QImage> userAvatars_;
|
||||||
|
static QMap<QString, QUrl> avatarUrls_;
|
||||||
|
};
|
|
@ -41,6 +41,7 @@ public:
|
||||||
void registerUser(const QString &username, const QString &password, const QString &server) noexcept;
|
void registerUser(const QString &username, const QString &password, const QString &server) noexcept;
|
||||||
void versions() noexcept;
|
void versions() noexcept;
|
||||||
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
|
void fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url);
|
||||||
|
void fetchUserAvatar(const QString &userId, const QUrl &avatarUrl);
|
||||||
void fetchOwnAvatar(const QUrl &avatar_url);
|
void fetchOwnAvatar(const QUrl &avatar_url);
|
||||||
void downloadImage(const QString &event_id, const QUrl &url);
|
void downloadImage(const QString &event_id, const QUrl &url);
|
||||||
void messages(const QString &room_id, const QString &from_token) noexcept;
|
void messages(const QString &room_id, const QString &from_token) noexcept;
|
||||||
|
@ -69,6 +70,7 @@ signals:
|
||||||
void registerSuccess(const QString &userid, const QString &homeserver, const QString &token);
|
void registerSuccess(const QString &userid, const QString &homeserver, const QString &token);
|
||||||
|
|
||||||
void roomAvatarRetrieved(const QString &roomid, const QPixmap &img);
|
void roomAvatarRetrieved(const QString &roomid, const QPixmap &img);
|
||||||
|
void userAvatarRetrieved(const QString &userId, const QImage &img);
|
||||||
void ownAvatarRetrieved(const QPixmap &img);
|
void ownAvatarRetrieved(const QPixmap &img);
|
||||||
void imageDownloaded(const QString &event_id, const QPixmap &img);
|
void imageDownloaded(const QString &event_id, const QPixmap &img);
|
||||||
|
|
||||||
|
@ -95,6 +97,7 @@ private:
|
||||||
Messages,
|
Messages,
|
||||||
Register,
|
Register,
|
||||||
RoomAvatar,
|
RoomAvatar,
|
||||||
|
UserAvatar,
|
||||||
SendTextMessage,
|
SendTextMessage,
|
||||||
Sync,
|
Sync,
|
||||||
Versions,
|
Versions,
|
||||||
|
@ -111,6 +114,7 @@ private:
|
||||||
void onInitialSyncResponse(QNetworkReply *reply);
|
void onInitialSyncResponse(QNetworkReply *reply);
|
||||||
void onSyncResponse(QNetworkReply *reply);
|
void onSyncResponse(QNetworkReply *reply);
|
||||||
void onRoomAvatarResponse(QNetworkReply *reply);
|
void onRoomAvatarResponse(QNetworkReply *reply);
|
||||||
|
void onUserAvatarResponse(QNetworkReply *reply);
|
||||||
void onImageResponse(QNetworkReply *reply);
|
void onImageResponse(QNetworkReply *reply);
|
||||||
void onMessagesResponse(QNetworkReply *reply);
|
void onMessagesResponse(QNetworkReply *reply);
|
||||||
|
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
#include "ImageItem.h"
|
#include "ImageItem.h"
|
||||||
#include "Sync.h"
|
#include "Sync.h"
|
||||||
|
|
||||||
|
#include "Avatar.h"
|
||||||
#include "Image.h"
|
#include "Image.h"
|
||||||
#include "MessageEvent.h"
|
#include "MessageEvent.h"
|
||||||
#include "Notice.h"
|
#include "Notice.h"
|
||||||
|
@ -46,19 +47,35 @@ public:
|
||||||
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, const QString &color, QWidget *parent);
|
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, const QString &color, QWidget *parent);
|
||||||
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, QWidget *parent);
|
TimelineItem(ImageItem *img, const events::MessageEvent<msgs::Image> &e, QWidget *parent);
|
||||||
|
|
||||||
|
void setUserAvatar(const QImage &pixmap);
|
||||||
|
|
||||||
~TimelineItem();
|
~TimelineItem();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void init();
|
||||||
|
|
||||||
void generateBody(const QString &body);
|
void generateBody(const QString &body);
|
||||||
void generateBody(const QString &userid, const QString &color, const QString &body);
|
void generateBody(const QString &userid, const QString &color, const QString &body);
|
||||||
void generateTimestamp(const QDateTime &time);
|
void generateTimestamp(const QDateTime &time);
|
||||||
|
|
||||||
|
void setupAvatarLayout(const QString &userName);
|
||||||
|
void setupSimpleLayout();
|
||||||
|
|
||||||
QString replaceEmoji(const QString &body);
|
QString replaceEmoji(const QString &body);
|
||||||
|
|
||||||
void setupLayout();
|
QHBoxLayout *topLayout_;
|
||||||
|
QVBoxLayout *sideLayout_; // Avatar or Timestamp
|
||||||
|
QVBoxLayout *mainLayout_; // Header & Message body
|
||||||
|
|
||||||
QHBoxLayout *top_layout_;
|
QHBoxLayout *headerLayout_; // Username (&) Timestamp
|
||||||
|
|
||||||
QLabel *time_label_;
|
Avatar *userAvatar_;
|
||||||
QLabel *content_label_;
|
|
||||||
|
QLabel *timestamp_;
|
||||||
|
QLabel *userName_;
|
||||||
|
QLabel *body_;
|
||||||
|
|
||||||
|
QFont bodyFont_;
|
||||||
|
QFont usernameFont_;
|
||||||
|
QFont timestampFont_;
|
||||||
};
|
};
|
||||||
|
|
83
src/AvatarProvider.cc
Normal file
83
src/AvatarProvider.cc
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* nheko Copyright (C) 2017 Konstantinos Sideris <siderisk@auth.gr>
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "AvatarProvider.h"
|
||||||
|
|
||||||
|
QSharedPointer<MatrixClient> AvatarProvider::client_;
|
||||||
|
|
||||||
|
QMap<QString, QImage> AvatarProvider::userAvatars_;
|
||||||
|
QMap<QString, QUrl> AvatarProvider::avatarUrls_;
|
||||||
|
QMap<QString, QList<TimelineItem *>> AvatarProvider::toBeResolved_;
|
||||||
|
|
||||||
|
void AvatarProvider::init(QSharedPointer<MatrixClient> client)
|
||||||
|
{
|
||||||
|
client_ = client;
|
||||||
|
|
||||||
|
connect(client_.data(), &MatrixClient::userAvatarRetrieved, &AvatarProvider::updateAvatar);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarProvider::updateAvatar(const QString &uid, const QImage &img)
|
||||||
|
{
|
||||||
|
if (toBeResolved_.contains(uid)) {
|
||||||
|
auto items = toBeResolved_[uid];
|
||||||
|
|
||||||
|
// Update all the timeline items with the resolved avatar.
|
||||||
|
for (const auto item : items)
|
||||||
|
item->setUserAvatar(img);
|
||||||
|
|
||||||
|
toBeResolved_.remove(uid);
|
||||||
|
}
|
||||||
|
|
||||||
|
userAvatars_.insert(uid, img);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarProvider::resolve(const QString &userId, TimelineItem *item)
|
||||||
|
{
|
||||||
|
if (userAvatars_.contains(userId)) {
|
||||||
|
auto img = userAvatars_[userId];
|
||||||
|
|
||||||
|
item->setUserAvatar(img);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avatarUrls_.contains(userId)) {
|
||||||
|
// Add the current timeline item to the waiting list for this avatar.
|
||||||
|
if (!toBeResolved_.contains(userId)) {
|
||||||
|
client_->fetchUserAvatar(userId, avatarUrls_[userId]);
|
||||||
|
|
||||||
|
QList<TimelineItem *> timelineItems;
|
||||||
|
timelineItems.push_back(item);
|
||||||
|
|
||||||
|
toBeResolved_.insert(userId, timelineItems);
|
||||||
|
} else {
|
||||||
|
toBeResolved_[userId].push_back(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarProvider::setAvatarUrl(const QString &userId, const QUrl &url)
|
||||||
|
{
|
||||||
|
avatarUrls_.insert(userId, url);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarProvider::clear()
|
||||||
|
{
|
||||||
|
userAvatars_.clear();
|
||||||
|
avatarUrls_.clear();
|
||||||
|
toBeResolved_.clear();
|
||||||
|
}
|
|
@ -27,6 +27,7 @@
|
||||||
|
|
||||||
#include "AliasesEventContent.h"
|
#include "AliasesEventContent.h"
|
||||||
#include "AvatarEventContent.h"
|
#include "AvatarEventContent.h"
|
||||||
|
#include "AvatarProvider.h"
|
||||||
#include "CanonicalAliasEventContent.h"
|
#include "CanonicalAliasEventContent.h"
|
||||||
#include "CreateEventContent.h"
|
#include "CreateEventContent.h"
|
||||||
#include "HistoryVisibilityEventContent.h"
|
#include "HistoryVisibilityEventContent.h"
|
||||||
|
@ -173,6 +174,8 @@ ChatPage::ChatPage(QSharedPointer<MatrixClient> client, QWidget *parent)
|
||||||
SIGNAL(ownAvatarRetrieved(const QPixmap &)),
|
SIGNAL(ownAvatarRetrieved(const QPixmap &)),
|
||||||
this,
|
this,
|
||||||
SLOT(setOwnAvatar(const QPixmap &)));
|
SLOT(setOwnAvatar(const QPixmap &)));
|
||||||
|
|
||||||
|
AvatarProvider::init(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChatPage::logout()
|
void ChatPage::logout()
|
||||||
|
@ -203,6 +206,8 @@ void ChatPage::logout()
|
||||||
settingsManager_.clear();
|
settingsManager_.clear();
|
||||||
room_avatars_.clear();
|
room_avatars_.clear();
|
||||||
|
|
||||||
|
AvatarProvider::clear();
|
||||||
|
|
||||||
emit close();
|
emit close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,6 +305,14 @@ void ChatPage::initialSyncCompleted(const SyncResponse &response)
|
||||||
|
|
||||||
state_manager_.insert(it.key(), room_state);
|
state_manager_.insert(it.key(), room_state);
|
||||||
settingsManager_.insert(it.key(), QSharedPointer<RoomSettings>(new RoomSettings(it.key())));
|
settingsManager_.insert(it.key(), QSharedPointer<RoomSettings>(new RoomSettings(it.key())));
|
||||||
|
|
||||||
|
for (const auto membership : room_state.memberships) {
|
||||||
|
auto uid = membership.sender();
|
||||||
|
auto url = membership.content().avatarUrl();
|
||||||
|
|
||||||
|
if (!url.toString().isEmpty())
|
||||||
|
AvatarProvider::setAvatarUrl(uid, url);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
view_manager_->initialize(response.rooms());
|
view_manager_->initialize(response.rooms());
|
||||||
|
|
|
@ -287,6 +287,29 @@ void MatrixClient::onRoomAvatarResponse(QNetworkReply *reply)
|
||||||
emit roomAvatarRetrieved(roomid, pixmap);
|
emit roomAvatarRetrieved(roomid, pixmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MatrixClient::onUserAvatarResponse(QNetworkReply *reply)
|
||||||
|
{
|
||||||
|
reply->deleteLater();
|
||||||
|
|
||||||
|
int status = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||||
|
|
||||||
|
if (status == 0 || status >= 400) {
|
||||||
|
qWarning() << reply->errorString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = reply->readAll();
|
||||||
|
|
||||||
|
if (data.size() == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto roomid = reply->property("userid").toString();
|
||||||
|
|
||||||
|
QImage img;
|
||||||
|
img.loadFromData(data);
|
||||||
|
|
||||||
|
emit userAvatarRetrieved(roomid, img);
|
||||||
|
}
|
||||||
void MatrixClient::onGetOwnAvatarResponse(QNetworkReply *reply)
|
void MatrixClient::onGetOwnAvatarResponse(QNetworkReply *reply)
|
||||||
{
|
{
|
||||||
reply->deleteLater();
|
reply->deleteLater();
|
||||||
|
@ -392,6 +415,9 @@ void MatrixClient::onResponse(QNetworkReply *reply)
|
||||||
case Endpoint::RoomAvatar:
|
case Endpoint::RoomAvatar:
|
||||||
onRoomAvatarResponse(reply);
|
onRoomAvatarResponse(reply);
|
||||||
break;
|
break;
|
||||||
|
case Endpoint::UserAvatar:
|
||||||
|
onUserAvatarResponse(reply);
|
||||||
|
break;
|
||||||
case Endpoint::GetOwnAvatar:
|
case Endpoint::GetOwnAvatar:
|
||||||
onGetOwnAvatarResponse(reply);
|
onGetOwnAvatarResponse(reply);
|
||||||
break;
|
break;
|
||||||
|
@ -591,6 +617,32 @@ void MatrixClient::fetchRoomAvatar(const QString &roomid, const QUrl &avatar_url
|
||||||
reply->setProperty("endpoint", static_cast<int>(Endpoint::RoomAvatar));
|
reply->setProperty("endpoint", static_cast<int>(Endpoint::RoomAvatar));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MatrixClient::fetchUserAvatar(const QString &userId, const QUrl &avatarUrl)
|
||||||
|
{
|
||||||
|
QList<QString> url_parts = avatarUrl.toString().split("mxc://");
|
||||||
|
|
||||||
|
if (url_parts.size() != 2) {
|
||||||
|
qDebug() << "Invalid format for user avatar " << avatarUrl.toString();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
query.addQueryItem("width", "128");
|
||||||
|
query.addQueryItem("height", "128");
|
||||||
|
query.addQueryItem("method", "crop");
|
||||||
|
|
||||||
|
QString media_url = QString("%1/_matrix/media/r0/thumbnail/%2").arg(getHomeServer().toString(), url_parts[1]);
|
||||||
|
|
||||||
|
QUrl endpoint(media_url);
|
||||||
|
endpoint.setQuery(query);
|
||||||
|
|
||||||
|
QNetworkRequest avatar_request(endpoint);
|
||||||
|
|
||||||
|
QNetworkReply *reply = get(avatar_request);
|
||||||
|
reply->setProperty("userid", userId);
|
||||||
|
reply->setProperty("endpoint", static_cast<int>(Endpoint::UserAvatar));
|
||||||
|
}
|
||||||
|
|
||||||
void MatrixClient::downloadImage(const QString &event_id, const QUrl &url)
|
void MatrixClient::downloadImage(const QString &event_id, const QUrl &url)
|
||||||
{
|
{
|
||||||
QNetworkRequest image_request(url);
|
QNetworkRequest image_request(url);
|
||||||
|
|
|
@ -17,8 +17,10 @@
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
#include <QFontDatabase>
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
|
|
||||||
|
#include "AvatarProvider.h"
|
||||||
#include "ImageItem.h"
|
#include "ImageItem.h"
|
||||||
#include "TimelineItem.h"
|
#include "TimelineItem.h"
|
||||||
#include "TimelineViewManager.h"
|
#include "TimelineViewManager.h"
|
||||||
|
@ -29,65 +31,119 @@ static const QString URL_HTML = "<a href=\"\\1\" style=\"color: #333333\">\\1</a
|
||||||
namespace events = matrix::events;
|
namespace events = matrix::events;
|
||||||
namespace msgs = matrix::events::messages;
|
namespace msgs = matrix::events::messages;
|
||||||
|
|
||||||
|
void TimelineItem::init()
|
||||||
|
{
|
||||||
|
userAvatar_ = nullptr;
|
||||||
|
timestamp_ = nullptr;
|
||||||
|
userName_ = nullptr;
|
||||||
|
body_ = nullptr;
|
||||||
|
|
||||||
|
QFontDatabase db;
|
||||||
|
|
||||||
|
bodyFont_ = db.font("Open Sans", "Regular", 10);
|
||||||
|
usernameFont_ = db.font("Open Sans", "Bold", 10);
|
||||||
|
timestampFont_ = db.font("Open Sans", "Regular", 7);
|
||||||
|
|
||||||
|
topLayout_ = new QHBoxLayout(this);
|
||||||
|
sideLayout_ = new QVBoxLayout();
|
||||||
|
mainLayout_ = new QVBoxLayout();
|
||||||
|
headerLayout_ = new QHBoxLayout();
|
||||||
|
|
||||||
|
topLayout_->setContentsMargins(7, 0, 0, 0);
|
||||||
|
topLayout_->setSpacing(9);
|
||||||
|
|
||||||
|
topLayout_->addLayout(sideLayout_);
|
||||||
|
topLayout_->addLayout(mainLayout_, 1);
|
||||||
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(const QString &userid, const QString &color, QString body, QWidget *parent)
|
TimelineItem::TimelineItem(const QString &userid, const QString &color, QString body, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
body.replace(URL_REGEX, URL_HTML);
|
body.replace(URL_REGEX, URL_HTML);
|
||||||
|
auto displayName = TimelineViewManager::displayName(userid);
|
||||||
|
|
||||||
generateTimestamp(QDateTime::currentDateTime());
|
generateTimestamp(QDateTime::currentDateTime());
|
||||||
generateBody(TimelineViewManager::displayName(userid), color, body);
|
generateBody(displayName, color, body);
|
||||||
setupLayout();
|
|
||||||
|
setupAvatarLayout(displayName);
|
||||||
|
|
||||||
|
mainLayout_->addLayout(headerLayout_);
|
||||||
|
mainLayout_->addWidget(body_);
|
||||||
|
mainLayout_->setMargin(0);
|
||||||
|
mainLayout_->setSpacing(0);
|
||||||
|
|
||||||
|
AvatarProvider::resolve(userid, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(QString body, QWidget *parent)
|
TimelineItem::TimelineItem(QString body, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
body.replace(URL_REGEX, URL_HTML);
|
body.replace(URL_REGEX, URL_HTML);
|
||||||
|
|
||||||
generateTimestamp(QDateTime::currentDateTime());
|
generateTimestamp(QDateTime::currentDateTime());
|
||||||
generateBody(body);
|
generateBody(body);
|
||||||
setupLayout();
|
|
||||||
|
setupSimpleLayout();
|
||||||
|
|
||||||
|
mainLayout_->addWidget(body_);
|
||||||
|
mainLayout_->setMargin(0);
|
||||||
|
mainLayout_->setSpacing(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, const QString &color, QWidget *parent)
|
TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, const QString &color, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
||||||
|
auto displayName = TimelineViewManager::displayName(event.sender());
|
||||||
|
|
||||||
generateTimestamp(timestamp);
|
generateTimestamp(timestamp);
|
||||||
generateBody(TimelineViewManager::displayName(event.sender()), color, "");
|
generateBody(displayName, color, "");
|
||||||
|
|
||||||
top_layout_ = new QHBoxLayout();
|
setupAvatarLayout(displayName);
|
||||||
top_layout_->setMargin(0);
|
|
||||||
top_layout_->addWidget(time_label_);
|
|
||||||
|
|
||||||
auto right_layout = new QVBoxLayout();
|
auto imageLayout = new QHBoxLayout();
|
||||||
right_layout->addWidget(content_label_);
|
imageLayout->addWidget(image);
|
||||||
right_layout->addWidget(image);
|
imageLayout->addStretch(1);
|
||||||
|
|
||||||
top_layout_->addLayout(right_layout);
|
mainLayout_->addLayout(headerLayout_);
|
||||||
top_layout_->addStretch(1);
|
mainLayout_->addLayout(imageLayout);
|
||||||
|
mainLayout_->setContentsMargins(0, 4, 0, 0);
|
||||||
|
mainLayout_->setSpacing(0);
|
||||||
|
|
||||||
setLayout(top_layout_);
|
AvatarProvider::resolve(event.sender(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, QWidget *parent)
|
TimelineItem::TimelineItem(ImageItem *image, const events::MessageEvent<msgs::Image> &event, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
||||||
generateTimestamp(timestamp);
|
generateTimestamp(timestamp);
|
||||||
|
|
||||||
top_layout_ = new QHBoxLayout();
|
setupSimpleLayout();
|
||||||
top_layout_->setMargin(0);
|
|
||||||
top_layout_->addWidget(time_label_);
|
|
||||||
top_layout_->addWidget(image, 1);
|
|
||||||
top_layout_->addStretch(1);
|
|
||||||
|
|
||||||
setLayout(top_layout_);
|
auto imageLayout = new QHBoxLayout();
|
||||||
|
imageLayout->setMargin(0);
|
||||||
|
imageLayout->addWidget(image);
|
||||||
|
imageLayout->addStretch(1);
|
||||||
|
|
||||||
|
mainLayout_->addLayout(imageLayout);
|
||||||
|
mainLayout_->setContentsMargins(0, 4, 0, 0);
|
||||||
|
mainLayout_->setSpacing(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event, bool with_sender, const QString &color, QWidget *parent)
|
TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event, bool with_sender, const QString &color, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
auto body = event.content().body().trimmed().toHtmlEscaped();
|
auto body = event.content().body().trimmed().toHtmlEscaped();
|
||||||
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
||||||
|
|
||||||
|
@ -96,17 +152,34 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Notice> &event, bool
|
||||||
body.replace(URL_REGEX, URL_HTML);
|
body.replace(URL_REGEX, URL_HTML);
|
||||||
body = "<i style=\"color: #565E5E\">" + body + "</i>";
|
body = "<i style=\"color: #565E5E\">" + body + "</i>";
|
||||||
|
|
||||||
if (with_sender)
|
if (with_sender) {
|
||||||
generateBody(TimelineViewManager::displayName(event.sender()), color, body);
|
auto displayName = TimelineViewManager::displayName(event.sender());
|
||||||
else
|
|
||||||
|
generateBody(displayName, color, body);
|
||||||
|
setupAvatarLayout(displayName);
|
||||||
|
|
||||||
|
mainLayout_->addLayout(headerLayout_);
|
||||||
|
mainLayout_->addWidget(body_);
|
||||||
|
mainLayout_->setMargin(0);
|
||||||
|
mainLayout_->setSpacing(0);
|
||||||
|
|
||||||
|
AvatarProvider::resolve(event.sender(), this);
|
||||||
|
} else {
|
||||||
generateBody(body);
|
generateBody(body);
|
||||||
|
|
||||||
setupLayout();
|
setupSimpleLayout();
|
||||||
|
|
||||||
|
mainLayout_->addWidget(body_);
|
||||||
|
mainLayout_->setMargin(0);
|
||||||
|
mainLayout_->setSpacing(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event, bool with_sender, const QString &color, QWidget *parent)
|
TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event, bool with_sender, const QString &color, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
{
|
{
|
||||||
|
init();
|
||||||
|
|
||||||
auto body = event.content().body().trimmed().toHtmlEscaped();
|
auto body = event.content().body().trimmed().toHtmlEscaped();
|
||||||
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
auto timestamp = QDateTime::fromMSecsSinceEpoch(event.timestamp());
|
||||||
|
|
||||||
|
@ -114,34 +187,45 @@ TimelineItem::TimelineItem(const events::MessageEvent<msgs::Text> &event, bool w
|
||||||
|
|
||||||
body.replace(URL_REGEX, URL_HTML);
|
body.replace(URL_REGEX, URL_HTML);
|
||||||
|
|
||||||
if (with_sender)
|
if (with_sender) {
|
||||||
generateBody(TimelineViewManager::displayName(event.sender()), color, body);
|
auto displayName = TimelineViewManager::displayName(event.sender());
|
||||||
else
|
generateBody(displayName, color, body);
|
||||||
|
|
||||||
|
setupAvatarLayout(displayName);
|
||||||
|
|
||||||
|
mainLayout_->addLayout(headerLayout_);
|
||||||
|
mainLayout_->addWidget(body_);
|
||||||
|
mainLayout_->setMargin(0);
|
||||||
|
mainLayout_->setSpacing(0);
|
||||||
|
|
||||||
|
AvatarProvider::resolve(event.sender(), this);
|
||||||
|
} else {
|
||||||
generateBody(body);
|
generateBody(body);
|
||||||
|
|
||||||
setupLayout();
|
setupSimpleLayout();
|
||||||
|
|
||||||
|
mainLayout_->addWidget(body_);
|
||||||
|
mainLayout_->setMargin(0);
|
||||||
|
mainLayout_->setSpacing(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only the body is displayed.
|
||||||
void TimelineItem::generateBody(const QString &body)
|
void TimelineItem::generateBody(const QString &body)
|
||||||
{
|
{
|
||||||
content_label_ = new QLabel(this);
|
QString content("<span style=\"color: #171919;\">%1</span>");
|
||||||
content_label_->setWordWrap(true);
|
|
||||||
content_label_->setAlignment(Qt::AlignTop);
|
body_ = new QLabel(this);
|
||||||
content_label_->setStyleSheet("margin: 0;");
|
body_->setWordWrap(true);
|
||||||
QString content(
|
body_->setFont(bodyFont_);
|
||||||
"<html>"
|
body_->setText(content.arg(replaceEmoji(body)));
|
||||||
"<head/>"
|
body_->setAlignment(Qt::AlignTop);
|
||||||
"<body>"
|
|
||||||
" <span style=\"font-size: 10pt; color: #171919;\">"
|
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
||||||
" %1"
|
body_->setOpenExternalLinks(true);
|
||||||
" </span>"
|
|
||||||
"</body>"
|
|
||||||
"</html>");
|
|
||||||
content_label_->setText(content.arg(replaceEmoji(body)));
|
|
||||||
content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
|
||||||
content_label_->setOpenExternalLinks(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The username/timestamp is displayed along with the message body.
|
||||||
void TimelineItem::generateBody(const QString &userid, const QString &color, const QString &body)
|
void TimelineItem::generateBody(const QString &userid, const QString &color, const QString &body)
|
||||||
{
|
{
|
||||||
auto sender = userid;
|
auto sender = userid;
|
||||||
|
@ -150,64 +234,35 @@ void TimelineItem::generateBody(const QString &userid, const QString &color, con
|
||||||
if (userid.split(":")[0].split("@").size() > 1)
|
if (userid.split(":")[0].split("@").size() > 1)
|
||||||
sender = userid.split(":")[0].split("@")[1];
|
sender = userid.split(":")[0].split("@")[1];
|
||||||
|
|
||||||
content_label_ = new QLabel(this);
|
QString userContent("<span style=\"color: %1\"> %2 </span>");
|
||||||
content_label_->setWordWrap(true);
|
QString bodyContent("<span style=\"color: #171717;\"> %1 </span>");
|
||||||
content_label_->setAlignment(Qt::AlignTop);
|
|
||||||
content_label_->setStyleSheet("margin: 0;");
|
userName_ = new QLabel(this);
|
||||||
QString content(
|
userName_->setFont(usernameFont_);
|
||||||
"<html>"
|
userName_->setText(userContent.arg(color).arg(sender));
|
||||||
"<head/>"
|
userName_->setAlignment(Qt::AlignTop);
|
||||||
"<body>"
|
|
||||||
" <span style=\"font-size: 10pt; font-weight: 600; color: %1\">"
|
if (body.isEmpty())
|
||||||
" %2"
|
return;
|
||||||
" </span>"
|
|
||||||
" <span style=\"font-size: 10pt; color: #171919;\">"
|
body_ = new QLabel(this);
|
||||||
" %3"
|
body_->setFont(bodyFont_);
|
||||||
" </span>"
|
body_->setWordWrap(true);
|
||||||
"</body>"
|
body_->setAlignment(Qt::AlignTop);
|
||||||
"</html>");
|
body_->setText(bodyContent.arg(replaceEmoji(body)));
|
||||||
content_label_->setText(content.arg(color).arg(sender).arg(replaceEmoji(body)));
|
body_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
||||||
content_label_->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextBrowserInteraction);
|
body_->setOpenExternalLinks(true);
|
||||||
content_label_->setOpenExternalLinks(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimelineItem::generateTimestamp(const QDateTime &time)
|
void TimelineItem::generateTimestamp(const QDateTime &time)
|
||||||
{
|
{
|
||||||
auto local_time = time.toString("HH:mm");
|
QString msg("<span style=\"color: #5d6565;\"> %1 </span>");
|
||||||
|
|
||||||
time_label_ = new QLabel(this);
|
timestamp_ = new QLabel(this);
|
||||||
QString msg(
|
timestamp_->setFont(timestampFont_);
|
||||||
"<html>"
|
timestamp_->setText(msg.arg(time.toString("HH:mm")));
|
||||||
"<head/>"
|
timestamp_->setAlignment(Qt::AlignTop);
|
||||||
"<body>"
|
timestamp_->setStyleSheet("margin-top: 2px;");
|
||||||
" <span style=\"font-size: 7pt; color: #5d6565;\">"
|
|
||||||
" %1"
|
|
||||||
" </span>"
|
|
||||||
"</body>"
|
|
||||||
"</html>");
|
|
||||||
time_label_->setText(msg.arg(local_time));
|
|
||||||
time_label_->setStyleSheet("margin-left: 7px; margin-right: 7px; margin-top: 0;");
|
|
||||||
time_label_->setAlignment(Qt::AlignTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimelineItem::setupLayout()
|
|
||||||
{
|
|
||||||
if (time_label_ == nullptr) {
|
|
||||||
qWarning() << "TimelineItem: Time label is not initialized";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (content_label_ == nullptr) {
|
|
||||||
qWarning() << "TimelineItem: Content label is not initialized";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
top_layout_ = new QHBoxLayout();
|
|
||||||
top_layout_->setMargin(0);
|
|
||||||
top_layout_->addWidget(time_label_);
|
|
||||||
top_layout_->addWidget(content_label_, 1);
|
|
||||||
|
|
||||||
setLayout(top_layout_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TimelineItem::replaceEmoji(const QString &body)
|
QString TimelineItem::replaceEmoji(const QString &body)
|
||||||
|
@ -227,6 +282,46 @@ QString TimelineItem::replaceEmoji(const QString &body)
|
||||||
return fmtBody;
|
return fmtBody;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TimelineItem::setupAvatarLayout(const QString &userName)
|
||||||
|
{
|
||||||
|
topLayout_->setContentsMargins(7, 6, 0, 0);
|
||||||
|
|
||||||
|
userAvatar_ = new Avatar(this);
|
||||||
|
userAvatar_->setLetter(QChar(userName[0]).toUpper());
|
||||||
|
userAvatar_->setBackgroundColor(QColor("#eee"));
|
||||||
|
userAvatar_->setTextColor(QColor("black"));
|
||||||
|
userAvatar_->setSize(32);
|
||||||
|
|
||||||
|
// TODO: The provided user name should be a UserId class
|
||||||
|
if (userName[0] == '@' && userName.size() > 1)
|
||||||
|
userAvatar_->setLetter(QChar(userName[1]).toUpper());
|
||||||
|
|
||||||
|
sideLayout_->addWidget(userAvatar_);
|
||||||
|
sideLayout_->addStretch(1);
|
||||||
|
sideLayout_->setMargin(0);
|
||||||
|
sideLayout_->setSpacing(0);
|
||||||
|
|
||||||
|
headerLayout_->addWidget(userName_);
|
||||||
|
headerLayout_->addWidget(timestamp_, 1);
|
||||||
|
headerLayout_->setMargin(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineItem::setupSimpleLayout()
|
||||||
|
{
|
||||||
|
sideLayout_->addWidget(timestamp_);
|
||||||
|
sideLayout_->addStretch(1);
|
||||||
|
|
||||||
|
topLayout_->setContentsMargins(9, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TimelineItem::setUserAvatar(const QImage &avatar)
|
||||||
|
{
|
||||||
|
if (userAvatar_ == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
userAvatar_->setImage(avatar);
|
||||||
|
}
|
||||||
|
|
||||||
TimelineItem::~TimelineItem()
|
TimelineItem::~TimelineItem()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,9 +36,7 @@ int main(int argc, char *argv[])
|
||||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Regular.ttf");
|
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Regular.ttf");
|
||||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Italic.ttf");
|
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Italic.ttf");
|
||||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Bold.ttf");
|
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Bold.ttf");
|
||||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-BoldItalic.ttf");
|
|
||||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Semibold.ttf");
|
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-Semibold.ttf");
|
||||||
QFontDatabase::addApplicationFont(":/fonts/fonts/OpenSans/OpenSans-SemiboldItalic.ttf");
|
|
||||||
QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
|
QFontDatabase::addApplicationFont(":/fonts/fonts/EmojiOne/emojione-android.ttf");
|
||||||
|
|
||||||
app.setWindowIcon(QIcon(":/logos/nheko.png"));
|
app.setWindowIcon(QIcon(":/logos/nheko.png"));
|
||||||
|
|
Loading…
Reference in a new issue