view user profile (notes+connections+badges)
change some stuff with callbacks
@ -44,6 +44,8 @@ file(GLOB ABADDON_SOURCES
|
||||
"windows/*.cpp"
|
||||
"windows/guildsettings/*.hpp"
|
||||
"windows/guildsettings/*.cpp"
|
||||
"windows/profile/*.hpp"
|
||||
"windows/profile/*.cpp"
|
||||
"dialogs/*.hpp"
|
||||
"dialogs/*.cpp"
|
||||
)
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "dialogs/setstatus.hpp"
|
||||
#include "abaddon.hpp"
|
||||
#include "windows/guildsettingswindow.hpp"
|
||||
#include "windows/profilewindow.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#pragma comment(lib, "crypt32.lib")
|
||||
@ -84,6 +85,7 @@ int Abaddon::StartGTK() {
|
||||
m_user_menu_copy_id = Gtk::manage(new Gtk::MenuItem("Copy ID"));
|
||||
m_user_menu_open_dm = Gtk::manage(new Gtk::MenuItem("Open DM"));
|
||||
m_user_menu_roles = Gtk::manage(new Gtk::MenuItem("Roles"));
|
||||
m_user_menu_info = Gtk::manage(new Gtk::MenuItem("View Profile"));
|
||||
m_user_menu_roles_submenu = Gtk::manage(new Gtk::Menu);
|
||||
m_user_menu_roles->set_submenu(*m_user_menu_roles_submenu);
|
||||
m_user_menu_insert_mention->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_insert_mention));
|
||||
@ -91,6 +93,11 @@ int Abaddon::StartGTK() {
|
||||
m_user_menu_kick->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_kick));
|
||||
m_user_menu_copy_id->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_copy_id));
|
||||
m_user_menu_open_dm->signal_activate().connect(sigc::mem_fun(*this, &Abaddon::on_user_menu_open_dm));
|
||||
m_user_menu_info->signal_activate().connect([this]() {
|
||||
auto *window = new ProfileWindow(m_shown_user_menu_id);
|
||||
window->show();
|
||||
});
|
||||
m_user_menu->append(*m_user_menu_info);
|
||||
m_user_menu->append(*m_user_menu_insert_mention);
|
||||
m_user_menu->append(*m_user_menu_ban);
|
||||
m_user_menu->append(*m_user_menu_kick);
|
||||
@ -503,7 +510,7 @@ void Abaddon::ActionReactionRemove(Snowflake id, const Glib::ustring ¶m) {
|
||||
}
|
||||
|
||||
void Abaddon::ActionGuildSettings(Snowflake id) {
|
||||
auto *window = new GuildSettingsWindow(id);
|
||||
auto window = new GuildSettingsWindow(id);
|
||||
window->show();
|
||||
}
|
||||
|
||||
|
@ -83,6 +83,7 @@ protected:
|
||||
void ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id);
|
||||
|
||||
Gtk::Menu *m_user_menu;
|
||||
Gtk::MenuItem *m_user_menu_info;
|
||||
Gtk::MenuItem *m_user_menu_insert_mention;
|
||||
Gtk::MenuItem *m_user_menu_ban;
|
||||
Gtk::MenuItem *m_user_menu_kick;
|
||||
|
54
css/main.css
@ -233,3 +233,57 @@
|
||||
.status-indicator.idle {
|
||||
color: #FAA61A;
|
||||
}
|
||||
|
||||
.profile-main-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.profile-username {
|
||||
margin-left: 10px;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.profile-badge {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.profile-switcher {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.profile-connections {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.profile-connection {
|
||||
background: @secondary_color;
|
||||
border-radius: 15px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.profile-connection box {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.profile-stack {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.profile-notes-text, .profile-notes-text text {
|
||||
background: @secondary_color;
|
||||
}
|
||||
|
||||
.profile-notes-text text {
|
||||
border-radius: 5px;
|
||||
border: 1px solid #36515e;
|
||||
color: @text_color;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.profile-badges {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.app-window textview text {
|
||||
caret-color: #ababab;
|
||||
}
|
||||
|
@ -112,8 +112,6 @@ std::set<Snowflake> DiscordClient::GetMessagesForChannel(Snowflake id) const {
|
||||
}
|
||||
|
||||
void DiscordClient::FetchInvite(std::string code, sigc::slot<void(std::optional<InviteData>)> callback) {
|
||||
sigc::signal<void, std::optional<InviteData>> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakeGET("/invites/" + code + "?with_counts=true", [this, callback](http::response_type r) {
|
||||
if (!CheckCode(r)) {
|
||||
if (r.status_code == 404)
|
||||
@ -371,18 +369,16 @@ void DiscordClient::EditMessage(Snowflake channel_id, Snowflake id, std::string
|
||||
|
||||
void DiscordClient::SendLazyLoad(Snowflake id) {
|
||||
LazyLoadRequestMessage msg;
|
||||
std::unordered_map<Snowflake, std::vector<std::pair<int, int>>> c;
|
||||
c[id] = {
|
||||
msg.Channels.emplace();
|
||||
msg.Channels.value()[id] = {
|
||||
std::make_pair(0, 99),
|
||||
std::make_pair(100, 199)
|
||||
};
|
||||
msg.Channels = c;
|
||||
msg.GuildID = *GetChannel(id)->GuildID;
|
||||
msg.ShouldGetActivities = true;
|
||||
msg.ShouldGetTyping = true;
|
||||
|
||||
nlohmann::json j = msg;
|
||||
m_websocket.Send(j);
|
||||
m_websocket.Send(msg);
|
||||
}
|
||||
|
||||
void DiscordClient::JoinGuild(std::string code) {
|
||||
@ -477,11 +473,9 @@ void DiscordClient::SetGuildName(Snowflake id, const Glib::ustring &name) {
|
||||
void DiscordClient::SetGuildName(Snowflake id, const Glib::ustring &name, sigc::slot<void(bool success)> callback) {
|
||||
ModifyGuildObject obj;
|
||||
obj.Name = name;
|
||||
sigc::signal<void, bool> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, signal](const http::response_type &r) {
|
||||
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &r) {
|
||||
const auto success = r.status_code == 200;
|
||||
signal.emit(success);
|
||||
callback(success);
|
||||
});
|
||||
}
|
||||
|
||||
@ -492,11 +486,9 @@ void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data) {
|
||||
void DiscordClient::SetGuildIcon(Snowflake id, const std::string &data, sigc::slot<void(bool success)> callback) {
|
||||
ModifyGuildObject obj;
|
||||
obj.IconData = data;
|
||||
sigc::signal<void, bool> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, signal](const http::response_type &r) {
|
||||
m_http.MakePATCH("/guilds/" + std::to_string(id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &r) {
|
||||
const auto success = r.status_code == 200;
|
||||
signal.emit(success);
|
||||
callback(success);
|
||||
});
|
||||
}
|
||||
|
||||
@ -505,8 +497,6 @@ void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id) {
|
||||
}
|
||||
|
||||
void DiscordClient::UnbanUser(Snowflake guild_id, Snowflake user_id, sigc::slot<void(bool success)> callback) {
|
||||
sigc::signal<void, bool> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakeDELETE("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback](const http::response_type &response) {
|
||||
callback(response.status_code == 204);
|
||||
});
|
||||
@ -517,8 +507,6 @@ void DiscordClient::DeleteInvite(const std::string &code) {
|
||||
}
|
||||
|
||||
void DiscordClient::DeleteInvite(const std::string &code, sigc::slot<void(bool success)> callback) {
|
||||
sigc::signal<void, bool> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakeDELETE("/invites/" + code, [this, callback](const http::response_type &response) {
|
||||
callback(CheckCode(response));
|
||||
});
|
||||
@ -529,8 +517,6 @@ std::vector<BanData> DiscordClient::GetBansInGuild(Snowflake guild_id) {
|
||||
}
|
||||
|
||||
void DiscordClient::FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::slot<void(BanData)> callback) {
|
||||
sigc::signal<void, BanData> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans/" + std::to_string(user_id), [this, callback, guild_id](const http::response_type &response) {
|
||||
if (!CheckCode(response)) return;
|
||||
auto ban = nlohmann::json::parse(response.text).get<BanData>();
|
||||
@ -541,8 +527,6 @@ void DiscordClient::FetchGuildBan(Snowflake guild_id, Snowflake user_id, sigc::s
|
||||
}
|
||||
|
||||
void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vector<BanData>)> callback) {
|
||||
sigc::signal<void, std::vector<BanData>> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/bans", [this, callback, guild_id](const http::response_type &response) {
|
||||
if (!CheckCode(response)) return;
|
||||
auto bans = nlohmann::json::parse(response.text).get<std::vector<BanData>>();
|
||||
@ -557,8 +541,6 @@ void DiscordClient::FetchGuildBans(Snowflake guild_id, sigc::slot<void(std::vect
|
||||
}
|
||||
|
||||
void DiscordClient::FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::vector<InviteData>)> callback) {
|
||||
sigc::signal<void, std::vector<InviteData>> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/invites", [this, callback, guild_id](const http::response_type &response) {
|
||||
// store?
|
||||
if (!CheckCode(response)) return;
|
||||
@ -575,8 +557,6 @@ void DiscordClient::FetchGuildInvites(Snowflake guild_id, sigc::slot<void(std::v
|
||||
}
|
||||
|
||||
void DiscordClient::FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback) {
|
||||
sigc::signal<void, AuditLogData> signal;
|
||||
signal.connect(callback);
|
||||
m_http.MakeGET("/guilds/" + std::to_string(guild_id) + "/audit-logs", [this, callback](const http::response &response) {
|
||||
if (!CheckCode(response)) return;
|
||||
auto data = nlohmann::json::parse(response.text).get<AuditLogData>();
|
||||
@ -590,6 +570,35 @@ void DiscordClient::FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogDa
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback) {
|
||||
m_http.MakeGET("/users/" + std::to_string(user_id) + "/profile", [this, callback](const http::response_type &response) {
|
||||
if (!CheckCode(response)) return;
|
||||
callback(nlohmann::json::parse(response.text).get<UserProfileData>());
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::FetchUserNote(Snowflake user_id, sigc::slot<void(std::string note)> callback) {
|
||||
m_http.MakeGET("/users/@me/notes/" + std::to_string(user_id), [this, callback](const http::response_type &response) {
|
||||
if (response.status_code == 404) return;
|
||||
if (!CheckCode(response)) return;
|
||||
const auto note = nlohmann::json::parse(response.text).get<UserNoteObject>().Note;
|
||||
if (note.has_value())
|
||||
callback(*note);
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::SetUserNote(Snowflake user_id, std::string note) {
|
||||
SetUserNote(user_id, note, [](auto) {});
|
||||
}
|
||||
|
||||
void DiscordClient::SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(bool success)> callback) {
|
||||
UserSetNoteObject obj;
|
||||
obj.Note = note;
|
||||
m_http.MakePUT("/users/@me/notes/" + std::to_string(user_id), nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) {
|
||||
callback(response.status_code == 204);
|
||||
});
|
||||
}
|
||||
|
||||
void DiscordClient::UpdateToken(std::string token) {
|
||||
if (!IsStarted()) {
|
||||
m_token = token;
|
||||
@ -769,6 +778,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) {
|
||||
case GatewayEvent::INVITE_DELETE: {
|
||||
HandleGatewayInviteDelete(m);
|
||||
} break;
|
||||
case GatewayEvent::USER_NOTE_UPDATE: {
|
||||
HandleGatewayUserNoteUpdate(m);
|
||||
} break;
|
||||
}
|
||||
} break;
|
||||
default:
|
||||
@ -1147,6 +1159,11 @@ void DiscordClient::HandleGatewayInviteDelete(const GatewayMessage &msg) {
|
||||
m_signal_invite_delete.emit(data);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayUserNoteUpdate(const GatewayMessage &msg) {
|
||||
UserNoteUpdateMessage data = msg.Data;
|
||||
m_signal_note_update.emit(data.ID, data.Note);
|
||||
}
|
||||
|
||||
void DiscordClient::HandleGatewayReconnect(const GatewayMessage &msg) {
|
||||
printf("received reconnect\n");
|
||||
m_signal_disconnected.emit(true, GatewayCloseCode::Reconnecting);
|
||||
@ -1427,6 +1444,7 @@ void DiscordClient::LoadEventMap() {
|
||||
m_event_map["GUILD_BAN_ADD"] = GatewayEvent::GUILD_BAN_ADD;
|
||||
m_event_map["INVITE_CREATE"] = GatewayEvent::INVITE_CREATE;
|
||||
m_event_map["INVITE_DELETE"] = GatewayEvent::INVITE_DELETE;
|
||||
m_event_map["USER_NOTE_UPDATE"] = GatewayEvent::USER_NOTE_UPDATE;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() {
|
||||
@ -1528,3 +1546,7 @@ DiscordClient::type_signal_invite_delete DiscordClient::signal_invite_delete() {
|
||||
DiscordClient::type_signal_presence_update DiscordClient::signal_presence_update() {
|
||||
return m_signal_presence_update;
|
||||
}
|
||||
|
||||
DiscordClient::type_signal_note_update DiscordClient::signal_note_update() {
|
||||
return m_signal_note_update;
|
||||
}
|
||||
|
@ -130,6 +130,11 @@ public:
|
||||
|
||||
void FetchAuditLog(Snowflake guild_id, sigc::slot<void(AuditLogData)> callback);
|
||||
|
||||
void FetchUserProfile(Snowflake user_id, sigc::slot<void(UserProfileData)> callback);
|
||||
void FetchUserNote(Snowflake user_id, sigc::slot<void(std::string note)> callback);
|
||||
void SetUserNote(Snowflake user_id, std::string note);
|
||||
void SetUserNote(Snowflake user_id, std::string note, sigc::slot<void(bool success)> callback);
|
||||
|
||||
void UpdateToken(std::string token);
|
||||
void SetUserAgent(std::string agent);
|
||||
|
||||
@ -172,6 +177,7 @@ private:
|
||||
void HandleGatewayGuildBanAdd(const GatewayMessage &msg);
|
||||
void HandleGatewayInviteCreate(const GatewayMessage &msg);
|
||||
void HandleGatewayInviteDelete(const GatewayMessage &msg);
|
||||
void HandleGatewayUserNoteUpdate(const GatewayMessage &msg);
|
||||
void HandleGatewayReconnect(const GatewayMessage &msg);
|
||||
void HandleGatewayInvalidSession(const GatewayMessage &msg);
|
||||
void HeartbeatThread();
|
||||
@ -252,6 +258,7 @@ public:
|
||||
typedef sigc::signal<void, InviteData> type_signal_invite_create;
|
||||
typedef sigc::signal<void, InviteDeleteObject> type_signal_invite_delete;
|
||||
typedef sigc::signal<void, Snowflake, PresenceStatus> type_signal_presence_update;
|
||||
typedef sigc::signal<void, Snowflake, std::string> type_signal_note_update;
|
||||
typedef sigc::signal<void, bool, GatewayCloseCode> type_signal_disconnected; // bool true if reconnecting
|
||||
typedef sigc::signal<void> type_signal_connected;
|
||||
|
||||
@ -278,6 +285,7 @@ public:
|
||||
type_signal_invite_create signal_invite_create();
|
||||
type_signal_invite_delete signal_invite_delete(); // safe to assume guild id is set
|
||||
type_signal_presence_update signal_presence_update();
|
||||
type_signal_note_update signal_note_update();
|
||||
type_signal_disconnected signal_disconnected();
|
||||
type_signal_connected signal_connected();
|
||||
|
||||
@ -305,6 +313,7 @@ protected:
|
||||
type_signal_invite_create m_signal_invite_create;
|
||||
type_signal_invite_delete m_signal_invite_delete;
|
||||
type_signal_presence_update m_signal_presence_update;
|
||||
type_signal_note_update m_signal_note_update;
|
||||
type_signal_disconnected m_signal_disconnected;
|
||||
type_signal_connected m_signal_connected;
|
||||
};
|
||||
|
@ -74,14 +74,15 @@ void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m) {
|
||||
j["op"] = GatewayOp::LazyLoadRequest;
|
||||
j["d"] = nlohmann::json::object();
|
||||
j["d"]["guild_id"] = m.GuildID;
|
||||
j["d"]["channels"] = nlohmann::json::object();
|
||||
for (const auto &[key, chans] : m.Channels) { // apparently a map gets written as a list
|
||||
j["d"]["channels"][std::to_string(key)] = chans;
|
||||
if (m.Channels.has_value()) {
|
||||
j["d"]["channels"] = nlohmann::json::object();
|
||||
for (const auto &[key, chans] : *m.Channels)
|
||||
j["d"]["channels"][std::to_string(key)] = chans;
|
||||
}
|
||||
j["d"]["typing"] = m.ShouldGetTyping;
|
||||
j["d"]["activities"] = m.ShouldGetActivities;
|
||||
if (m.Members.size() > 0)
|
||||
j["d"]["members"] = m.Members;
|
||||
if (m.Members.has_value())
|
||||
j["d"]["members"] = *m.Members;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, const UpdateStatusMessage &m) {
|
||||
@ -301,3 +302,38 @@ void from_json(const nlohmann::json &j, InviteDeleteObject &m) {
|
||||
JS_O("guild_id", m.GuildID);
|
||||
JS_D("code", m.Code);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, ConnectionData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_D("type", m.Type);
|
||||
JS_D("name", m.Name);
|
||||
JS_D("verified", m.IsVerified);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, MutualGuildData &m) {
|
||||
JS_D("id", m.ID);
|
||||
JS_ON("nick", m.Nick);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserProfileData &m) {
|
||||
JS_D("connected_accounts", m.ConnectedAccounts);
|
||||
JS_D("mutual_guilds", m.MutualGuilds);
|
||||
JS_ON("premium_guild_since", m.PremiumGuildSince);
|
||||
JS_ON("premium_since", m.PremiumSince);
|
||||
JS_D("user", m.User);
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserNoteObject &m) {
|
||||
JS_ON("note", m.Note);
|
||||
JS_ON("note_user_id", m.NoteUserID);
|
||||
JS_ON("user_id", m.UserID);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json &j, UserSetNoteObject &m) {
|
||||
j["note"] = m.Note;
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json &j, UserNoteUpdateMessage &m) {
|
||||
JS_D("note", m.Note);
|
||||
JS_D("id", m.ID);
|
||||
}
|
||||
|
@ -61,6 +61,7 @@ enum class GatewayEvent : int {
|
||||
GUILD_BAN_ADD,
|
||||
INVITE_CREATE,
|
||||
INVITE_DELETE,
|
||||
USER_NOTE_UPDATE,
|
||||
};
|
||||
|
||||
enum class GatewayCloseCode : uint16_t {
|
||||
@ -170,8 +171,8 @@ struct LazyLoadRequestMessage {
|
||||
Snowflake GuildID;
|
||||
bool ShouldGetTyping = false;
|
||||
bool ShouldGetActivities = false;
|
||||
std::vector<std::string> Members; // snowflake?
|
||||
std::unordered_map<Snowflake, std::vector<std::pair<int, int>>> Channels; // channel ID -> range of sidebar
|
||||
std::optional<std::vector<std::string>> Members; // snowflake?
|
||||
std::optional<std::unordered_map<Snowflake, std::vector<std::pair<int, int>>>> Channels; // channel ID -> range of sidebar
|
||||
|
||||
friend void to_json(nlohmann::json &j, const LazyLoadRequestMessage &m);
|
||||
};
|
||||
@ -424,3 +425,51 @@ struct InviteDeleteObject {
|
||||
|
||||
friend void from_json(const nlohmann::json &j, InviteDeleteObject &m);
|
||||
};
|
||||
|
||||
struct ConnectionData {
|
||||
std::string ID;
|
||||
std::string Type;
|
||||
std::string Name;
|
||||
bool IsVerified;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, ConnectionData &m);
|
||||
};
|
||||
|
||||
struct MutualGuildData {
|
||||
Snowflake ID;
|
||||
std::optional<std::string> Nick; // null
|
||||
|
||||
friend void from_json(const nlohmann::json &j, MutualGuildData &m);
|
||||
};
|
||||
|
||||
struct UserProfileData {
|
||||
std::vector<ConnectionData> ConnectedAccounts;
|
||||
std::vector<MutualGuildData> MutualGuilds;
|
||||
std::optional<std::string> PremiumGuildSince; // null
|
||||
std::optional<std::string> PremiumSince; // null
|
||||
UserData User;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserProfileData &m);
|
||||
};
|
||||
|
||||
struct UserNoteObject {
|
||||
// idk if these can be null or missing but i play it safe
|
||||
std::optional<std::string> Note;
|
||||
std::optional<Snowflake> NoteUserID;
|
||||
std::optional<Snowflake> UserID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserNoteObject &m);
|
||||
};
|
||||
|
||||
struct UserSetNoteObject {
|
||||
std::string Note;
|
||||
|
||||
friend void to_json(nlohmann::json &j, UserSetNoteObject &m);
|
||||
};
|
||||
|
||||
struct UserNoteUpdateMessage {
|
||||
std::string Note;
|
||||
Snowflake ID;
|
||||
|
||||
friend void from_json(const nlohmann::json &j, UserNoteUpdateMessage &m);
|
||||
};
|
||||
|
@ -86,3 +86,69 @@ void UserData::update_from_json(const nlohmann::json &j) {
|
||||
JS_RD("nsfw_allowed", IsNSFWAllowed);
|
||||
JS_RD("phone", Phone);
|
||||
}
|
||||
|
||||
const char *UserData::GetFlagName(uint64_t flag) {
|
||||
switch (flag) {
|
||||
case DiscordEmployee:
|
||||
return "discordstaff";
|
||||
case PartneredServerOwner:
|
||||
return "partneredowner";
|
||||
case HypeSquadEvents:
|
||||
return "hypesquadevents";
|
||||
case BugHunterLevel1:
|
||||
return "discordbughunter";
|
||||
case HouseBravery:
|
||||
return "hypesquadbravery";
|
||||
case HouseBrilliance:
|
||||
return "hypesquadbrilliance";
|
||||
case HouseBalance:
|
||||
return "hypesquadbalance";
|
||||
case EarlySupporter:
|
||||
return "earlysupporter";
|
||||
case TeamUser:
|
||||
return "teamuser";
|
||||
case System:
|
||||
return "system";
|
||||
case BugHunterLevel2:
|
||||
return "discordbughunter2";
|
||||
case VerifiedBot:
|
||||
return "verifiedbot";
|
||||
case EarlyVerifiedBotDeveloper:
|
||||
return "earlyverifiedbotdeveloper";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const char *UserData::GetFlagReadableName(uint64_t flag) {
|
||||
switch (flag) {
|
||||
case DiscordEmployee:
|
||||
return "Discord Staff";
|
||||
case PartneredServerOwner:
|
||||
return "Partnered Server Owner";
|
||||
case HypeSquadEvents:
|
||||
return "HypeSquad Events";
|
||||
case BugHunterLevel1:
|
||||
return "Discord Bug Hunter";
|
||||
case HouseBravery:
|
||||
return "HypeSquad Bravery";
|
||||
case HouseBrilliance:
|
||||
return "HypeSquad Brilliance";
|
||||
case HouseBalance:
|
||||
return "HypeSquad Balance";
|
||||
case EarlySupporter:
|
||||
return "Early Supporter";
|
||||
case TeamUser:
|
||||
return "Team User"; // ???
|
||||
case System:
|
||||
return "System";
|
||||
case BugHunterLevel2:
|
||||
return "Discord Bug Hunter Level 2";
|
||||
case VerifiedBot:
|
||||
return "Verified Bot";
|
||||
case EarlyVerifiedBotDeveloper:
|
||||
return "Early Verified Bot Developer";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,27 @@
|
||||
#include <string>
|
||||
|
||||
struct UserData {
|
||||
enum {
|
||||
DiscordEmployee = 1 << 0,
|
||||
PartneredServerOwner = 1 << 1,
|
||||
HypeSquadEvents = 1 << 2,
|
||||
BugHunterLevel1 = 1 << 3,
|
||||
HouseBravery = 1 << 6,
|
||||
HouseBrilliance = 1 << 7,
|
||||
HouseBalance = 1 << 8,
|
||||
EarlySupporter = 1 << 9,
|
||||
TeamUser = 1 << 10, // no idea what this is
|
||||
System = 1 << 12,
|
||||
BugHunterLevel2 = 1 << 14,
|
||||
VerifiedBot = 1 << 16,
|
||||
EarlyVerifiedBotDeveloper = 1 << 17,
|
||||
|
||||
MaxFlag = EarlyVerifiedBotDeveloper,
|
||||
};
|
||||
|
||||
static const char *GetFlagName(uint64_t flag);
|
||||
static const char *GetFlagReadableName(uint64_t flag);
|
||||
|
||||
Snowflake ID;
|
||||
std::string Username;
|
||||
std::string Discriminator;
|
||||
@ -14,9 +35,9 @@ struct UserData {
|
||||
std::optional<std::string> Locale;
|
||||
std::optional<bool> IsVerified;
|
||||
std::optional<std::string> Email; // null
|
||||
std::optional<int> Flags;
|
||||
std::optional<uint64_t> Flags;
|
||||
std::optional<int> PremiumType; // null
|
||||
std::optional<int> PublicFlags;
|
||||
std::optional<uint64_t> PublicFlags;
|
||||
|
||||
// undocumented (opt)
|
||||
std::optional<bool> IsDesktop;
|
||||
|
BIN
res/battlenet.png
Normal file
After Width: | Height: | Size: 7.9 KiB |
BIN
res/discordbughunter.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
res/discordbughunter2.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
res/discordstaff.png
Normal file
After Width: | Height: | Size: 8.2 KiB |
BIN
res/earlysupporter.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
res/earlyverifiedbotdeveloper.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
res/facebook.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
BIN
res/github.png
Normal file
After Width: | Height: | Size: 6.5 KiB |
BIN
res/guildsubscriber.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
res/hypesquadbalance.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
BIN
res/hypesquadbravery.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
res/hypesquadbrilliance.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
res/hypesquadevents.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
res/leagueoflegends.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
res/partneredowner.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
res/premium.png
Normal file
After Width: | Height: | Size: 7.0 KiB |
BIN
res/reddit.png
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
res/skype.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
res/spotify.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
res/steam.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
BIN
res/twitch.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/twitter.png
Normal file
After Width: | Height: | Size: 3.6 KiB |
BIN
res/xbox.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
res/youtube.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
9
util.cpp
@ -182,3 +182,12 @@ bool IsURLViewableImage(const std::string &url) {
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void AddPointerCursor(Gtk::Widget &widget) {
|
||||
widget.signal_realize().connect([&widget]() {
|
||||
auto window = widget.get_window();
|
||||
auto display = window->get_display();
|
||||
auto cursor = Gdk::Cursor::create(display, "pointer");
|
||||
window->set_cursor(cursor);
|
||||
});
|
||||
}
|
||||
|
1
util.hpp
@ -46,6 +46,7 @@ bool IsURLViewableImage(const std::string &url);
|
||||
std::vector<uint8_t> ReadWholeFile(std::string path);
|
||||
std::string HumanReadableBytes(uint64_t bytes);
|
||||
std::string FormatISO8601(const std::string &in, int extra_offset = 0, const std::string &fmt = "%x %X");
|
||||
void AddPointerCursor(Gtk::Widget &widget);
|
||||
|
||||
template<typename T>
|
||||
struct Bitwise {
|
||||
|
179
windows/profile/userinfopane.cpp
Normal file
@ -0,0 +1,179 @@
|
||||
#include "userinfopane.hpp"
|
||||
#include <unordered_set>
|
||||
#include "../../abaddon.hpp"
|
||||
|
||||
ConnectionsContainer::ConnectionsContainer() {
|
||||
get_style_context()->add_class("profile-connections");
|
||||
set_column_homogeneous(true);
|
||||
set_row_spacing(10);
|
||||
set_column_spacing(10);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void ConnectionsContainer::SetConnections(const std::vector<ConnectionData> &connections) {
|
||||
for (auto child : get_children())
|
||||
delete child;
|
||||
|
||||
static const std::unordered_set<std::string> supported_services = {
|
||||
"battlenet",
|
||||
"github",
|
||||
"leagueoflegends",
|
||||
"reddit",
|
||||
"skype",
|
||||
"spotify",
|
||||
"steam",
|
||||
"twitch",
|
||||
"twitter",
|
||||
"xbox",
|
||||
"youtube",
|
||||
"facebook"
|
||||
};
|
||||
|
||||
for (int i = 0; i < connections.size(); i++) {
|
||||
const auto &conn = connections[i];
|
||||
if (supported_services.find(conn.Type) == supported_services.end()) continue;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
try {
|
||||
pixbuf = Gdk::Pixbuf::create_from_file("./res/" + conn.Type + ".png", 32, 32);
|
||||
} catch (const Glib::Exception &e) {}
|
||||
std::string url;
|
||||
if (conn.Type == "github")
|
||||
url = "https://github.com/" + conn.Name;
|
||||
else if (conn.Type == "steam")
|
||||
url = "https://steamcommunity.com/profiles/" + conn.ID;
|
||||
else if (conn.Type == "twitch")
|
||||
url = "https://twitch.tv/" + conn.Name;
|
||||
else if (conn.Type == "twitter")
|
||||
url = "https://twitter.com/i/user/" + conn.ID;
|
||||
else if (conn.Type == "spotify")
|
||||
url = "https://open.spotify.com/user/" + conn.ID;
|
||||
else if (conn.Type == "reddit")
|
||||
url = "https://reddit.com/u/" + conn.Name;
|
||||
else if (conn.Type == "youtube")
|
||||
url = "https://www.youtube.com/channel/" + conn.ID;
|
||||
else if (conn.Type == "facebook")
|
||||
url = "https://www.facebook.com/" + conn.ID;
|
||||
auto *ev = Gtk::manage(new Gtk::EventBox);
|
||||
auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
|
||||
if (pixbuf) {
|
||||
auto *img = Gtk::manage(new Gtk::Image(pixbuf));
|
||||
img->get_style_context()->add_class("profile-connection-image");
|
||||
box->add(*img);
|
||||
}
|
||||
auto *lbl = Gtk::manage(new Gtk::Label(conn.Name));
|
||||
box->set_halign(Gtk::ALIGN_START);
|
||||
box->set_size_request(200, -1);
|
||||
box->get_style_context()->add_class("profile-connection");
|
||||
lbl->get_style_context()->add_class("profile-connection-label");
|
||||
lbl->set_valign(Gtk::ALIGN_CENTER);
|
||||
lbl->set_single_line_mode(true);
|
||||
lbl->set_ellipsize(Pango::ELLIPSIZE_END);
|
||||
box->add(*lbl);
|
||||
if (url != "") {
|
||||
auto cb = [this, url](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY) {
|
||||
LaunchBrowser(url);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
ev->signal_button_press_event().connect(sigc::track_obj(cb, *ev));
|
||||
AddPointerCursor(*ev);
|
||||
}
|
||||
ev->add(*box);
|
||||
ev->show_all();
|
||||
attach(*ev, i % 2, i / 2, 1, 1);
|
||||
}
|
||||
|
||||
set_halign(Gtk::ALIGN_FILL);
|
||||
set_hexpand(true);
|
||||
}
|
||||
|
||||
NotesContainer::NotesContainer()
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL) {
|
||||
get_style_context()->add_class("profile-notes");
|
||||
m_label.get_style_context()->add_class("profile-notes-label");
|
||||
m_note.get_style_context()->add_class("profile-notes-text");
|
||||
|
||||
m_label.set_markup("<b>NOTE</b>");
|
||||
m_label.set_halign(Gtk::ALIGN_START);
|
||||
|
||||
m_note.set_wrap_mode(Gtk::WRAP_WORD_CHAR);
|
||||
m_note.signal_key_press_event().connect(sigc::mem_fun(*this, &NotesContainer::OnNoteKeyPress), false);
|
||||
|
||||
add(m_label);
|
||||
add(m_note);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void NotesContainer::SetNote(const std::string ¬e) {
|
||||
m_note.get_buffer()->set_text(note);
|
||||
}
|
||||
|
||||
void NotesContainer::UpdateNote() {
|
||||
auto text = m_note.get_buffer()->get_text();
|
||||
if (text.size() > 256)
|
||||
text = text.substr(0, 256);
|
||||
m_signal_update_note.emit(text);
|
||||
}
|
||||
|
||||
bool NotesContainer::OnNoteKeyPress(GdkEventKey *event) {
|
||||
if (event->type != GDK_KEY_PRESS) return false;
|
||||
const auto text = m_note.get_buffer()->get_text();
|
||||
if (event->keyval == GDK_KEY_Return) {
|
||||
if (event->state & GDK_SHIFT_MASK) {
|
||||
int newlines = 0;
|
||||
for (const auto c : text)
|
||||
if (c == '\n') newlines++;
|
||||
return newlines >= 5;
|
||||
} else {
|
||||
UpdateNote();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
NotesContainer::type_signal_update_note NotesContainer::signal_update_note() {
|
||||
return m_signal_update_note;
|
||||
}
|
||||
|
||||
ProfileUserInfoPane::ProfileUserInfoPane(Snowflake ID)
|
||||
: Gtk::Box(Gtk::ORIENTATION_VERTICAL)
|
||||
, UserID(ID) {
|
||||
get_style_context()->add_class("profile-info-pane");
|
||||
|
||||
m_note.signal_update_note().connect([this](const Glib::ustring ¬e) {
|
||||
auto cb = [this](bool success) {
|
||||
if (!success) {
|
||||
Gtk::MessageDialog dlg("Failed to set note", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true);
|
||||
dlg.set_position(Gtk::WIN_POS_CENTER);
|
||||
dlg.run();
|
||||
}
|
||||
};
|
||||
Abaddon::Get().GetDiscordClient().SetUserNote(UserID, note, sigc::track_obj(cb, *this));
|
||||
});
|
||||
|
||||
auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
discord.signal_note_update().connect([this](Snowflake id, std::string note) {
|
||||
if (id == UserID)
|
||||
m_note.SetNote(note);
|
||||
});
|
||||
|
||||
auto fetch_note_cb = [this](const std::string ¬e) {
|
||||
m_note.SetNote(note);
|
||||
};
|
||||
discord.FetchUserNote(UserID, sigc::track_obj(fetch_note_cb, *this));
|
||||
|
||||
m_conns.set_halign(Gtk::ALIGN_START);
|
||||
m_conns.set_hexpand(true);
|
||||
|
||||
add(m_note);
|
||||
add(m_conns);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void ProfileUserInfoPane::SetConnections(const std::vector<ConnectionData> &connections) {
|
||||
m_conns.SetConnections(connections);
|
||||
}
|
40
windows/profile/userinfopane.hpp
Normal file
@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "../../discord/objects.hpp"
|
||||
|
||||
class ConnectionsContainer : public Gtk::Grid {
|
||||
public:
|
||||
ConnectionsContainer();
|
||||
void SetConnections(const std::vector<ConnectionData> &connections);
|
||||
};
|
||||
|
||||
class NotesContainer : public Gtk::Box {
|
||||
public:
|
||||
NotesContainer();
|
||||
void SetNote(const std::string ¬e);
|
||||
|
||||
private:
|
||||
void UpdateNote();
|
||||
bool OnNoteKeyPress(GdkEventKey *event);
|
||||
|
||||
Gtk::Label m_label;
|
||||
Gtk::TextView m_note;
|
||||
|
||||
typedef sigc::signal<void, Glib::ustring> type_signal_update_note;
|
||||
type_signal_update_note m_signal_update_note;
|
||||
|
||||
public:
|
||||
type_signal_update_note signal_update_note();
|
||||
};
|
||||
|
||||
class ProfileUserInfoPane : public Gtk::Box {
|
||||
public:
|
||||
ProfileUserInfoPane(Snowflake ID);
|
||||
void SetConnections(const std::vector<ConnectionData> &connections);
|
||||
|
||||
Snowflake UserID;
|
||||
|
||||
private:
|
||||
NotesContainer m_note;
|
||||
ConnectionsContainer m_conns;
|
||||
};
|
124
windows/profilewindow.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include "profilewindow.hpp"
|
||||
#include "../abaddon.hpp"
|
||||
|
||||
ProfileWindow::ProfileWindow(Snowflake user_id)
|
||||
: ID(user_id)
|
||||
, m_main(Gtk::ORIENTATION_VERTICAL)
|
||||
, m_upper(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_badges(Gtk::ORIENTATION_HORIZONTAL)
|
||||
, m_pane_info(user_id) {
|
||||
const auto &discord = Abaddon::Get().GetDiscordClient();
|
||||
auto user = *discord.GetUser(ID);
|
||||
|
||||
Abaddon::Get().GetDiscordClient().FetchUserProfile(user_id, sigc::mem_fun(*this, &ProfileWindow::OnFetchProfile));
|
||||
|
||||
set_name("user-profile");
|
||||
set_default_size(450, 375);
|
||||
set_title(user.Username + "#" + user.Discriminator);
|
||||
set_position(Gtk::WIN_POS_CENTER);
|
||||
get_style_context()->add_class("app-window");
|
||||
get_style_context()->add_class("app-popup");
|
||||
get_style_context()->add_class("user-profile-window");
|
||||
m_main.get_style_context()->add_class("profile-main-container");
|
||||
m_avatar.get_style_context()->add_class("profile-avatar");
|
||||
m_username.get_style_context()->add_class("profile-username");
|
||||
m_switcher.get_style_context()->add_class("profile-switcher");
|
||||
m_stack.get_style_context()->add_class("profile-stack");
|
||||
m_badges.get_style_context()->add_class("profile-badges");
|
||||
|
||||
m_scroll.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
|
||||
m_scroll.set_vexpand(true);
|
||||
m_scroll.set_propagate_natural_height(true);
|
||||
|
||||
if (user.HasAvatar())
|
||||
AddPointerCursor(m_avatar_ev);
|
||||
m_avatar_ev.signal_button_press_event().connect([this, user](GdkEventButton *event) -> bool {
|
||||
if (event->type == GDK_BUTTON_PRESS && event->button == GDK_BUTTON_PRIMARY)
|
||||
if (user.HasAnimatedAvatar())
|
||||
LaunchBrowser(user.GetAvatarURL("gif", "512"));
|
||||
else
|
||||
LaunchBrowser(user.GetAvatarURL("png", "512"));
|
||||
return false;
|
||||
});
|
||||
|
||||
auto &img = Abaddon::Get().GetImageManager();
|
||||
m_avatar.property_pixbuf() = img.GetPlaceholder(64);
|
||||
if (user.HasAvatar()) {
|
||||
auto icon_cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
set_icon(pb);
|
||||
};
|
||||
img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(icon_cb, *this));
|
||||
|
||||
if (user.HasAnimatedAvatar()) {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::PixbufAnimation> &pb) {
|
||||
m_avatar.property_pixbuf_animation() = pb;
|
||||
};
|
||||
img.LoadAnimationFromURL(user.GetAvatarURL("gif", "64"), 64, 64, sigc::track_obj(cb, *this));
|
||||
} else {
|
||||
auto cb = [this](const Glib::RefPtr<Gdk::Pixbuf> &pb) {
|
||||
m_avatar.property_pixbuf() = pb->scale_simple(64, 64, Gdk::INTERP_BILINEAR);
|
||||
};
|
||||
img.LoadFromURL(user.GetAvatarURL("png", "64"), sigc::track_obj(cb, *this));
|
||||
}
|
||||
}
|
||||
|
||||
m_username.set_markup(user.GetEscapedString());
|
||||
|
||||
m_switcher.set_stack(m_stack);
|
||||
m_switcher.set_halign(Gtk::ALIGN_START);
|
||||
m_switcher.set_hexpand(true);
|
||||
|
||||
m_stack.add(m_pane_info, "info", "User Info");
|
||||
|
||||
m_badges.set_valign(Gtk::ALIGN_CENTER);
|
||||
m_badges_scroll.set_hexpand(true);
|
||||
m_badges_scroll.set_propagate_natural_width(true);
|
||||
m_badges_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_NEVER);
|
||||
|
||||
m_upper.set_halign(Gtk::ALIGN_START);
|
||||
m_avatar.set_halign(Gtk::ALIGN_START);
|
||||
m_username.set_halign(Gtk::ALIGN_START);
|
||||
m_avatar_ev.add(m_avatar);
|
||||
m_upper.add(m_avatar_ev);
|
||||
m_upper.add(m_username);
|
||||
m_badges_scroll.add(m_badges);
|
||||
m_upper.add(m_badges_scroll);
|
||||
m_main.add(m_upper);
|
||||
m_main.add(m_switcher);
|
||||
m_scroll.add(m_stack);
|
||||
m_main.add(m_scroll);
|
||||
add(m_main);
|
||||
show_all_children();
|
||||
}
|
||||
|
||||
void ProfileWindow::on_hide() {
|
||||
Gtk::Window::on_hide();
|
||||
delete this;
|
||||
}
|
||||
|
||||
void ProfileWindow::OnFetchProfile(const UserProfileData &data) {
|
||||
m_pane_info.SetConnections(data.ConnectedAccounts);
|
||||
|
||||
for (auto child : m_badges.get_children())
|
||||
delete child;
|
||||
|
||||
if (!data.User.PublicFlags.has_value()) return;
|
||||
const auto x = *data.User.PublicFlags;
|
||||
for (uint64_t i = 1; i <= UserData::MaxFlag; i <<= 1) {
|
||||
if (!(x & i)) continue;
|
||||
const std::string name = UserData::GetFlagName(i);
|
||||
if (name == "unknown") continue;
|
||||
Glib::RefPtr<Gdk::Pixbuf> pixbuf;
|
||||
try {
|
||||
pixbuf = Gdk::Pixbuf::create_from_file("./res/" + name + ".png", 24, 24);
|
||||
} catch (const Glib::Exception &e) {
|
||||
pixbuf = Abaddon::Get().GetImageManager().GetPlaceholder(24);
|
||||
}
|
||||
if (!pixbuf) continue;
|
||||
auto *image = Gtk::manage(new Gtk::Image(pixbuf));
|
||||
image->get_style_context()->add_class("profile-badge");
|
||||
image->set_tooltip_text(UserData::GetFlagReadableName(i));
|
||||
image->show();
|
||||
m_badges.add(*image);
|
||||
}
|
||||
}
|
29
windows/profilewindow.hpp
Normal file
@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
#include <gtkmm.h>
|
||||
#include "../discord/snowflake.hpp"
|
||||
#include "profile/userinfopane.hpp"
|
||||
|
||||
class ProfileWindow : public Gtk::Window {
|
||||
public:
|
||||
ProfileWindow(Snowflake user_id);
|
||||
|
||||
void on_hide() override;
|
||||
|
||||
Snowflake ID;
|
||||
|
||||
private:
|
||||
void OnFetchProfile(const UserProfileData &data);
|
||||
|
||||
Gtk::Box m_main;
|
||||
Gtk::Box m_upper;
|
||||
Gtk::Box m_badges;
|
||||
Gtk::ScrolledWindow m_badges_scroll;
|
||||
Gtk::EventBox m_avatar_ev;
|
||||
Gtk::Image m_avatar;
|
||||
Gtk::Label m_username;
|
||||
Gtk::ScrolledWindow m_scroll;
|
||||
Gtk::Stack m_stack;
|
||||
Gtk::StackSwitcher m_switcher;
|
||||
|
||||
ProfileUserInfoPane m_pane_info;
|
||||
};
|