diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 1b5b7f23..29f6839a 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -178,6 +178,8 @@ add_executable(vibechat src/videoplayer_window.cpp src/imageviewer_window.cpp src/member_files/browser.cpp + src/avatars/avatarcache.cpp + src/avatars/avatarmanager.cpp src/audiodevicesettingspage.cpp src/storagesettingspage.cpp src/connmonitorwindow.cpp diff --git a/tools/chatgui/src/memberlistmodel.cpp b/tools/chatgui/src/memberlistmodel.cpp index ce5677b6..ef4b91e7 100644 --- a/tools/chatgui/src/memberlistmodel.cpp +++ b/tools/chatgui/src/memberlistmodel.cpp @@ -1,5 +1,6 @@ #include #include "memberlistmodel.h" +#include "avatars/avatarmanager.h" #include "../transport/gui_bridge.h" #include #include @@ -12,12 +13,28 @@ extern "C" { #include "../../../lib/mem.h" } -#define CHAT_MEMBER_DISPLAY_SIZE 79 +static void applyPhoto(MemberListItem& item, const QString& channel) { + auto* manager = AvatarManager::instance(); if (!manager) return; + const QImage image = manager->image(channel, item.nodeId); if (image.isNull()) return; + QPixmap pixmap(24, 24); pixmap.fill(Qt::transparent); + QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing); + QPainterPath clip; clip.addEllipse(QRectF(0, 0, 24, 24)); painter.setClipPath(clip); + painter.drawImage(QRect(0, 0, 24, 24), image); painter.setClipping(false); + painter.setPen(QPen(Qt::white, 1)); painter.setBrush(item.online ? QColor("#4CAF50") : QColor("#9E9E9E")); + painter.drawEllipse(QRect(17, 17, 6, 6)); painter.end(); item.avatar = pixmap; +} MemberListModel::MemberListModel(QObject* parent) : QAbstractListModel(parent) , m_rttTimer(new QTimer(this)) { + if (auto* manager = AvatarManager::instance()) connect(manager, &AvatarManager::changed, this, + [this](const QString& channel, quint64 owner) { + if (channel != m_channelId) return; + for (auto it = m_cache.begin(); it != m_cache.end(); ++it) if (it->nodeId == owner) { + applyPhoto(*it, channel); emit dataChanged(index(it.key()), index(it.key()), {Qt::DecorationRole}); + } + }); m_rttTimer->setInterval(2000); connect(m_rttTimer, &QTimer::timeout, this, &MemberListModel::refreshRtt); m_rttTimer->start(); @@ -133,8 +150,8 @@ static void deserialize_member(const uint8_t* src, MemberListItem& item) { } void MemberListModel::onMemberList(const QString& chId, int count, const uint8_t* data, int dataLen) { - (void)dataLen; if (chId != m_channelId) return; + if (count < 0 || count > dataLen / CHAT_MEMBER_DISPLAY_SIZE) return; beginResetModel(); m_totalCount = 0; @@ -144,6 +161,7 @@ void MemberListModel::onMemberList(const QString& chId, int count, const uint8_t for (int i = 0; i < count; i++) { MemberListItem item; deserialize_member(data + i * CHAT_MEMBER_DISPLAY_SIZE, item); + applyPhoto(item, chId); if ((item.flags & CHAT_MEMBER_FLAG_DELETED) && !m_isOwner) continue; m_cache[row++] = item; } @@ -156,6 +174,7 @@ void MemberListModel::onMemberUpdated(const QString& chId, const uint8_t* data) MemberListItem item; deserialize_member(data, item); + applyPhoto(item, chId); /* find existing row by nodeId */ int row = -1; diff --git a/tools/chatgui/src/messagelist.cpp b/tools/chatgui/src/messagelist.cpp index 524977f2..a7d49a8b 100644 --- a/tools/chatgui/src/messagelist.cpp +++ b/tools/chatgui/src/messagelist.cpp @@ -54,6 +54,7 @@ void chat_core_save_ui_state_trampoline(void* arg); #include "radio_audio_engine.h" #include "radiopanel.h" #include "flagpainter.h" +#include "avatars/avatarmanager.h" #include #include #include @@ -336,6 +337,22 @@ MessageList::MessageList(DbManager* db, AudioRecorder* recorder, QWidget *parent , m_recorder(recorder) { auto *layout = new QVBoxLayout(this); + if (auto* manager = AvatarManager::instance()) connect(manager, &AvatarManager::changed, this, + [this, manager](const QString& channel, quint64 owner) { + if (channel != m_currentChannelId) return; + const QImage image = manager->image(channel, owner); + for (int row = 0; row < m_model->rowCount(); row++) { + auto* item = m_model->item(row); + if (item->data(MsgAuthorNodeIdRole).toULongLong() != owner) continue; + QPixmap avatar; + if (!image.isNull()) avatar = QPixmap::fromImage(image); + else { + const AccountRow account = m_db->getAccount(owner); + avatar = makeAvatar(QColor(account.avatarColor), account.avatarLetter.isEmpty() ? authorName(owner).left(1) : account.avatarLetter); + } + item->setData(avatar, MsgAvatarRole); + } + }); layout->setContentsMargins(0, 0, 0, 0); layout->setSpacing(0); @@ -701,6 +718,10 @@ void MessageList::loadChannel(const QString& channelId) { } auto avatar = makeAvatar(color, letter); + if (auto* manager = AvatarManager::instance()) { + const QImage image = manager->image(m_currentChannelId, m.authorNodeId); + if (!image.isNull()) avatar = QPixmap::fromImage(image); + } auto time = QDateTime::fromMSecsSinceEpoch(m.timestamp).toString("HH:mm"); QVariantList reacts; @@ -775,6 +796,10 @@ void MessageList::refresh() { if (!m_avatarCache.contains(m.authorNodeId)) m_avatarCache[m.authorNodeId] = color; auto avatar = makeAvatar(color, letter); + if (auto* manager = AvatarManager::instance()) { + const QImage image = manager->image(m_currentChannelId, m.authorNodeId); + if (!image.isNull()) avatar = QPixmap::fromImage(image); + } auto time = QDateTime::fromMSecsSinceEpoch(m.timestamp).toString("HH:mm"); auto *item = new QStandardItem(); setMsg(item, author, QString::fromUtf8(m.data), time, avatar, false,