diff options
-rw-r--r-- | abaddon.hpp | 4 | ||||
-rw-r--r-- | components/friendslist.cpp | 353 | ||||
-rw-r--r-- | components/friendslist.hpp | 92 | ||||
-rw-r--r-- | css/main.css | 9 | ||||
-rw-r--r-- | discord/activity.hpp | 14 | ||||
-rw-r--r-- | discord/discord.cpp | 46 | ||||
-rw-r--r-- | discord/discord.hpp | 7 | ||||
-rw-r--r-- | discord/errors.hpp | 36 | ||||
-rw-r--r-- | discord/objects.cpp | 9 | ||||
-rw-r--r-- | discord/objects.hpp | 14 | ||||
-rw-r--r-- | windows/mainwindow.cpp | 24 | ||||
-rw-r--r-- | windows/mainwindow.hpp | 7 |
12 files changed, 612 insertions, 3 deletions
diff --git a/abaddon.hpp b/abaddon.hpp index 6bcb385..c8c4351 100644 --- a/abaddon.hpp +++ b/abaddon.hpp @@ -84,13 +84,13 @@ public: void ShowUserMenu(const GdkEvent *event, Snowflake id, Snowflake guild_id); + void ManageHeapWindow(Gtk::Window *window); + protected: void ShowGuildVerificationGateDialog(Snowflake guild_id); void SetupUserMenu(); - void ManageHeapWindow(Gtk::Window *window); - Snowflake m_shown_user_menu_id; Snowflake m_shown_user_menu_guild_id; diff --git a/components/friendslist.cpp b/components/friendslist.cpp new file mode 100644 index 0000000..7d9fc21 --- /dev/null +++ b/components/friendslist.cpp @@ -0,0 +1,353 @@ +#include "friendslist.hpp" +#include "../abaddon.hpp" +#include "lazyimage.hpp" + +using namespace std::string_literals; + +FriendsList::FriendsList() + : Gtk::Box(Gtk::ORIENTATION_VERTICAL) + , m_filter_mode(FILTER_FRIENDS) { + get_style_context()->add_class("friends-list"); + + auto &discord = Abaddon::Get().GetDiscordClient(); + + discord.signal_relationship_add().connect(sigc::mem_fun(*this, &FriendsList::OnRelationshipAdd)); + discord.signal_relationship_remove().connect(sigc::mem_fun(*this, &FriendsList::OnRelationshipRemove)); + + PopulateRelationships(); + signal_map().connect(sigc::mem_fun(*this, &FriendsList::PopulateRelationships)); + + constexpr static std::array<const char *, 4> strs = { + "Friends", + "Online", + "Pending", + "Blocked", + }; + for (const auto &x : strs) { + auto *btn = Gtk::manage(new Gtk::RadioButton(m_group, x)); + m_buttons.add(*btn); + btn->show(); + btn->signal_toggled().connect([this, btn, str = x] { + if (!btn->get_active()) return; + switch (str[0]) { // hehe + case 'F': + m_filter_mode = FILTER_FRIENDS; + break; + case 'O': + m_filter_mode = FILTER_ONLINE; + break; + case 'P': + m_filter_mode = FILTER_PENDING; + break; + case 'B': + m_filter_mode = FILTER_BLOCKED; + break; + } + m_list.invalidate_filter(); + }); + } + m_buttons.set_homogeneous(true); + m_buttons.set_halign(Gtk::ALIGN_CENTER); + + m_add.set_halign(Gtk::ALIGN_CENTER); + m_add.set_margin_top(5); + m_add.set_margin_bottom(5); + + m_scroll.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + + m_list.set_sort_func(sigc::mem_fun(*this, &FriendsList::ListSortFunc)); + m_list.set_filter_func(sigc::mem_fun(*this, &FriendsList::ListFilterFunc)); + m_list.set_selection_mode(Gtk::SELECTION_NONE); + m_list.set_hexpand(true); + m_list.set_vexpand(true); + m_scroll.add(m_list); + add(m_add); + add(m_buttons); + add(m_scroll); + + m_add.show(); + m_scroll.show(); + m_buttons.show(); + m_list.show(); +} + +FriendsListFriendRow *FriendsList::MakeRow(const UserData &user, RelationshipType type) { + auto *row = Gtk::manage(new FriendsListFriendRow(type, user)); + row->signal_action_remove().connect(sigc::bind(sigc::mem_fun(*this, &FriendsList::OnActionRemove), user.ID)); + row->signal_action_accept().connect(sigc::bind(sigc::mem_fun(*this, &FriendsList::OnActionAccept), user.ID)); + return row; +} + +void FriendsList::OnRelationshipAdd(const RelationshipAddData &data) { + for (auto *row_ : m_list.get_children()) { + auto *row = dynamic_cast<FriendsListFriendRow *>(row_); + if (row == nullptr || row->ID != data.ID) continue; + delete row; + break; + } + + auto *row = MakeRow(data.User, data.Type); + m_list.add(*row); + row->show(); +} + +void FriendsList::OnRelationshipRemove(Snowflake id, RelationshipType type) { + for (auto *row_ : m_list.get_children()) { + auto *row = dynamic_cast<FriendsListFriendRow *>(row_); + if (row == nullptr || row->ID != id) continue; + delete row; + return; + } +} + +void FriendsList::OnActionAccept(Snowflake id) { + const auto cb = [this](bool success, DiscordError code) { + if (!success) { + Gtk::MessageDialog dlg(*dynamic_cast<Gtk::Window *>(get_toplevel()), "Failed to accept", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + dlg.set_position(Gtk::WIN_POS_CENTER); + dlg.run(); + } + }; + Abaddon::Get().GetDiscordClient().PutRelationship(id, sigc::track_obj(cb, *this)); +} + +void FriendsList::OnActionRemove(Snowflake id) { + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto user = discord.GetUser(id); + if (auto *window = dynamic_cast<Gtk::Window *>(get_toplevel())) { + Glib::ustring str; + switch (*discord.GetRelationship(id)) { + case RelationshipType::Blocked: + str = "Are you sure you want to unblock " + user->Username + "#" + user->Discriminator + "?"; + break; + case RelationshipType::Friend: + str = "Are you sure you want to remove " + user->Username + "#" + user->Discriminator + "?"; + break; + case RelationshipType::PendingIncoming: + str = "Are you sure you want to ignore " + user->Username + "#" + user->Discriminator + "?"; + break; + case RelationshipType::PendingOutgoing: + str = "Are you sure you want to cancel your request to " + user->Username + "#" + user->Discriminator + "?"; + break; + default: + break; + } + if (Abaddon::Get().ShowConfirm(str, window)) { + const auto cb = [this, window](bool success) { + if (success) return; + Gtk::MessageDialog dlg(*window, "Failed to remove user", false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_OK, true); + dlg.set_position(Gtk::WIN_POS_CENTER); + dlg.run(); + }; + discord.RemoveRelationship(id, sigc::track_obj(cb, *this)); + } + } +} + +void FriendsList::PopulateRelationships() { + for (auto child : m_list.get_children()) + delete child; + + auto &discord = Abaddon::Get().GetDiscordClient(); + for (const auto &[id, type] : discord.GetRelationships()) { + const auto user = discord.GetUser(id); + if (!user.has_value()) continue; + auto *row = MakeRow(*user, type); + m_list.add(*row); + row->show(); + } +} + +int FriendsList::ListSortFunc(Gtk::ListBoxRow *a_, Gtk::ListBoxRow *b_) { + auto *a = dynamic_cast<FriendsListFriendRow *>(a_); + auto *b = dynamic_cast<FriendsListFriendRow *>(b_); + if (a == nullptr || b == nullptr) return 0; + return a->Name.compare(b->Name); +} + +bool FriendsList::ListFilterFunc(Gtk::ListBoxRow *row_) { + auto *row = dynamic_cast<FriendsListFriendRow *>(row_); + if (row == nullptr) return false; + switch (m_filter_mode) { + case FILTER_FRIENDS: + return row->Type == RelationshipType::Friend; + case FILTER_ONLINE: + return row->Type == RelationshipType::Friend && row->Status != PresenceStatus::Offline; + case FILTER_PENDING: + return row->Type == RelationshipType::PendingIncoming || row->Type == RelationshipType::PendingOutgoing; + case FILTER_BLOCKED: + return row->Type == RelationshipType::Blocked; + default: + return false; + } +} + +FriendsListAddComponent::FriendsListAddComponent() + : Gtk::Box(Gtk::ORIENTATION_VERTICAL) + , m_label("Add a Friend", Gtk::ALIGN_START) + , m_box(Gtk::ORIENTATION_HORIZONTAL) + , m_add("Add") + , m_status("", Gtk::ALIGN_START) { + m_box.add(m_entry); + m_box.add(m_add); + m_box.add(m_status); + + m_add.signal_clicked().connect(sigc::mem_fun(*this, &FriendsListAddComponent::Submit)); + + m_label.set_halign(Gtk::ALIGN_CENTER); + + m_entry.set_placeholder_text("Enter a Username#1234"); + m_entry.signal_key_press_event().connect(sigc::mem_fun(*this, &FriendsListAddComponent::OnKeyPress), false); + + add(m_label); + add(m_box); + + show_all_children(); +} + +void FriendsListAddComponent::Submit() { + if (m_requesting) return; + + auto text = m_entry.get_text(); + m_label.set_text("Invalid input"); // cheeky !! + m_entry.set_text(""); + const auto hashpos = text.find("#"); + if (hashpos == Glib::ustring::npos) return; + const auto username = text.substr(0, hashpos); + const auto discriminator = text.substr(hashpos + 1); + if (username.size() == 0 || discriminator.size() != 4) return; + if (discriminator.find_first_not_of("0123456789") != Glib::ustring::npos) return; + + m_requesting = true; + m_label.set_text("Hang on..."); + + const auto cb = [this](bool success, DiscordError code) { + m_requesting = false; + if (success) { + m_label.set_text("Success!"); + } else { + m_label.set_text("Failed: "s + GetDiscordErrorDisplayString(code)); + } + }; + Abaddon::Get().GetDiscordClient().SendFriendRequest(username, std::stoul(discriminator), sigc::track_obj(cb, *this)); +} + +bool FriendsListAddComponent::OnKeyPress(GdkEventKey *e) { + if (e->keyval == GDK_KEY_Return) { + Submit(); + return true; + } + return false; +} + +FriendsListFriendRow::FriendsListFriendRow(RelationshipType type, const UserData &data) + : Name(data.Username + "#" + data.Discriminator) + , Type(type) + , ID(data.ID) + , Status(Abaddon::Get().GetDiscordClient().GetUserStatus(data.ID)) + , m_accept("Accept") { + auto *ev = Gtk::manage(new Gtk::EventBox); + auto *box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); + auto *img = Gtk::manage(new LazyImage(32, 32, true)); + auto *namebox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL)); + auto *namelbl = Gtk::manage(new Gtk::Label("", Gtk::ALIGN_START)); + m_status_lbl = Gtk::manage(new Gtk::Label("", Gtk::ALIGN_START)); + auto *lblbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL)); + + auto &discord = Abaddon::Get().GetDiscordClient(); + discord.signal_presence_update().connect(sigc::mem_fun(*this, &FriendsListFriendRow::OnPresenceUpdate)); + + if (data.HasAnimatedAvatar()) { + img->SetAnimated(true); + img->SetURL(data.GetAvatarURL("gif", "32")); + } else { + img->SetURL(data.GetAvatarURL("png", "32")); + } + + namelbl->set_markup(data.GetEscapedBoldName()); + + UpdatePresenceLabel(); + + AddWidgetMenuHandler(ev, m_menu, [this] { + m_accept.set_visible(Type == RelationshipType::PendingIncoming); + + switch (Type) { + case RelationshipType::Blocked: + case RelationshipType::Friend: + m_remove.set_label("Remove"); + break; + case RelationshipType::PendingIncoming: + m_remove.set_label("Ignore"); + break; + case RelationshipType::PendingOutgoing: + m_remove.set_label("Cancel"); + break; + default: + break; + } + }); + + m_remove.signal_activate().connect([this] { + m_signal_remove.emit(); + }); + + m_accept.signal_activate().connect([this] { + m_signal_accept.emit(); + }); + + m_menu.append(m_accept); + m_menu.append(m_remove); + m_menu.show_all(); + + lblbox->set_valign(Gtk::ALIGN_CENTER); + + img->set_margin_end(5); + + namebox->add(*namelbl); + lblbox->add(*namebox); + lblbox->add(*m_status_lbl); + + box->add(*img); + box->add(*lblbox); + + ev->add(*box); + add(*ev); + show_all_children(); +} + +void FriendsListFriendRow::UpdatePresenceLabel() { + switch (Type) { + case RelationshipType::PendingIncoming: + m_status_lbl->set_text("Incoming Friend Request"); + break; + case RelationshipType::PendingOutgoing: + m_status_lbl->set_text("Outgoing Friend Request"); + break; + default: + m_status_lbl->set_text(GetPresenceDisplayString(Status)); + break; + } +} + +void FriendsListFriendRow::OnPresenceUpdate(const UserData &user, PresenceStatus status) { + if (user.ID != ID) return; + Status = status; + UpdatePresenceLabel(); + changed(); +} + +FriendsListFriendRow::type_signal_remove FriendsListFriendRow::signal_action_remove() { + return m_signal_remove; +} + +FriendsListFriendRow::type_signal_accept FriendsListFriendRow::signal_action_accept() { + return m_signal_accept; +} + +FriendsListWindow::FriendsListWindow() { + add(m_friends); + set_default_size(500, 500); + get_style_context()->add_class("app-window"); + get_style_context()->add_class("app-popup"); + m_friends.show(); +} diff --git a/components/friendslist.hpp b/components/friendslist.hpp new file mode 100644 index 0000000..0101db6 --- /dev/null +++ b/components/friendslist.hpp @@ -0,0 +1,92 @@ +#pragma once +#include <gtkmm.h> +#include "../discord/objects.hpp" + +class FriendsListAddComponent : public Gtk::Box { +public: + FriendsListAddComponent(); + +private: + void Submit(); + bool OnKeyPress(GdkEventKey *e); + + Gtk::Label m_label; + Gtk::Label m_status; + Gtk::Entry m_entry; + Gtk::Button m_add; + Gtk::Box m_box; + + bool m_requesting = false; +}; + +class FriendsListFriendRow; +class FriendsList : public Gtk::Box { +public: + FriendsList(); + +private: + FriendsListFriendRow *MakeRow(const UserData &user, RelationshipType type); + + void OnRelationshipAdd(const RelationshipAddData &data); + void OnRelationshipRemove(Snowflake id, RelationshipType type); + + void OnActionAccept(Snowflake id); + void OnActionRemove(Snowflake id); + + void PopulateRelationships(); + + enum FilterMode { + FILTER_FRIENDS, + FILTER_ONLINE, + FILTER_PENDING, + FILTER_BLOCKED, + }; + + FilterMode m_filter_mode; + + int ListSortFunc(Gtk::ListBoxRow *a, Gtk::ListBoxRow *b); + bool ListFilterFunc(Gtk::ListBoxRow *row); + + FriendsListAddComponent m_add; + Gtk::RadioButtonGroup m_group; + Gtk::ButtonBox m_buttons; + Gtk::ScrolledWindow m_scroll; + Gtk::ListBox m_list; +}; + +class FriendsListFriendRow : public Gtk::ListBoxRow { +public: + FriendsListFriendRow(RelationshipType type, const UserData &str); + + Snowflake ID; + RelationshipType Type; + Glib::ustring Name; + PresenceStatus Status; + +private: + void UpdatePresenceLabel(); + void OnPresenceUpdate(const UserData &user, PresenceStatus status); + + Gtk::Label *m_status_lbl; + + Gtk::Menu m_menu; + Gtk::MenuItem m_remove; // or cancel or ignore + Gtk::MenuItem m_accept; // incoming + + using type_signal_remove = sigc::signal<void>; + using type_signal_accept = sigc::signal<void>; + type_signal_remove m_signal_remove; + type_signal_accept m_signal_accept; + +public: + type_signal_remove signal_action_remove(); + type_signal_accept signal_action_accept(); +}; + +class FriendsListWindow : public Gtk::Window { +public: + FriendsListWindow(); + +private: + FriendsList m_friends; +}; diff --git a/css/main.css b/css/main.css index 14e5a86..e99ccd1 100644 --- a/css/main.css +++ b/css/main.css @@ -341,3 +341,12 @@ .drag-hover-bottom { background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 65%, rgba(255, 66, 66, 0.65) 100%); } + +.friends-list list { + background: @background_color; + padding-left: 10px; +} + +.friends-list-row-bot { + color: #ff0000; +} diff --git a/discord/activity.hpp b/discord/activity.hpp index b50d176..76ba9cd 100644 --- a/discord/activity.hpp +++ b/discord/activity.hpp @@ -26,6 +26,20 @@ constexpr inline const char *GetPresenceString(PresenceStatus s) { return ""; } +constexpr inline const char* GetPresenceDisplayString(PresenceStatus s) { + switch (s) { + case PresenceStatus::Online: + return "Online"; + case PresenceStatus::Offline: + return "Offline"; + case PresenceStatus::Idle: + return "Away"; + case PresenceStatus::DND: + return "Do Not Disturb"; + } + return ""; +} + enum class ActivityType : int { Game = 0, Streaming = 1, diff --git a/discord/discord.cpp b/discord/discord.cpp index e4592ca..0e06e39 100644 --- a/discord/discord.cpp +++ b/discord/discord.cpp @@ -691,6 +691,33 @@ std::optional<GuildApplicationData> DiscordClient::GetGuildApplication(Snowflake return it->second; } +void DiscordClient::RemoveRelationship(Snowflake id, sigc::slot<void(bool success)> callback) { + m_http.MakeDELETE("/users/@me/relationships/" + std::to_string(id), [this, callback](const http::response_type &response) { + callback(CheckCode(response)); + }); +} + +void DiscordClient::SendFriendRequest(const Glib::ustring &username, int discriminator, sigc::slot<void(bool success, DiscordError code)> callback) { + FriendRequestObject obj; + obj.Username = username; + obj.Discriminator = discriminator; + m_http.MakePOST("/users/@me/relationships", nlohmann::json(obj).dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response, 204)) + callback(true, DiscordError::NONE); + else + callback(false, GetCodeFromResponse(response)); + }); +} + +void DiscordClient::PutRelationship(Snowflake id, sigc::slot<void(bool success, DiscordError code)> callback) { + m_http.MakePUT("/users/@me/relationships/" + std::to_string(id), "{}", [this, callback](const http::response_type &response) { + if (CheckCode(response, 204)) + callback(true, DiscordError::NONE); + else + callback(false, GetCodeFromResponse(response)); + }); +} + bool DiscordClient::CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const { const auto guild = *GetGuild(guild_id); if (guild.OwnerID == user_id) return true; @@ -850,6 +877,10 @@ PresenceStatus DiscordClient::GetUserStatus(Snowflake id) const { return PresenceStatus::Offline; } +std::unordered_map<Snowflake, RelationshipType> DiscordClient::GetRelationships() const { + return m_user_relationships; +} + std::unordered_set<Snowflake> DiscordClient::GetRelationships(RelationshipType type) const { std::unordered_set<Snowflake> ret; for (const auto &[id, rtype] : m_user_relationships) @@ -858,6 +889,12 @@ std::unordered_set<Snowflake> DiscordClient::GetRelationships(RelationshipType t return ret; } +std::optional<RelationshipType> DiscordClient::GetRelationship(Snowflake id) const { + if (auto it = m_user_relationships.find(id); it != m_user_relationships.end()) + return it->second; + return std::nullopt; +} + void DiscordClient::HandleGatewayMessageRaw(std::string str) { // handles multiple zlib compressed messages, calling HandleGatewayMessage when a full message is received std::vector<uint8_t> buf(str.begin(), str.end()); @@ -1067,6 +1104,15 @@ void DiscordClient::HandleGatewayHello(const GatewayMessage &msg) { SendIdentify(); } +DiscordError DiscordClient::GetCodeFromResponse(const http::response_type &response) { + try { + // pull me somewhere else? + const auto data = nlohmann::json::parse(response.text); + return data.at("code").get<DiscordError>(); + } catch (...) {} + return DiscordError::GENERIC; +} + void DiscordClient::ProcessNewGuild(GuildData &guild) { if (guild.IsUnavailable) { printf("guild (%lld) unavailable\n", static_cast<uint64_t>(guild.ID)); diff --git a/discord/discord.hpp b/discord/discord.hpp index 6bdeb37..ff4fe2e 100644 --- a/discord/discord.hpp +++ b/discord/discord.hpp @@ -138,6 +138,9 @@ public: void ModifyEmojiName(Snowflake guild_id, Snowflake emoji_id, const Glib::ustring &name, sigc::slot<void(bool success)> callback); void DeleteEmoji(Snowflake guild_id, Snowflake emoji_id, sigc::slot<void(bool success)> callback); std::optional<GuildApplicationData> GetGuildApplication(Snowflake guild_id) const; + void RemoveRelationship(Snowflake id, sigc::slot<void(bool success)> callback); + void SendFriendRequest(const Glib::ustring &username, int discriminator, sigc::slot<void(bool success, DiscordError code)> callback); + void PutRelationship(Snowflake id, sigc::slot<void(bool success, DiscordError code)> callback); // send fr by id, accept incoming bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const; bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const; @@ -178,7 +181,9 @@ public: PresenceStatus GetUserStatus(Snowflake id) const; + std::unordered_map<Snowflake, RelationshipType> GetRelationships() const; std::unordered_set<Snowflake> GetRelationships(RelationshipType type) const; + std::optional<RelationshipType> GetRelationship(Snowflake id) const; private: static const constexpr int InflateChunkSize = 0x10000; @@ -186,6 +191,8 @@ private: std::vector<uint8_t> m_decompress_buf; z_stream m_zstream; + static DiscordError GetCodeFromResponse(const http::response_type &response); + void ProcessNewGuild(GuildData &guild); void HandleGatewayMessageRaw(std::string str); diff --git a/discord/errors.hpp b/discord/errors.hpp new file mode 100644 index 0000000..4579563 --- /dev/null +++ b/discord/errors.hpp @@ -0,0 +1,36 @@ +#pragma once + +enum class DiscordError { + GENERIC = 0, + INVALID_FORM_BODY = 50035, + RELATIONSHIP_INCOMING_DISABLED = 80000, + RELATIONSHIP_INCOMING_BLOCKED = 80001, + RELATIONSHIP_INVALID_USER_BOT = 80002, // this is misspelled in discord's source lul + RELATIONSHIP_INVALID_SELF = 80003, + RELATIONSHIP_INVALID_DISCORD_TAG = 80004, + RELATIONSHIP_ALREADY_FRIENDS = 80007, + + NONE = -1, +}; + +constexpr const char *GetDiscordErrorDisplayString(DiscordError error) { + switch (error) { + case DiscordError::INVALID_FORM_BODY: + return "Something's wrong with your input"; + case DiscordError::RELATIONSHIP_INCOMING_DISABLED: + return "This user isn't accepting friend requests"; + case DiscordError::RELATIONSHIP_INCOMING_BLOCKED: + return "You are blocked by this user"; + case DiscordError::RELATIONSHIP_INVALID_USER_BOT: + return "You can't send a request to a bot"; + case DiscordError::RELATIONSHIP_INVALID_SELF: + return "You can't send a request to yourself"; + case DiscordError::RELATIONSHIP_INVALID_DISCORD_TAG: + return "No users with that tag exist"; + case DiscordError::RELATIONSHIP_ALREADY_FRIENDS: + return "You are already friends with that user"; + case DiscordError::GENERIC: + default: + return "An error occurred"; + } +} diff --git a/discord/objects.cpp b/discord/objects.cpp index 95f7fa6..8588b30 100644 --- a/discord/objects.cpp +++ b/discord/objects.cpp @@ -463,3 +463,12 @@ void from_json(const nlohmann::json &j, RelationshipAddData &m) { JS_D("type", m.Type); JS_D("user", m.User); } + +void to_json(nlohmann::json &j, const FriendRequestObject &m) { + j["username"] = m.Username; + j["discriminator"] = m.Discriminator; +} + +void to_json(nlohmann::json &j, const PutRelationshipObject &m) { + JS_IF("type", m.Type); +} diff --git a/discord/objects.hpp b/discord/objects.hpp index 56191df..11c8df4 100644 --- a/discord/objects.hpp +++ b/discord/objects.hpp @@ -19,6 +19,7 @@ #include "ban.hpp" #include "auditlog.hpp" #include "relationship.hpp" +#include "errors.hpp" // most stuff below should just be objects that get processed and thrown away immediately @@ -645,3 +646,16 @@ struct RelationshipAddData { friend void from_json(const nlohmann::json &j, RelationshipAddData &m); }; + +struct FriendRequestObject { + std::string Username; + int Discriminator; + + friend void to_json(nlohmann::json &j, const FriendRequestObject &m); +}; + +struct PutRelationshipObject { + std::optional<RelationshipType> Type; + + friend void to_json(nlohmann::json &j, const PutRelationshipObject &m); +}; diff --git a/windows/mainwindow.cpp b/windows/mainwindow.cpp index b6d3ca5..6565e3b 100644 --- a/windows/mainwindow.cpp +++ b/windows/mainwindow.cpp @@ -39,8 +39,15 @@ MainWindow::MainWindow() m_menu_file_sub.append(m_menu_file_reload_css); m_menu_file_sub.append(m_menu_file_clear_cache); + m_menu_view.set_label("View"); + m_menu_view.set_submenu(m_menu_view_sub); + m_menu_view_friends.set_label("Friends"); + m_menu_view_sub.append(m_menu_view_friends); + m_menu_view_sub.signal_popped_up().connect(sigc::mem_fun(*this, &MainWindow::OnViewSubmenuPopup)); + m_menu_bar.append(m_menu_file); m_menu_bar.append(m_menu_discord); + m_menu_bar.append(m_menu_view); m_menu_bar.show_all(); m_menu_discord_connect.signal_activate().connect([this] { @@ -79,6 +86,10 @@ MainWindow::MainWindow() m_signal_action_add_recipient.emit(GetChatActiveChannel()); }); + m_menu_view_friends.signal_activate().connect([this] { + m_content_stack.set_visible_child("friends"); + }); + m_content_box.set_hexpand(true); m_content_box.set_vexpand(true); m_content_box.show(); @@ -110,9 +121,15 @@ MainWindow::MainWindow() member_list->set_vexpand(true); member_list->show(); - m_content_stack.add(*chat); + m_friends.set_vexpand(true); + m_friends.set_hexpand(true); + m_friends.show(); + + m_content_stack.add(*chat, "chat"); + m_content_stack.add(m_friends, "friends"); m_content_stack.set_vexpand(true); m_content_stack.set_hexpand(true); + m_content_stack.set_visible_child("chat"); m_content_stack.show(); m_chan_content_paned.pack1(*channel_list); @@ -207,6 +224,7 @@ void MainWindow::UpdateChatActiveChannel(Snowflake id) { m_chat.SetActiveChannel(id); m_members.SetActiveChannel(id); m_channel_list.SetActiveChannel(id); + m_content_stack.set_visible_child("chat"); } Snowflake MainWindow::GetChatActiveChannel() const { @@ -258,6 +276,10 @@ void MainWindow::OnDiscordSubmenuPopup(const Gdk::Rectangle *flipped_rect, const m_menu_discord_add_recipient.set_visible(channel->Type == ChannelType::GROUP_DM); } +void MainWindow::OnViewSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { + m_menu_view_friends.set_sensitive(Abaddon::Get().GetDiscordClient().IsStarted()); +} + ChannelList *MainWindow::GetChannelList() { return &m_channel_list; } diff --git a/windows/mainwindow.hpp b/windows/mainwindow.hpp index 0be4bc0..544e9a3 100644 --- a/windows/mainwindow.hpp +++ b/windows/mainwindow.hpp @@ -2,6 +2,7 @@ #include "../components/channels.hpp" #include "../components/chatwindow.hpp" #include "../components/memberlist.hpp" +#include "../components/friendslist.hpp" #include <gtkmm.h> class MainWindow : public Gtk::Window { @@ -74,6 +75,7 @@ protected: ChannelList m_channel_list; ChatWindow m_chat; MemberList m_members; + FriendsList m_friends; Gtk::Stack m_content_stack; @@ -93,4 +95,9 @@ protected: Gtk::MenuItem m_menu_file_reload_settings; Gtk::MenuItem m_menu_file_reload_css; Gtk::MenuItem m_menu_file_clear_cache; + + Gtk::MenuItem m_menu_view; + Gtk::Menu m_menu_view_sub; + Gtk::MenuItem m_menu_view_friends; + void OnViewSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); }; |