view user profile (notes+connections+badges)

change some stuff with callbacks
This commit is contained in:
ouwou
2021-02-04 23:41:53 -05:00
parent 0479bf52c2
commit 64adcffe42
40 changed files with 686 additions and 37 deletions

View 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 &note) {
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 &note) {
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 &note) {
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);
}

View 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 &note);
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
View 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
View 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;
};