citra_qt: Use the new verify backend; UI changes
Displayed username along with nickname (when they are not identical); Requested and displayed user's avatar; Made the dialog bigger for extended names. Added a few functions to web_backend (GetImage, GetPlain) to support getting data in multiple content-types. Added a no_avatar icon for users without avatars.
This commit is contained in:
parent
4906c8ce7b
commit
386bf5c861
18 changed files with 263 additions and 80 deletions
|
@ -5,6 +5,7 @@
|
|||
#include <array>
|
||||
#include <future>
|
||||
#include <QColor>
|
||||
#include <QFutureWatcher>
|
||||
#include <QImage>
|
||||
#include <QList>
|
||||
#include <QLocale>
|
||||
|
@ -12,6 +13,7 @@
|
|||
#include <QMessageBox>
|
||||
#include <QMetaType>
|
||||
#include <QTime>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
#include "citra_qt/game_list_p.h"
|
||||
#include "citra_qt/multiplayer/chat_room.h"
|
||||
|
@ -19,6 +21,9 @@
|
|||
#include "common/logging/log.h"
|
||||
#include "core/announce_multiplayer_session.h"
|
||||
#include "ui_chat_room.h"
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
#include "web_service/web_backend.h"
|
||||
#endif
|
||||
|
||||
class ChatMessage {
|
||||
public:
|
||||
|
@ -27,14 +32,21 @@ public:
|
|||
QLocale locale;
|
||||
timestamp = locale.toString(ts.isValid() ? ts : QTime::currentTime(), QLocale::ShortFormat);
|
||||
nickname = QString::fromStdString(chat.nickname);
|
||||
username = QString::fromStdString(chat.username);
|
||||
message = QString::fromStdString(chat.message);
|
||||
}
|
||||
|
||||
/// Format the message using the players color
|
||||
QString GetPlayerChatMessage(u16 player) const {
|
||||
auto color = player_color[player % 16];
|
||||
QString name;
|
||||
if (username.isEmpty() || username == nickname) {
|
||||
name = nickname;
|
||||
} else {
|
||||
name = QString("%1 (%2)").arg(nickname, username);
|
||||
}
|
||||
return QString("[%1] <font color='%2'><%3></font> %4")
|
||||
.arg(timestamp, color, nickname.toHtmlEscaped(), message.toHtmlEscaped());
|
||||
.arg(timestamp, color, name.toHtmlEscaped(), message.toHtmlEscaped());
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -44,6 +56,7 @@ private:
|
|||
|
||||
QString timestamp;
|
||||
QString nickname;
|
||||
QString username;
|
||||
QString message;
|
||||
};
|
||||
|
||||
|
@ -67,22 +80,54 @@ private:
|
|||
QString message;
|
||||
};
|
||||
|
||||
class PlayerListItem : public QStandardItem {
|
||||
public:
|
||||
static const int NicknameRole = Qt::UserRole + 1;
|
||||
static const int UsernameRole = Qt::UserRole + 2;
|
||||
static const int AvatarUrlRole = Qt::UserRole + 3;
|
||||
static const int GameNameRole = Qt::UserRole + 4;
|
||||
|
||||
PlayerListItem() = default;
|
||||
explicit PlayerListItem(const std::string& nickname, const std::string& username,
|
||||
const std::string& avatar_url, const std::string& game_name) {
|
||||
setEditable(false);
|
||||
setData(QString::fromStdString(nickname), NicknameRole);
|
||||
setData(QString::fromStdString(username), UsernameRole);
|
||||
setData(QString::fromStdString(avatar_url), AvatarUrlRole);
|
||||
if (game_name.empty()) {
|
||||
setData(QObject::tr("Not playing a game"), GameNameRole);
|
||||
} else {
|
||||
setData(QString::fromStdString(game_name), GameNameRole);
|
||||
}
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
if (role != Qt::DisplayRole) {
|
||||
return QStandardItem::data(role);
|
||||
}
|
||||
QString name;
|
||||
const QString nickname = data(NicknameRole).toString();
|
||||
const QString username = data(UsernameRole).toString();
|
||||
if (username.isEmpty() || username == nickname) {
|
||||
name = nickname;
|
||||
} else {
|
||||
name = QString("%1 (%2)").arg(nickname, username);
|
||||
}
|
||||
return QString("%1\n %2").arg(name, data(GameNameRole).toString());
|
||||
}
|
||||
};
|
||||
|
||||
ChatRoom::ChatRoom(QWidget* parent) : QWidget(parent), ui(std::make_unique<Ui::ChatRoom>()) {
|
||||
ui->setupUi(this);
|
||||
|
||||
// set the item_model for player_view
|
||||
enum {
|
||||
COLUMN_NAME,
|
||||
COLUMN_GAME,
|
||||
COLUMN_COUNT, // Number of columns
|
||||
};
|
||||
|
||||
player_list = new QStandardItemModel(ui->player_view);
|
||||
ui->player_view->setModel(player_list);
|
||||
ui->player_view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
player_list->insertColumns(0, COLUMN_COUNT);
|
||||
player_list->setHeaderData(COLUMN_NAME, Qt::Horizontal, tr("Name"));
|
||||
player_list->setHeaderData(COLUMN_GAME, Qt::Horizontal, tr("Game"));
|
||||
// set a header to make it look better though there is only one column
|
||||
player_list->insertColumns(0, 1);
|
||||
player_list->setHeaderData(0, Qt::Horizontal, tr("Members"));
|
||||
|
||||
ui->chat_history->document()->setMaximumBlockCount(max_chat_lines);
|
||||
|
||||
|
@ -157,7 +202,8 @@ void ChatRoom::OnChatReceive(const Network::ChatEntry& chat) {
|
|||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&chat](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == chat.nickname;
|
||||
return member.nickname == chat.nickname &&
|
||||
member.username == chat.username;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
LOG_INFO(Network, "Chat message received from unknown player. Ignoring it.");
|
||||
|
@ -184,12 +230,14 @@ void ChatRoom::OnSendChat() {
|
|||
return;
|
||||
}
|
||||
auto nick = room->GetNickname();
|
||||
Network::ChatEntry chat{nick, message};
|
||||
auto username = room->GetUsername();
|
||||
Network::ChatEntry chat{nick, username, message};
|
||||
|
||||
auto members = room->GetMemberInformation();
|
||||
auto it = std::find_if(members.begin(), members.end(),
|
||||
[&chat](const Network::RoomMember::MemberInformation& member) {
|
||||
return member.nickname == chat.nickname;
|
||||
return member.nickname == chat.nickname &&
|
||||
member.username == chat.username;
|
||||
});
|
||||
if (it == members.end()) {
|
||||
LOG_INFO(Network, "Cannot find self in the player list when sending a message.");
|
||||
|
@ -202,20 +250,64 @@ void ChatRoom::OnSendChat() {
|
|||
}
|
||||
}
|
||||
|
||||
void ChatRoom::UpdateIconDisplay() {
|
||||
for (int row = 0; row < player_list->invisibleRootItem()->rowCount(); ++row) {
|
||||
QStandardItem* item = player_list->invisibleRootItem()->child(row);
|
||||
const std::string avatar_url =
|
||||
item->data(PlayerListItem::AvatarUrlRole).toString().toStdString();
|
||||
if (icon_cache.count(avatar_url)) {
|
||||
item->setData(icon_cache.at(avatar_url), Qt::DecorationRole);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatRoom::SetPlayerList(const Network::RoomMember::MemberList& member_list) {
|
||||
// TODO(B3N30): Remember which row is selected
|
||||
player_list->removeRows(0, player_list->rowCount());
|
||||
for (const auto& member : member_list) {
|
||||
if (member.nickname.empty())
|
||||
continue;
|
||||
QList<QStandardItem*> l;
|
||||
std::vector<std::string> elements = {member.nickname, member.game_info.name};
|
||||
for (const auto& item : elements) {
|
||||
QStandardItem* child = new QStandardItem(QString::fromStdString(item));
|
||||
child->setEditable(false);
|
||||
l.append(child);
|
||||
QStandardItem* name_item = new PlayerListItem(member.nickname, member.username,
|
||||
member.avatar_url, member.game_info.name);
|
||||
|
||||
if (!icon_cache.count(member.avatar_url)) {
|
||||
// Emplace a default question mark icon as avatar
|
||||
icon_cache.emplace(member.avatar_url, QIcon::fromTheme("no_avatar").pixmap(48));
|
||||
if (!member.avatar_url.empty()) {
|
||||
#ifdef ENABLE_WEB_SERVICE
|
||||
// Start a request to get the member's avatar
|
||||
const QUrl url(QString::fromStdString(member.avatar_url));
|
||||
QFuture<std::string> future = QtConcurrent::run([url] {
|
||||
WebService::Client client(
|
||||
QString("%1://%2").arg(url.scheme(), url.host()).toStdString(), "", "");
|
||||
auto result = client.GetImage(url.path().toStdString(), true);
|
||||
if (result.returned_data.empty()) {
|
||||
LOG_ERROR(WebService, "Failed to get avatar");
|
||||
}
|
||||
return result.returned_data;
|
||||
});
|
||||
auto* future_watcher = new QFutureWatcher<std::string>(this);
|
||||
connect(future_watcher, &QFutureWatcher<std::string>::finished, this,
|
||||
[this, future_watcher, avatar_url = member.avatar_url] {
|
||||
const std::string result = future_watcher->result();
|
||||
if (result.empty())
|
||||
return;
|
||||
QPixmap pixmap;
|
||||
if (!pixmap.loadFromData(reinterpret_cast<const u8*>(result.data()),
|
||||
result.size()))
|
||||
return;
|
||||
icon_cache[avatar_url] = pixmap.scaled(48, 48, Qt::IgnoreAspectRatio,
|
||||
Qt::SmoothTransformation);
|
||||
// Update all the displayed icons with the new icon_cache
|
||||
UpdateIconDisplay();
|
||||
});
|
||||
future_watcher->setFuture(future);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
player_list->invisibleRootItem()->appendRow(l);
|
||||
name_item->setData(icon_cache.at(member.avatar_url), Qt::DecorationRole);
|
||||
|
||||
player_list->invisibleRootItem()->appendRow(name_item);
|
||||
}
|
||||
// TODO(B3N30): Restore row selection
|
||||
}
|
||||
|
@ -230,7 +322,8 @@ void ChatRoom::PopupContextMenu(const QPoint& menu_location) {
|
|||
if (!item.isValid())
|
||||
return;
|
||||
|
||||
std::string nickname = player_list->item(item.row())->text().toStdString();
|
||||
std::string nickname =
|
||||
player_list->item(item.row())->data(PlayerListItem::NicknameRole).toString().toStdString();
|
||||
if (auto room = Network::GetRoomMember().lock()) {
|
||||
// You can't block yourself
|
||||
if (nickname == room->GetNickname())
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue