From af567847970121765674dc8d0542b9c4a1f89ed1 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 5 Dec 2021 03:57:26 -0500 Subject: basic unread indicators for channels --- src/components/channels.cpp | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 455d3b1..f61abd2 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1,21 +1,21 @@ #include "channels.hpp" -#include -#include -#include #include "abaddon.hpp" #include "imgmanager.hpp" #include "util.hpp" #include "statusindicator.hpp" +#include +#include +#include ChannelList::ChannelList() : Glib::ObjectBase(typeid(ChannelList)) - , Gtk::ScrolledWindow() , m_model(Gtk::TreeStore::create(m_columns)) , m_menu_guild_copy_id("_Copy ID", true) , m_menu_guild_settings("View _Settings", true) , m_menu_guild_leave("_Leave", true) , m_menu_category_copy_id("_Copy ID", true) , m_menu_channel_copy_id("_Copy ID", true) + , m_menu_channel_mark_as_read("Mark as _Read", true) , m_menu_dm_copy_id("_Copy ID", true) , m_menu_dm_close("") // changes depending on if group or not , m_menu_thread_copy_id("_Copy ID", true) @@ -24,6 +24,7 @@ ChannelList::ChannelList() , m_menu_thread_unarchive("_Unarchive", true) { get_style_context()->add_class("channel-list"); + // todo: move to method const auto cb = [this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *column) { auto row = *m_model->get_iter(path); const auto type = row[m_columns.m_type]; @@ -40,7 +41,9 @@ ChannelList::ChannelList() } if (type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread) { - m_signal_action_channel_item_select.emit(static_cast(row[m_columns.m_id])); + const auto id = static_cast(row[m_columns.m_id]); + m_signal_action_channel_item_select.emit(id); + Abaddon::Get().GetDiscordClient().MarkAsRead(id, [](...) {}); } }; m_view.signal_row_activated().connect(cb, false); @@ -77,6 +80,7 @@ ChannelList::ChannelList() column->add_attribute(renderer->property_icon(), m_columns.m_icon); column->add_attribute(renderer->property_icon_animation(), m_columns.m_icon_anim); column->add_attribute(renderer->property_name(), m_columns.m_name); + column->add_attribute(renderer->property_id(), m_columns.m_id); column->add_attribute(renderer->property_expanded(), m_columns.m_expanded); column->add_attribute(renderer->property_nsfw(), m_columns.m_nsfw); m_view.append_column(*column); @@ -98,13 +102,18 @@ ChannelList::ChannelList() m_menu_category_copy_id.signal_activate().connect([this] { Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); + m_menu_category.append(m_menu_category_copy_id); m_menu_category.show_all(); m_menu_channel_copy_id.signal_activate().connect([this] { Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); + m_menu_channel_mark_as_read.signal_activate().connect([this] { + Abaddon::Get().GetDiscordClient().MarkAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); + }); m_menu_channel.append(m_menu_channel_copy_id); + m_menu_channel.append(m_menu_channel_mark_as_read); m_menu_channel.show_all(); m_menu_dm_copy_id.signal_activate().connect([this] { @@ -159,6 +168,7 @@ ChannelList::ChannelList() discord.signal_added_to_thread().connect(sigc::mem_fun(*this, &ChannelList::OnThreadJoined)); discord.signal_removed_from_thread().connect(sigc::mem_fun(*this, &ChannelList::OnThreadRemoved)); discord.signal_guild_update().connect(sigc::mem_fun(*this, &ChannelList::UpdateGuild)); + discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck)); } void ChannelList::UpdateListing() { @@ -658,7 +668,7 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { std::optional top_recipient; const auto recipients = dm.GetDMRecipients(); - if (recipients.size() > 0) + if (!recipients.empty()) top_recipient = recipients[0]; auto iter = m_model->append(header_row->children()); @@ -682,6 +692,12 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { } } +void ChannelList::OnMessageAck(const MessageAckData &data) { + // trick renderer into redrawing + auto iter = GetIteratorForChannelFromID(data.ChannelID); + if (iter) m_model->row_changed(m_model->get_path(iter), iter); +} + void ChannelList::OnMessageCreate(const Message &msg) { const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(msg.ChannelID); if (!channel.has_value()) return; -- cgit v1.2.3 From d63941797f30d2d114d66a8edb03000e64488bd0 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sun, 5 Dec 2021 04:07:30 -0500 Subject: mark channels as unread on MESSAGE_CREATE --- src/components/channels.cpp | 3 ++- src/discord/discord.cpp | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index f61abd2..764fb43 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -699,10 +699,11 @@ void ChannelList::OnMessageAck(const MessageAckData &data) { } void ChannelList::OnMessageCreate(const Message &msg) { + auto iter = GetIteratorForChannelFromID(msg.ChannelID); + m_model->row_changed(m_model->get_path(iter), iter); // redraw const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(msg.ChannelID); if (!channel.has_value()) return; if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM) return; - auto iter = GetIteratorForChannelFromID(msg.ChannelID); if (iter) (*iter)[m_columns.m_sort] = -msg.ID; } diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 34626f3..27d162c 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1444,6 +1444,9 @@ void DiscordClient::HandleGatewayMessageCreate(const GatewayMessage &msg) { if (data.GuildID.has_value()) AddUserToGuild(data.Author.ID, *data.GuildID); m_last_message_id[data.ChannelID] = data.ID; + const auto iter = m_unread.find(data.ChannelID); + if (iter == m_unread.end()) + m_unread[data.ChannelID] = 0; m_signal_message_create.emit(data); } -- cgit v1.2.3 From d288989386acbade608fd02da7f078a99efa8578 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 6 Dec 2021 03:04:22 -0500 Subject: mark guild as read --- src/components/channels.cpp | 9 +++++++-- src/components/channels.hpp | 1 + src/discord/discord.cpp | 23 ++++++++++++++++++++++- src/discord/discord.hpp | 3 ++- src/discord/objects.cpp | 9 +++++++++ src/discord/objects.hpp | 7 +++++++ 6 files changed, 48 insertions(+), 4 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 764fb43..418781a 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -13,6 +13,7 @@ ChannelList::ChannelList() , m_menu_guild_copy_id("_Copy ID", true) , m_menu_guild_settings("View _Settings", true) , m_menu_guild_leave("_Leave", true) + , m_menu_guild_mark_as_read("Mark as _Read", true) , m_menu_category_copy_id("_Copy ID", true) , m_menu_channel_copy_id("_Copy ID", true) , m_menu_channel_mark_as_read("Mark as _Read", true) @@ -43,7 +44,7 @@ ChannelList::ChannelList() if (type == RenderType::TextChannel || type == RenderType::DM || type == RenderType::Thread) { const auto id = static_cast(row[m_columns.m_id]); m_signal_action_channel_item_select.emit(id); - Abaddon::Get().GetDiscordClient().MarkAsRead(id, [](...) {}); + Abaddon::Get().GetDiscordClient().MarkChannelAsRead(id, [](...) {}); } }; m_view.signal_row_activated().connect(cb, false); @@ -94,9 +95,13 @@ ChannelList::ChannelList() m_menu_guild_leave.signal_activate().connect([this] { m_signal_action_guild_leave.emit(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); + m_menu_guild_mark_as_read.signal_activate().connect([this] { + Abaddon::Get().GetDiscordClient().MarkGuildAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); + }); m_menu_guild.append(m_menu_guild_copy_id); m_menu_guild.append(m_menu_guild_settings); m_menu_guild.append(m_menu_guild_leave); + m_menu_guild.append(m_menu_guild_mark_as_read); m_menu_guild.show_all(); m_menu_category_copy_id.signal_activate().connect([this] { @@ -110,7 +115,7 @@ ChannelList::ChannelList() Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); m_menu_channel_mark_as_read.signal_activate().connect([this] { - Abaddon::Get().GetDiscordClient().MarkAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); + Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); }); m_menu_channel.append(m_menu_channel_copy_id); m_menu_channel.append(m_menu_channel_mark_as_read); diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 99eff5f..d279907 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -101,6 +101,7 @@ protected: Gtk::MenuItem m_menu_guild_copy_id; Gtk::MenuItem m_menu_guild_settings; Gtk::MenuItem m_menu_guild_leave; + Gtk::MenuItem m_menu_guild_mark_as_read; Gtk::Menu m_menu_category; Gtk::MenuItem m_menu_category_copy_id; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 27d162c..09463b8 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -874,7 +874,7 @@ void DiscordClient::UnArchiveThread(Snowflake channel_id, sigc::slot callback) { +void DiscordClient::MarkChannelAsRead(Snowflake channel_id, sigc::slot callback) { if (m_unread.find(channel_id) == m_unread.end()) return; const auto iter = m_last_message_id.find(channel_id); if (iter == m_last_message_id.end()) return; @@ -886,6 +886,27 @@ void DiscordClient::MarkAsRead(Snowflake channel_id, sigc::slot callback) { + AckBulkData data; + const auto channels = GetChannelsInGuild(guild_id); + for (const auto &[unread, mention_count] : m_unread) { + const auto iter = m_last_message_id.find(unread); + if (iter == m_last_message_id.end()) continue; + auto &e = data.ReadStates.emplace_back(); + e.ID = unread; + e.LastMessageID = iter->second; + } + + if (data.ReadStates.empty()) return; + + m_http.MakePOST("/read-states/ack-bulk", nlohmann::json(data).dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + void DiscordClient::FetchPinned(Snowflake id, sigc::slot, DiscordError code)> callback) { // return from db if we know the pins have already been requested if (m_channels_pinned_requested.find(id) != m_channels_pinned_requested.end()) { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 06aa698..465866a 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -138,7 +138,8 @@ public: void LeaveThread(Snowflake channel_id, const std::string &location, sigc::slot callback); void ArchiveThread(Snowflake channel_id, sigc::slot callback); void UnArchiveThread(Snowflake channel_id, sigc::slot callback); - void MarkAsRead(Snowflake channel_id, sigc::slot callback); + void MarkChannelAsRead(Snowflake channel_id, sigc::slot callback); + void MarkGuildAsRead(Snowflake guild_id, sigc::slot callback); bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const; bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 88d3f30..9fbc7ce 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -125,6 +125,11 @@ void from_json(const nlohmann::json &j, ReadStateEntry &m) { JS_D("id", m.ID); } +void to_json(nlohmann::json &j, const ReadStateEntry &m) { + j["channel_id"] = m.ID; + j["message_id"] = m.LastMessageID; +} + void from_json(const nlohmann::json &j, ReadStateData &m) { JS_ON("version", m.Version); JS_ON("partial", m.IsPartial); @@ -551,3 +556,7 @@ void from_json(const nlohmann::json &j, MessageAckData &m) { JS_D("message_id", m.MessageID); JS_D("channel_id", m.ChannelID); } + +void to_json(nlohmann::json &j, const AckBulkData &m) { + j["read_states"] = m.ReadStates; +} diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 008fe98..28331c3 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -232,6 +232,7 @@ struct ReadStateEntry { // std::string LastPinTimestamp; iso friend void from_json(const nlohmann::json &j, ReadStateEntry &m); + friend void to_json(nlohmann::json &j, const ReadStateEntry &m); }; struct ReadStateData { @@ -772,3 +773,9 @@ struct MessageAckData { friend void from_json(const nlohmann::json &j, MessageAckData &m); }; + +struct AckBulkData { + std::vector ReadStates; + + friend void to_json(nlohmann::json &j, const AckBulkData &m); +}; -- cgit v1.2.3 From 14b5bf7d0d985afb23e1497092172fcaa235b8da Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 6 Dec 2021 17:35:44 -0500 Subject: reorder menu items --- src/components/channels.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 418781a..4d3d5a2 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -98,10 +98,10 @@ ChannelList::ChannelList() m_menu_guild_mark_as_read.signal_activate().connect([this] { Abaddon::Get().GetDiscordClient().MarkGuildAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); }); - m_menu_guild.append(m_menu_guild_copy_id); + m_menu_guild.append(m_menu_guild_mark_as_read); m_menu_guild.append(m_menu_guild_settings); m_menu_guild.append(m_menu_guild_leave); - m_menu_guild.append(m_menu_guild_mark_as_read); + m_menu_guild.append(m_menu_guild_copy_id); m_menu_guild.show_all(); m_menu_category_copy_id.signal_activate().connect([this] { @@ -117,8 +117,8 @@ ChannelList::ChannelList() m_menu_channel_mark_as_read.signal_activate().connect([this] { Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); }); - m_menu_channel.append(m_menu_channel_copy_id); m_menu_channel.append(m_menu_channel_mark_as_read); + m_menu_channel.append(m_menu_channel_copy_id); m_menu_channel.show_all(); m_menu_dm_copy_id.signal_activate().connect([this] { @@ -135,8 +135,8 @@ ChannelList::ChannelList() else if (Abaddon::Get().ShowConfirm("Are you sure you want to leave this group DM?")) Abaddon::Get().GetDiscordClient().CloseDM(id); }); - m_menu_dm.append(m_menu_dm_copy_id); m_menu_dm.append(m_menu_dm_close); + m_menu_dm.append(m_menu_dm_copy_id); m_menu_dm.show_all(); m_menu_thread_copy_id.signal_activate().connect([this] { -- cgit v1.2.3 From 511fb445d15e379d4dd3b7fd137a249c960cf84d Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 9 Dec 2021 02:54:59 -0500 Subject: rudimentary guild unread indicator --- src/components/channels.cpp | 5 +++++ src/components/channelscellrenderer.cpp | 4 +++- src/components/unreadrenderer.cpp | 30 ++++++++++++++++++++++++++---- src/components/unreadrenderer.hpp | 1 + 4 files changed, 35 insertions(+), 5 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 4d3d5a2..d8fbde9 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -701,6 +701,11 @@ void ChannelList::OnMessageAck(const MessageAckData &data) { // trick renderer into redrawing auto iter = GetIteratorForChannelFromID(data.ChannelID); if (iter) m_model->row_changed(m_model->get_path(iter), iter); + auto channel = Abaddon::Get().GetDiscordClient().GetChannel(data.ChannelID); + if (channel.has_value() && channel->GuildID.has_value()) { + iter = GetIteratorForGuildFromID(*channel->GuildID); + if (iter) m_model->row_changed(m_model->get_path(iter), iter); + } } void ChannelList::OnMessageCreate(const Message &msg) { diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index f4cd33c..a96668c 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -198,7 +198,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtrrectangle(icon_x, icon_y, icon_w, icon_h); cr->fill(); } + + UnreadRenderer::RenderUnreadOnGuild(m_property_id.get_value(), cr, background_area, cell_area); } // category diff --git a/src/components/unreadrenderer.cpp b/src/components/unreadrenderer.cpp index 4e508fc..b53af2d 100644 --- a/src/components/unreadrenderer.cpp +++ b/src/components/unreadrenderer.cpp @@ -1,14 +1,36 @@ #include "unreadrenderer.hpp" #include "abaddon.hpp" +void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { + // maybe have DiscordClient track this? + auto &discord = Abaddon::Get().GetDiscordClient(); + const auto channels = discord.GetChannelsInGuild(id); + bool has_unread = false; + for (const auto &id : channels) { + if (Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id) >= 0) { + has_unread = true; + break; + } + } + if (!has_unread) return; + + cr->set_source_rgb(1.0, 1.0, 1.0); + const auto x = background_area.get_x(); + const auto y = background_area.get_y(); + const auto w = background_area.get_width(); + const auto h = background_area.get_height(); + cr->rectangle(x, y + h / 2 - 24 / 2, 3, 24); + cr->fill(); +} + void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { const auto state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); if (state >= 0) { cr->set_source_rgb(1.0, 1.0, 1.0); - const auto x = cell_area.get_x() + 1; - const auto y = cell_area.get_y(); - const auto w = cell_area.get_width(); - const auto h = cell_area.get_height(); + const auto x = background_area.get_x(); + const auto y = background_area.get_y(); + const auto w = background_area.get_width(); + const auto h = background_area.get_height(); cr->rectangle(x, y, 3, h); cr->fill(); } diff --git a/src/components/unreadrenderer.hpp b/src/components/unreadrenderer.hpp index e333543..30446fa 100644 --- a/src/components/unreadrenderer.hpp +++ b/src/components/unreadrenderer.hpp @@ -5,5 +5,6 @@ class UnreadRenderer { public: + static void RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); static void RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); }; -- cgit v1.2.3 From 0b0135268ec4684dfcdc0a5ae5c8da947bb17e12 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 10 Dec 2021 00:15:39 -0500 Subject: basic channel mentions count indicator --- src/components/channels.cpp | 8 ++++++ src/components/channels.hpp | 4 +++ src/components/channelscellrenderer.cpp | 4 +-- src/components/unreadrenderer.cpp | 43 +++++++++++++++++++++++++-------- src/components/unreadrenderer.hpp | 5 ++-- src/discord/discord.cpp | 7 +++--- src/discord/message.cpp | 6 +++++ src/discord/message.hpp | 2 ++ src/windows/mainwindow.cpp | 1 + 9 files changed, 63 insertions(+), 17 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index d8fbde9..c83b5f8 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -176,6 +176,14 @@ ChannelList::ChannelList() discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck)); } +void ChannelList::UsePanedHack(Gtk::Paned& paned) { + paned.property_position().signal_changed().connect(sigc::mem_fun(*this, &ChannelList::OnPanedPositionChanged)); +} + +void ChannelList::OnPanedPositionChanged() { + m_view.queue_draw(); +} + void ChannelList::UpdateListing() { m_updating_listing = true; diff --git a/src/components/channels.hpp b/src/components/channels.hpp index d279907..2f4d045 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -25,7 +25,11 @@ public: void UseExpansionState(const ExpansionStateRoot &state); ExpansionStateRoot GetExpansionState() const; + void UsePanedHack(Gtk::Paned &paned); + protected: + void OnPanedPositionChanged(); + void UpdateNewGuild(const GuildData &guild); void UpdateRemoveGuild(Snowflake id); void UpdateRemoveChannel(Snowflake id); diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index a96668c..f507802 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -240,7 +240,7 @@ void CellRendererChannels::render_vfunc_guild(const Cairo::RefPtrfill(); } - UnreadRenderer::RenderUnreadOnGuild(m_property_id.get_value(), cr, background_area, cell_area); + UnreadRenderer::RenderUnreadOnGuild(m_property_id.get_value(), widget, cr, background_area, cell_area); } // category @@ -337,7 +337,7 @@ void CellRendererChannels::render_vfunc_channel(const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { +void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { // maybe have DiscordClient track this? auto &discord = Abaddon::Get().GetDiscordClient(); const auto channels = discord.GetChannelsInGuild(id); @@ -23,15 +23,38 @@ void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtrfill(); } -void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { +void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { const auto state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); - if (state >= 0) { - cr->set_source_rgb(1.0, 1.0, 1.0); - const auto x = background_area.get_x(); - const auto y = background_area.get_y(); - const auto w = background_area.get_width(); - const auto h = background_area.get_height(); - cr->rectangle(x, y, 3, h); - cr->fill(); + if (state < 0) return; + cr->set_source_rgb(1.0, 1.0, 1.0); + const auto x = background_area.get_x(); + const auto y = background_area.get_y(); + const auto w = background_area.get_width(); + const auto h = background_area.get_height(); + cr->rectangle(x, y, 3, h); + cr->fill(); + + if (state < 1) return; + auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); + const auto edge = paned->get_position(); + + // surely this shouldnt be run every draw? + // https://developer-old.gnome.org/gtkmm-tutorial/3.22/sec-drawing-text.html.en + + Pango::FontDescription font; + font.set_family("sans 14"); + font.set_weight(Pango::WEIGHT_BOLD); + + auto layout = widget.create_pango_layout(std::to_string(state)); + layout->set_font_description(font); + layout->set_alignment(Pango::ALIGN_RIGHT); + + int width, height; + layout->get_pixel_size(width, height); + { + const auto x = cell_area.get_x() + std::min(edge, cell_area.get_width()) - 14 - 2; + const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0; + cr->move_to(x, y); + layout->show_in_cairo_context(cr); } } diff --git a/src/components/unreadrenderer.hpp b/src/components/unreadrenderer.hpp index 30446fa..1b4ddc2 100644 --- a/src/components/unreadrenderer.hpp +++ b/src/components/unreadrenderer.hpp @@ -1,10 +1,11 @@ #pragma once #include #include +#include #include "discord/snowflake.hpp" class UnreadRenderer { public: - static void RenderUnreadOnGuild(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); - static void RenderUnreadOnChannel(Snowflake id, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); + static void RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); + static void RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area); }; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index a8ceb5b..f21946f 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1467,9 +1467,10 @@ void DiscordClient::HandleGatewayMessageCreate(const GatewayMessage &msg) { if (data.GuildID.has_value()) AddUserToGuild(data.Author.ID, *data.GuildID); m_last_message_id[data.ChannelID] = data.ID; - const auto iter = m_unread.find(data.ChannelID); - if (iter == m_unread.end()) - m_unread[data.ChannelID] = 0; + m_unread[data.ChannelID]; + if (data.DoesMention(GetUserData().ID)) { + m_unread[data.ChannelID]++; + } m_signal_message_create.emit(data); } diff --git a/src/discord/message.cpp b/src/discord/message.cpp index 70c557d..93d57c2 100644 --- a/src/discord/message.cpp +++ b/src/discord/message.cpp @@ -263,3 +263,9 @@ bool Message::IsDeleted() const { bool Message::IsEdited() const { return m_edited; } + +bool Message::DoesMention(Snowflake id) const noexcept { + return std::any_of(Mentions.begin(), Mentions.end(), [id](const UserData &user) { + return user.ID == id; + }); +} diff --git a/src/discord/message.hpp b/src/discord/message.hpp index 56f4c0f..244b572 100644 --- a/src/discord/message.hpp +++ b/src/discord/message.hpp @@ -212,6 +212,8 @@ struct Message { bool IsDeleted() const; bool IsEdited() const; + bool DoesMention(Snowflake id) const noexcept; + private: bool m_deleted = false; bool m_edited = false; diff --git a/src/windows/mainwindow.cpp b/src/windows/mainwindow.cpp index 3fc0b0d..d171a03 100644 --- a/src/windows/mainwindow.cpp +++ b/src/windows/mainwindow.cpp @@ -153,6 +153,7 @@ MainWindow::MainWindow() m_chan_content_paned.set_position(200); m_chan_content_paned.show(); m_content_box.add(m_chan_content_paned); + m_channel_list.UsePanedHack(m_chan_content_paned); m_content_members_paned.pack1(m_content_stack); m_content_members_paned.pack2(*member_list); -- cgit v1.2.3 From 46ab760a56430463f216467ec227453402ed43de Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 10 Dec 2021 01:41:19 -0500 Subject: render total mentions on guild, redraw on message create --- src/components/channels.cpp | 14 +++++--- src/components/unreadrenderer.cpp | 75 ++++++++++++++++++++++++++++----------- 2 files changed, 63 insertions(+), 26 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index c83b5f8..e91ed92 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -176,7 +176,7 @@ ChannelList::ChannelList() discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck)); } -void ChannelList::UsePanedHack(Gtk::Paned& paned) { +void ChannelList::UsePanedHack(Gtk::Paned &paned) { paned.property_position().signal_changed().connect(sigc::mem_fun(*this, &ChannelList::OnPanedPositionChanged)); } @@ -718,12 +718,16 @@ void ChannelList::OnMessageAck(const MessageAckData &data) { void ChannelList::OnMessageCreate(const Message &msg) { auto iter = GetIteratorForChannelFromID(msg.ChannelID); - m_model->row_changed(m_model->get_path(iter), iter); // redraw + if (iter) m_model->row_changed(m_model->get_path(iter), iter); // redraw const auto channel = Abaddon::Get().GetDiscordClient().GetChannel(msg.ChannelID); if (!channel.has_value()) return; - if (channel->Type != ChannelType::DM && channel->Type != ChannelType::GROUP_DM) return; - if (iter) - (*iter)[m_columns.m_sort] = -msg.ID; + if (channel->Type == ChannelType::DM || channel->Type == ChannelType::GROUP_DM) { + if (iter) + (*iter)[m_columns.m_sort] = -msg.ID; + } + if (channel->GuildID.has_value()) + if ((iter = GetIteratorForGuildFromID(*channel->GuildID))) + m_model->row_changed(m_model->get_path(iter), iter); } bool ChannelList::OnButtonPressEvent(GdkEventButton *ev) { diff --git a/src/components/unreadrenderer.cpp b/src/components/unreadrenderer.cpp index af0363e..4ac1739 100644 --- a/src/components/unreadrenderer.cpp +++ b/src/components/unreadrenderer.cpp @@ -1,15 +1,56 @@ #include "unreadrenderer.hpp" #include "abaddon.hpp" +constexpr static int MentionsRightPad = 7; +constexpr static double M_PI = 3.14159265358979; +constexpr static double M_PI_H = M_PI / 2.0; +constexpr static double M_PI_3_2 = M_PI * 3.0 / 2.0; + +void CairoPathRoundedRect(const Cairo::RefPtr &cr, double x, double y, double w, double h, double r) { + const double degrees = M_PI / 180.0; + + cr->begin_new_sub_path(); + cr->arc(x + w - r, y + r, r, -M_PI_H, 0); + cr->arc(x + w - r, y + h - r, r, 0, M_PI_H); + cr->arc(x + r, y + h - r, r, M_PI_H, M_PI); + cr->arc(x + r, y + r, r, M_PI, M_PI_3_2); + cr->close_path(); +} + +void RenderMentionsCount(const Cairo::RefPtr &cr, Gtk::Widget &widget, int mentions, int edge, const Gdk::Rectangle &cell_area) { + Pango::FontDescription font; + font.set_family("sans 14"); + //font.set_weight(Pango::WEIGHT_BOLD); + + auto layout = widget.create_pango_layout(std::to_string(mentions)); + layout->set_font_description(font); + layout->set_alignment(Pango::ALIGN_RIGHT); + + int width, height; + layout->get_pixel_size(width, height); + { + const auto x = cell_area.get_x() + edge - width - MentionsRightPad; + const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0; + CairoPathRoundedRect(cr, x - 4, y + 2, width + 8, height, 5); + cr->set_source_rgb(184.0 / 255.0, 37.0 / 255.0, 37.0 / 255.0); + cr->fill(); + cr->set_source_rgb(1.0, 1.0, 1.0); + cr->move_to(x, y); + layout->show_in_cairo_context(cr); + } +} + void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { // maybe have DiscordClient track this? + int total_mentions = 0; auto &discord = Abaddon::Get().GetDiscordClient(); const auto channels = discord.GetChannelsInGuild(id); bool has_unread = false; for (const auto &id : channels) { - if (Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id) >= 0) { + const int state = Abaddon::Get().GetDiscordClient().GetUnreadStateForChannel(id); + if (state >= 0) { has_unread = true; - break; + total_mentions += state; } } if (!has_unread) return; @@ -21,6 +62,14 @@ void UnreadRenderer::RenderUnreadOnGuild(Snowflake id, Gtk::Widget &widget, cons const auto h = background_area.get_height(); cr->rectangle(x, y + h / 2 - 24 / 2, 3, 24); cr->fill(); + + if (total_mentions < 1) return; + auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), cell_area.get_width()); + + RenderMentionsCount(cr, widget, total_mentions, edge, cell_area); + } } void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, const Cairo::RefPtr &cr, const Gdk::Rectangle &background_area, const Gdk::Rectangle &cell_area) { @@ -36,25 +85,9 @@ void UnreadRenderer::RenderUnreadOnChannel(Snowflake id, Gtk::Widget &widget, co if (state < 1) return; auto *paned = static_cast(widget.get_ancestor(Gtk::Paned::get_type())); - const auto edge = paned->get_position(); - - // surely this shouldnt be run every draw? - // https://developer-old.gnome.org/gtkmm-tutorial/3.22/sec-drawing-text.html.en - - Pango::FontDescription font; - font.set_family("sans 14"); - font.set_weight(Pango::WEIGHT_BOLD); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), cell_area.get_width()); - auto layout = widget.create_pango_layout(std::to_string(state)); - layout->set_font_description(font); - layout->set_alignment(Pango::ALIGN_RIGHT); - - int width, height; - layout->get_pixel_size(width, height); - { - const auto x = cell_area.get_x() + std::min(edge, cell_area.get_width()) - 14 - 2; - const auto y = cell_area.get_y() + cell_area.get_height() / 2.0 - height / 2.0; - cr->move_to(x, y); - layout->show_in_cairo_context(cr); + RenderMentionsCount(cr, widget, state, edge, cell_area); } } -- cgit v1.2.3 From 1d7529e60925c36af5aa5a8702e7a0827e646f00 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 17 Dec 2021 02:34:14 -0500 Subject: handle mute/unmute of channels (USER_GUILD_SETTINGS_UPDATE) --- src/components/channels.cpp | 12 ++++++++++++ src/components/channels.hpp | 2 ++ src/discord/discord.cpp | 37 +++++++++++++++++++++++++++++++++++++ src/discord/discord.hpp | 7 +++++++ src/discord/objects.cpp | 4 ++++ src/discord/objects.hpp | 7 +++++++ 6 files changed, 69 insertions(+) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index e91ed92..d49afca 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -174,6 +174,8 @@ ChannelList::ChannelList() discord.signal_removed_from_thread().connect(sigc::mem_fun(*this, &ChannelList::OnThreadRemoved)); discord.signal_guild_update().connect(sigc::mem_fun(*this, &ChannelList::UpdateGuild)); discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck)); + discord.signal_channel_muted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelMute)); + discord.signal_channel_unmuted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelUnmute)); } void ChannelList::UsePanedHack(Gtk::Paned &paned) { @@ -370,6 +372,16 @@ void ChannelList::DeleteThreadRow(Snowflake id) { m_model->erase(iter); } +void ChannelList::OnChannelMute(Snowflake id) { + if (auto iter = GetIteratorForChannelFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + +void ChannelList::OnChannelUnmute(Snowflake id) { + if (auto iter = GetIteratorForChannelFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + // create a temporary channel row for non-joined threads // and delete them when the active channel switches off of them if still not joined void ChannelList::SetActiveChannel(Snowflake id) { diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 2f4d045..16e6360 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -37,6 +37,8 @@ protected: void UpdateCreateChannel(const ChannelData &channel); void UpdateGuild(Snowflake id); void DeleteThreadRow(Snowflake id); + void OnChannelMute(Snowflake id); + void OnChannelUnmute(Snowflake id); void OnThreadJoined(Snowflake id); void OnThreadRemoved(Snowflake id); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index b54064a..9d711c8 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1342,6 +1342,9 @@ void DiscordClient::HandleGatewayMessage(std::string str) { case GatewayEvent::MESSAGE_ACK: { HandleGatewayMessageAck(m); } break; + case GatewayEvent::USER_GUILD_SETTINGS_UPDATE: { + HandleGatewayUserGuildSettingsUpdate(m); + } break; } } break; default: @@ -1845,6 +1848,31 @@ void DiscordClient::HandleGatewayMessageAck(const GatewayMessage &msg) { m_signal_message_ack.emit(data); } +void DiscordClient::HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg) { + UserGuildSettingsUpdateData data = msg.Data; + const auto channels = GetChannelsInGuild(data.Settings.GuildID); + std::set now_muted_channels; + for (const auto &override : data.Settings.ChannelOverrides) { + if (override.Muted) + now_muted_channels.insert(override.ChannelID); + } + for (const auto &channel_id : channels) { + const bool was_muted = IsChannelMuted(channel_id); + const bool now_muted = now_muted_channels.find(channel_id) != now_muted_channels.end(); + if (now_muted) { + m_muted_channels.insert(channel_id); + if (!was_muted) { + m_signal_channel_muted.emit(channel_id); + } + } else { + m_muted_channels.erase(channel_id); + if (was_muted) { + m_signal_channel_unmuted.emit(channel_id); + } + } + } +} + void DiscordClient::HandleGatewayReadySupplemental(const GatewayMessage &msg) { ReadySupplementalData data = msg.Data; for (const auto &p : data.MergedPresences.Friends) { @@ -2244,6 +2272,7 @@ void DiscordClient::LoadEventMap() { m_event_map["THREAD_UPDATE"] = GatewayEvent::THREAD_UPDATE; m_event_map["THREAD_MEMBER_LIST_UPDATE"] = GatewayEvent::THREAD_MEMBER_LIST_UPDATE; m_event_map["MESSAGE_ACK"] = GatewayEvent::MESSAGE_ACK; + m_event_map["USER_GUILD_SETTINGS_UPDATE"] = GatewayEvent::USER_GUILD_SETTINGS_UPDATE; } DiscordClient::type_signal_gateway_ready DiscordClient::signal_gateway_ready() { @@ -2422,6 +2451,14 @@ DiscordClient::type_signal_message_sent DiscordClient::signal_message_sent() { return m_signal_message_sent; } +DiscordClient::type_signal_channel_muted DiscordClient::signal_channel_muted() { + return m_signal_channel_muted; +} + +DiscordClient::type_signal_channel_unmuted DiscordClient::signal_channel_unmuted() { + return m_signal_channel_unmuted; +} + DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_fail() { return m_signal_message_send_fail; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index fbee775..90919b6 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -251,6 +251,7 @@ private: void HandleGatewayThreadUpdate(const GatewayMessage &msg); void HandleGatewayThreadMemberListUpdate(const GatewayMessage &msg); void HandleGatewayMessageAck(const GatewayMessage &msg); + void HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &msg); void HandleGatewayReadySupplemental(const GatewayMessage &msg); void HandleGatewayReconnect(const GatewayMessage &msg); void HandleGatewayInvalidSession(const GatewayMessage &msg); @@ -365,6 +366,8 @@ public: typedef sigc::signal type_signal_message_unpinned; typedef sigc::signal type_signal_message_pinned; typedef sigc::signal type_signal_message_sent; + typedef sigc::signal type_signal_channel_muted; + typedef sigc::signal type_signal_channel_unmuted; typedef sigc::signal type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode typedef sigc::signal type_signal_disconnected; // bool true if reconnecting @@ -413,6 +416,8 @@ public: type_signal_added_to_thread signal_added_to_thread(); type_signal_removed_from_thread signal_removed_from_thread(); type_signal_message_sent signal_message_sent(); + type_signal_channel_muted signal_channel_muted(); + type_signal_channel_unmuted signal_channel_unmuted(); type_signal_message_send_fail signal_message_send_fail(); type_signal_disconnected signal_disconnected(); type_signal_connected signal_connected(); @@ -461,6 +466,8 @@ protected: type_signal_removed_from_thread m_signal_removed_from_thread; type_signal_added_to_thread m_signal_added_to_thread; type_signal_message_sent m_signal_message_sent; + type_signal_channel_muted m_signal_channel_muted; + type_signal_channel_unmuted m_signal_channel_unmuted; type_signal_message_send_fail m_signal_message_send_fail; type_signal_disconnected m_signal_disconnected; type_signal_connected m_signal_connected; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index 8bf70c3..a41b664 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -586,3 +586,7 @@ void from_json(const nlohmann::json &j, MessageAckData &m) { void to_json(nlohmann::json &j, const AckBulkData &m) { j["read_states"] = m.ReadStates; } + +void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m) { + m.Settings = j; +} diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 8afbc19..69ad422 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -79,6 +79,7 @@ enum class GatewayEvent : int { THREAD_MEMBERS_UPDATE, THREAD_MEMBER_LIST_UPDATE, MESSAGE_ACK, + USER_GUILD_SETTINGS_UPDATE, }; enum class GatewayCloseCode : uint16_t { @@ -813,3 +814,9 @@ struct AckBulkData { friend void to_json(nlohmann::json &j, const AckBulkData &m); }; + +struct UserGuildSettingsUpdateData { + UserGuildSettingsEntry Settings; + + friend void from_json(const nlohmann::json &j, UserGuildSettingsUpdateData &m); +}; -- cgit v1.2.3 From f580535d3570de59125cc377b01188601c82b5b9 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 18 Dec 2021 01:58:29 -0500 Subject: add mute/unmute channel menu item --- src/components/channels.cpp | 24 +++++++++++++++++++-- src/components/channels.hpp | 2 ++ src/components/channelscellrenderer.cpp | 2 +- src/discord/discord.cpp | 33 +++++++++++++++++++++++++++- src/discord/discord.hpp | 2 ++ src/discord/objects.cpp | 38 +++++++++++++++++++++++++++++++-- src/discord/objects.hpp | 16 +++++++++++--- 7 files changed, 108 insertions(+), 9 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index d49afca..76fd69d 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1,8 +1,8 @@ -#include "channels.hpp" #include "abaddon.hpp" +#include "channels.hpp" #include "imgmanager.hpp" -#include "util.hpp" #include "statusindicator.hpp" +#include "util.hpp" #include #include #include @@ -117,7 +117,16 @@ ChannelList::ChannelList() m_menu_channel_mark_as_read.signal_activate().connect([this] { Abaddon::Get().GetDiscordClient().MarkChannelAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); }); + m_menu_channel_toggle_mute.signal_activate().connect([this] { + const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsChannelMuted(id)) + discord.UnmuteChannel(id, [](...) {}); + else + discord.MuteChannel(id, [](...) {}); + }); m_menu_channel.append(m_menu_channel_mark_as_read); + m_menu_channel.append(m_menu_channel_toggle_mute); m_menu_channel.append(m_menu_channel_copy_id); m_menu_channel.show_all(); @@ -158,6 +167,7 @@ ChannelList::ChannelList() m_menu_thread.append(m_menu_thread_unarchive); m_menu_thread.show_all(); + m_menu_channel.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnChannelSubmenuPopup)); m_menu_thread.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnThreadSubmenuPopup)); auto &discord = Abaddon::Get().GetDiscordClient(); @@ -805,6 +815,16 @@ void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeM m_model->erase(iter); } +void ChannelList::OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { + const auto iter = m_model->get_iter(m_path_for_menu); + if (!iter) return; + const auto id = static_cast((*iter)[m_columns.m_id]); + if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id)) + m_menu_channel_toggle_mute.set_label("Unmute"); + else + m_menu_channel_toggle_mute.set_label("Mute"); +} + void ChannelList::OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { m_menu_thread_archive.set_visible(false); m_menu_thread_unarchive.set_visible(false); diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 16e6360..bc7dfc2 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -115,6 +115,7 @@ protected: Gtk::Menu m_menu_channel; Gtk::MenuItem m_menu_channel_copy_id; Gtk::MenuItem m_menu_channel_mark_as_read; + Gtk::MenuItem m_menu_channel_toggle_mute; Gtk::Menu m_menu_dm; Gtk::MenuItem m_menu_dm_copy_id; @@ -126,6 +127,7 @@ protected: Gtk::MenuItem m_menu_thread_archive; Gtk::MenuItem m_menu_thread_unarchive; + void OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); bool m_updating_listing = false; diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index f507802..df9f03d 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -1,7 +1,7 @@ #include "channelscellrenderer.hpp" #include "abaddon.hpp" -#include #include "unreadrenderer.hpp" +#include CellRendererChannels::CellRendererChannels() : Glib::ObjectBase(typeid(CellRendererChannels)) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 9d711c8..1872985 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1,9 +1,11 @@ +#include "abaddon.hpp" #include "discord.hpp" #include "util.hpp" -#include "abaddon.hpp" #include #include +using namespace std::string_literals; + DiscordClient::DiscordClient(bool mem_store) : m_decompress_buf(InflateChunkSize) , m_store(mem_store) { @@ -909,6 +911,35 @@ void DiscordClient::MarkGuildAsRead(Snowflake guild_id, sigc::slot callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value()) return; + const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s; + nlohmann::json j; + j["channel_overrides"][std::to_string(channel_id)]["mute_config"] = MuteConfigData { std::nullopt, -1 }; + j["channel_overrides"][std::to_string(channel_id)]["muted"] = true; + m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::UnmuteChannel(Snowflake channel_id, sigc::slot callback) { + const auto channel = GetChannel(channel_id); + if (!channel.has_value()) return; + const auto guild_id_path = channel->GuildID.has_value() ? std::to_string(*channel->GuildID) : "@me"s; + nlohmann::json j; + j["channel_overrides"][std::to_string(channel_id)]["muted"] = false; + m_http.MakePATCH("/users/@me/guilds/" + guild_id_path + "/settings", j.dump(), [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + void DiscordClient::FetchPinned(Snowflake id, sigc::slot, DiscordError code)> callback) { // return from db if we know the pins have already been requested if (m_channels_pinned_requested.find(id) != m_channels_pinned_requested.end()) { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 90919b6..9e526f9 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -140,6 +140,8 @@ public: void UnArchiveThread(Snowflake channel_id, sigc::slot callback); void MarkChannelAsRead(Snowflake channel_id, sigc::slot callback); void MarkGuildAsRead(Snowflake guild_id, sigc::slot callback); + void MuteChannel(Snowflake channel_id, sigc::slot callback); + void UnmuteChannel(Snowflake channel_id, sigc::slot callback); bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const; bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const; diff --git a/src/discord/objects.cpp b/src/discord/objects.cpp index a41b664..9e3741f 100644 --- a/src/discord/objects.cpp +++ b/src/discord/objects.cpp @@ -143,6 +143,27 @@ void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m) { JS_D("channel_id", m.ChannelID); } +void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m) { + j["channel_id"] = m.ChannelID; + j["collapsed"] = m.Collapsed; + j["message_notifications"] = m.MessageNotifications; + j["mute_config"] = m.MuteConfig; + j["muted"] = m.Muted; +} + +void from_json(const nlohmann::json &j, MuteConfigData &m) { + JS_ON("end_time", m.EndTime); + JS_D("selected_time_window", m.SelectedTimeWindow); +} + +void to_json(nlohmann::json &j, const MuteConfigData &m) { + if (m.EndTime.has_value()) + j["end_time"] = *m.EndTime; + else + j["end_time"] = nullptr; + j["selected_time_window"] = m.SelectedTimeWindow; +} + void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m) { JS_D("version", m.Version); JS_D("suppress_roles", m.SuppressRoles); @@ -151,13 +172,26 @@ void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m) { JS_D("mobile_push", m.MobilePush); JS_D("message_notifications", m.MessageNotifications); JS_D("hide_muted_channels", m.HideMutedChannels); - JS_D("guild_id", m.GuildID); + JS_N("guild_id", m.GuildID); JS_D("channel_overrides", m.ChannelOverrides); } +void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m) { + j["channel_overrides"] = m.ChannelOverrides; + j["guild_id"] = m.GuildID; + j["hide_muted_channels"] = m.HideMutedChannels; + j["message_notifications"] = m.MessageNotifications; + j["mobile_push"] = m.MobilePush; + j["mute_config"] = m.MuteConfig; + j["muted"] = m.Muted; + j["suppress_everyone"] = m.SuppressEveryone; + j["suppress_roles"] = m.SuppressRoles; + j["version"] = m.Version; +} + void from_json(const nlohmann::json &j, UserGuildSettingsData &m) { JS_D("version", m.Version); - JS_D("partial", m.IsParital); + JS_D("partial", m.IsPartial); JS_D("entries", m.Entries); } diff --git a/src/discord/objects.hpp b/src/discord/objects.hpp index 69ad422..c72361b 100644 --- a/src/discord/objects.hpp +++ b/src/discord/objects.hpp @@ -244,14 +244,23 @@ struct ReadStateData { friend void from_json(const nlohmann::json &j, ReadStateData &m); }; +struct MuteConfigData { + std::optional EndTime; // nullopt is encoded as null + int SelectedTimeWindow; + + friend void from_json(const nlohmann::json &j, MuteConfigData &m); + friend void to_json(nlohmann::json &j, const MuteConfigData &m); +}; + struct UserGuildSettingsChannelOverride { bool Muted; - // MuteConfig + MuteConfigData MuteConfig; int MessageNotifications; bool Collapsed; Snowflake ChannelID; friend void from_json(const nlohmann::json &j, UserGuildSettingsChannelOverride &m); + friend void to_json(nlohmann::json &j, const UserGuildSettingsChannelOverride &m); }; struct UserGuildSettingsEntry { @@ -259,7 +268,7 @@ struct UserGuildSettingsEntry { bool SuppressRoles; bool SuppressEveryone; bool Muted; - // MuteConfig + MuteConfigData MuteConfig; bool MobilePush; int MessageNotifications; bool HideMutedChannels; @@ -267,11 +276,12 @@ struct UserGuildSettingsEntry { std::vector ChannelOverrides; friend void from_json(const nlohmann::json &j, UserGuildSettingsEntry &m); + friend void to_json(nlohmann::json &j, const UserGuildSettingsEntry &m); }; struct UserGuildSettingsData { int Version; - bool IsParital; + bool IsPartial; std::vector Entries; friend void from_json(const nlohmann::json &j, UserGuildSettingsData &m); -- cgit v1.2.3 From 9fd0d404a128923c29c7cb4b990ca88cc719e5d6 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Mon, 20 Dec 2021 02:13:18 -0500 Subject: mark channel being switched off as read when switching --- src/components/channels.cpp | 6 ++++++ src/components/channels.hpp | 2 ++ 2 files changed, 8 insertions(+) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 76fd69d..c6606e4 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -395,6 +395,12 @@ void ChannelList::OnChannelUnmute(Snowflake id) { // create a temporary channel row for non-joined threads // and delete them when the active channel switches off of them if still not joined void ChannelList::SetActiveChannel(Snowflake id) { + // mark channel as read when switching off + if (m_active_channel.IsValid()) + Abaddon::Get().GetDiscordClient().MarkChannelAsRead(m_active_channel, [](...) {}); + + m_active_channel = id; + if (m_temporary_thread_row) { const auto thread_id = static_cast((*m_temporary_thread_row)[m_columns.m_id]); const auto thread = Abaddon::Get().GetDiscordClient().GetChannel(thread_id); diff --git a/src/components/channels.hpp b/src/components/channels.hpp index bc7dfc2..dd861b7 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -132,6 +132,8 @@ protected: bool m_updating_listing = false; + Snowflake m_active_channel; + public: typedef sigc::signal type_signal_action_channel_item_select; typedef sigc::signal type_signal_action_guild_leave; -- cgit v1.2.3 From ea7464722b00501e077b4cb8ae267200ee6df220 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 29 Dec 2021 23:51:12 -0500 Subject: handle change of mute state for guilds --- src/components/channels.cpp | 12 ++++++++++++ src/components/channels.hpp | 2 ++ src/discord/discord.cpp | 28 ++++++++++++++++++++++++++++ src/discord/discord.hpp | 6 ++++++ 4 files changed, 48 insertions(+) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index c6606e4..b631023 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -186,6 +186,8 @@ ChannelList::ChannelList() discord.signal_message_ack().connect(sigc::mem_fun(*this, &ChannelList::OnMessageAck)); discord.signal_channel_muted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelMute)); discord.signal_channel_unmuted().connect(sigc::mem_fun(*this, &ChannelList::OnChannelUnmute)); + discord.signal_guild_muted().connect(sigc::mem_fun(*this, &ChannelList::OnGuildMute)); + discord.signal_guild_unmuted().connect(sigc::mem_fun(*this, &ChannelList::OnGuildUnmute)); } void ChannelList::UsePanedHack(Gtk::Paned &paned) { @@ -392,6 +394,16 @@ void ChannelList::OnChannelUnmute(Snowflake id) { m_model->row_changed(m_model->get_path(iter), iter); } +void ChannelList::OnGuildMute(Snowflake id) { + if (auto iter = GetIteratorForGuildFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + +void ChannelList::OnGuildUnmute(Snowflake id) { + if (auto iter = GetIteratorForGuildFromID(id)) + m_model->row_changed(m_model->get_path(iter), iter); +} + // create a temporary channel row for non-joined threads // and delete them when the active channel switches off of them if still not joined void ChannelList::SetActiveChannel(Snowflake id) { diff --git a/src/components/channels.hpp b/src/components/channels.hpp index dd861b7..dd2a4d9 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -39,6 +39,8 @@ protected: void DeleteThreadRow(Snowflake id); void OnChannelMute(Snowflake id); void OnChannelUnmute(Snowflake id); + void OnGuildMute(Snowflake id); + void OnGuildUnmute(Snowflake id); void OnThreadJoined(Snowflake id); void OnThreadRemoved(Snowflake id); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 7b0db38..0536a4e 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1919,6 +1919,26 @@ void DiscordClient::HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &m const auto channels = GetChannelsInGuild(data.Settings.GuildID); std::set now_muted_channels; const auto now = Snowflake::FromNow(); + + const bool was_muted = IsGuildMuted(data.Settings.GuildID); + bool now_muted = false; + if (data.Settings.Muted) { + if (data.Settings.MuteConfig.EndTime.has_value()) { + const auto end = Snowflake::FromISO8601(*data.Settings.MuteConfig.EndTime); + if (end.IsValid() && end > now) + now_muted = true; + } else { + now_muted = true; + } + } + if (was_muted && !now_muted) { + m_muted_guilds.erase(data.Settings.GuildID); + m_signal_guild_unmuted.emit(data.Settings.GuildID); + } else if (!was_muted && now_muted) { + m_muted_guilds.insert(data.Settings.GuildID); + m_signal_guild_muted.emit(data.Settings.GuildID); + } + for (const auto &override : data.Settings.ChannelOverrides) { if (override.Muted) { if (override.MuteConfig.EndTime.has_value()) { @@ -2575,6 +2595,14 @@ DiscordClient::type_signal_channel_unmuted DiscordClient::signal_channel_unmuted return m_signal_channel_unmuted; } +DiscordClient::type_signal_guild_muted DiscordClient::signal_guild_muted() { + return m_signal_guild_muted; +} + +DiscordClient::type_signal_guild_unmuted DiscordClient::signal_guild_unmuted() { + return m_signal_guild_unmuted; +} + DiscordClient::type_signal_message_send_fail DiscordClient::signal_message_send_fail() { return m_signal_message_send_fail; } diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 4374396..1f8daa5 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -372,6 +372,8 @@ public: typedef sigc::signal type_signal_message_sent; typedef sigc::signal type_signal_channel_muted; typedef sigc::signal type_signal_channel_unmuted; + typedef sigc::signal type_signal_guild_muted; + typedef sigc::signal type_signal_guild_unmuted; typedef sigc::signal type_signal_message_send_fail; // retry after param will be 0 if it failed for a reason that isnt slowmode typedef sigc::signal type_signal_disconnected; // bool true if reconnecting @@ -422,6 +424,8 @@ public: type_signal_message_sent signal_message_sent(); type_signal_channel_muted signal_channel_muted(); type_signal_channel_unmuted signal_channel_unmuted(); + type_signal_guild_muted signal_guild_muted(); + type_signal_guild_unmuted signal_guild_unmuted(); type_signal_message_send_fail signal_message_send_fail(); type_signal_disconnected signal_disconnected(); type_signal_connected signal_connected(); @@ -472,6 +476,8 @@ protected: type_signal_message_sent m_signal_message_sent; type_signal_channel_muted m_signal_channel_muted; type_signal_channel_unmuted m_signal_channel_unmuted; + type_signal_guild_muted m_signal_guild_muted; + type_signal_guild_unmuted m_signal_guild_unmuted; type_signal_message_send_fail m_signal_message_send_fail; type_signal_disconnected m_signal_disconnected; type_signal_connected m_signal_connected; -- cgit v1.2.3 From d7bb6049e130dcd3a36e1a07fb39d24468c4889b Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Thu, 30 Dec 2021 01:24:55 -0500 Subject: add mute/unmute guild menu item --- src/components/channels.cpp | 24 ++++++++++++++++++++++-- src/components/channels.hpp | 2 ++ src/discord/discord.cpp | 18 ++++++++++++++++++ src/discord/discord.hpp | 2 ++ 4 files changed, 44 insertions(+), 2 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index b631023..290093d 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -98,9 +98,18 @@ ChannelList::ChannelList() m_menu_guild_mark_as_read.signal_activate().connect([this] { Abaddon::Get().GetDiscordClient().MarkGuildAsRead(static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]), [](...) {}); }); + m_menu_guild_toggle_mute.signal_activate().connect([this] { + const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsGuildMuted(id)) + discord.UnmuteGuild(id, NOOP_CALLBACK); + else + discord.MuteGuild(id, NOOP_CALLBACK); + }); m_menu_guild.append(m_menu_guild_mark_as_read); m_menu_guild.append(m_menu_guild_settings); m_menu_guild.append(m_menu_guild_leave); + m_menu_guild.append(m_menu_guild_toggle_mute); m_menu_guild.append(m_menu_guild_copy_id); m_menu_guild.show_all(); @@ -121,9 +130,9 @@ ChannelList::ChannelList() const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); auto &discord = Abaddon::Get().GetDiscordClient(); if (discord.IsChannelMuted(id)) - discord.UnmuteChannel(id, [](...) {}); + discord.UnmuteChannel(id, NOOP_CALLBACK); else - discord.MuteChannel(id, [](...) {}); + discord.MuteChannel(id, NOOP_CALLBACK); }); m_menu_channel.append(m_menu_channel_mark_as_read); m_menu_channel.append(m_menu_channel_toggle_mute); @@ -167,6 +176,7 @@ ChannelList::ChannelList() m_menu_thread.append(m_menu_thread_unarchive); m_menu_thread.show_all(); + m_menu_guild.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnGuildSubmenuPopup)); m_menu_channel.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnChannelSubmenuPopup)); m_menu_thread.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnThreadSubmenuPopup)); @@ -833,6 +843,16 @@ void ChannelList::MoveRow(const Gtk::TreeModel::iterator &iter, const Gtk::TreeM m_model->erase(iter); } +void ChannelList::OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { + const auto iter = m_model->get_iter(m_path_for_menu); + if (!iter) return; + const auto id = static_cast((*iter)[m_columns.m_id]); + if (Abaddon::Get().GetDiscordClient().IsGuildMuted(id)) + m_menu_guild_toggle_mute.set_label("Unmute"); + else + m_menu_guild_toggle_mute.set_label("Mute"); +} + void ChannelList::OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { const auto iter = m_model->get_iter(m_path_for_menu); if (!iter) return; diff --git a/src/components/channels.hpp b/src/components/channels.hpp index dd2a4d9..40e8358 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -110,6 +110,7 @@ protected: Gtk::MenuItem m_menu_guild_settings; Gtk::MenuItem m_menu_guild_leave; Gtk::MenuItem m_menu_guild_mark_as_read; + Gtk::MenuItem m_menu_guild_toggle_mute; Gtk::Menu m_menu_category; Gtk::MenuItem m_menu_category_copy_id; @@ -129,6 +130,7 @@ protected: Gtk::MenuItem m_menu_thread_archive; Gtk::MenuItem m_menu_thread_unarchive; + void OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 0536a4e..6819375 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -960,6 +960,24 @@ void DiscordClient::MarkAllAsRead(sigc::slot callback) }); } +void DiscordClient::MuteGuild(Snowflake id, sigc::slot callback) { + m_http.MakePATCH("/users/@me/guilds/" + std::to_string(id) + "/settings", R"({"muted":true})", [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + +void DiscordClient::UnmuteGuild(Snowflake id, sigc::slot callback) { + m_http.MakePATCH("/users/@me/guilds/" + std::to_string(id) + "/settings", R"({"muted":false})", [this, callback](const http::response_type &response) { + if (CheckCode(response)) + callback(DiscordError::NONE); + else + callback(GetCodeFromResponse(response)); + }); +} + void DiscordClient::FetchPinned(Snowflake id, sigc::slot, DiscordError code)> callback) { // return from db if we know the pins have already been requested if (m_channels_pinned_requested.find(id) != m_channels_pinned_requested.end()) { diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 1f8daa5..44f02bb 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -143,6 +143,8 @@ public: void MuteChannel(Snowflake channel_id, sigc::slot callback); void UnmuteChannel(Snowflake channel_id, sigc::slot callback); void MarkAllAsRead(sigc::slot callback); + void MuteGuild(Snowflake id, sigc::slot callback); + void UnmuteGuild(Snowflake id, sigc::slot callback); bool CanModifyRole(Snowflake guild_id, Snowflake role_id) const; bool CanModifyRole(Snowflake guild_id, Snowflake role_id, Snowflake user_id) const; -- cgit v1.2.3 From 5338eab3a5f9a15fbc5dd049bf45fe16d983a8cb Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Fri, 31 Dec 2021 16:42:06 -0500 Subject: speed up connection speed a good bit loading save state was slow so now theres a temporary lookup table --- src/abaddon.cpp | 6 +++++- src/components/channels.cpp | 22 +++++++++++++--------- src/components/channels.hpp | 4 ++++ 3 files changed, 22 insertions(+), 10 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/abaddon.cpp b/src/abaddon.cpp index 2b0cda0..51f8052 100644 --- a/src/abaddon.cpp +++ b/src/abaddon.cpp @@ -388,7 +388,11 @@ void Abaddon::SaveState() { } void Abaddon::LoadState() { - if (!GetSettings().SaveState) return; + if (!GetSettings().SaveState) { + // call with empty data to purge the temporary table + m_main_window->GetChannelList()->UseExpansionState({}); + return; + } const auto data = ReadWholeFile(GetStateCachePath("/state.json")); if (data.empty()) return; diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 455d3b1..148f26f 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -1,11 +1,11 @@ +#include "abaddon.hpp" #include "channels.hpp" +#include "imgmanager.hpp" +#include "statusindicator.hpp" +#include "util.hpp" #include #include #include -#include "abaddon.hpp" -#include "imgmanager.hpp" -#include "util.hpp" -#include "statusindicator.hpp" ChannelList::ChannelList() : Glib::ObjectBase(typeid(ChannelList)) @@ -231,7 +231,6 @@ void ChannelList::UpdateChannel(Snowflake id) { } void ChannelList::UpdateCreateChannel(const ChannelData &channel) { - ; if (channel.Type == ChannelType::GUILD_CATEGORY) return (void)UpdateCreateChannelCategory(channel); if (channel.Type == ChannelType::DM || channel.Type == ChannelType::GROUP_DM) return UpdateCreateDMChannel(channel); if (channel.Type != ChannelType::GUILD_TEXT && channel.Type != ChannelType::GUILD_NEWS) return; @@ -378,11 +377,11 @@ void ChannelList::UseExpansionState(const ExpansionStateRoot &root) { auto recurse = [this](auto &self, const ExpansionStateRoot &root) -> void { // and these are only channels for (const auto &[id, state] : root.Children) { - if (const auto iter = GetIteratorForChannelFromID(id)) { + if (const auto iter = m_tmp_channel_map.find(id); iter != m_tmp_channel_map.end()) { if (state.IsExpanded) - m_view.expand_row(m_model->get_path(iter), false); + m_view.expand_row(m_model->get_path(iter->second), false); else - m_view.collapse_row(m_model->get_path(iter)); + m_view.collapse_row(m_model->get_path(iter->second)); } self(self, state.Children); @@ -400,6 +399,8 @@ void ChannelList::UseExpansionState(const ExpansionStateRoot &root) { recurse(recurse, state.Children); } + + m_tmp_channel_map.clear(); } ExpansionStateRoot ChannelList::GetExpansionState() const { @@ -480,7 +481,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { if (it == threads.end()) return; for (const auto &thread : it->second) - CreateThreadRow(row.children(), thread); + m_tmp_channel_map[thread.ID] = CreateThreadRow(row.children(), thread); }; for (const auto &channel : orphan_channels) { @@ -491,6 +492,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { channel_row[m_columns.m_sort] = *channel.Position + OrphanChannelSortOffset; channel_row[m_columns.m_nsfw] = channel.NSFW(); add_threads(channel, channel_row); + m_tmp_channel_map[channel.ID] = channel_row; } for (const auto &[category_id, channels] : categories) { @@ -502,6 +504,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { cat_row[m_columns.m_name] = Glib::Markup::escape_text(*category->Name); cat_row[m_columns.m_sort] = *category->Position; cat_row[m_columns.m_expanded] = true; + m_tmp_channel_map[category_id] = cat_row; // m_view.expand_row wont work because it might not have channels for (const auto &channel : channels) { @@ -512,6 +515,7 @@ Gtk::TreeModel::iterator ChannelList::AddGuild(const GuildData &guild) { channel_row[m_columns.m_sort] = *channel.Position; channel_row[m_columns.m_nsfw] = channel.NSFW(); add_threads(channel, channel_row); + m_tmp_channel_map[channel.ID] = channel_row; } } diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 6ab8174..a255f01 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -120,6 +120,10 @@ protected: bool m_updating_listing = false; + // (GetIteratorForChannelFromID is rather slow) + // only temporary since i dont want to worry about maintaining this map + std::unordered_map m_tmp_channel_map; + public: typedef sigc::signal type_signal_action_channel_item_select; typedef sigc::signal type_signal_action_guild_leave; -- cgit v1.2.3 From 40106ddeb11a755864e446920b74739f1ec21c57 Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Wed, 5 Jan 2022 03:52:20 -0500 Subject: handle mutable categories --- src/components/channels.cpp | 21 ++++++++++++++++++++- src/components/channels.hpp | 2 ++ src/components/channelscellrenderer.cpp | 4 ++++ src/discord/channel.cpp | 8 ++++++++ src/discord/channel.hpp | 2 ++ src/discord/discord.cpp | 33 +++++++++++++++++++++++++++++++-- src/discord/discord.hpp | 2 ++ src/discord/store.cpp | 23 +++++++++++++++++++++++ src/discord/store.hpp | 2 ++ 9 files changed, 94 insertions(+), 3 deletions(-) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index 7ad2dd0..c4ba2c0 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -116,7 +116,15 @@ ChannelList::ChannelList() m_menu_category_copy_id.signal_activate().connect([this] { Gtk::Clipboard::get()->set_text(std::to_string((*m_model->get_iter(m_path_for_menu))[m_columns.m_id])); }); - + m_menu_category_toggle_mute.signal_activate().connect([this] { + const auto id = static_cast((*m_model->get_iter(m_path_for_menu))[m_columns.m_id]); + auto &discord = Abaddon::Get().GetDiscordClient(); + if (discord.IsChannelMuted(id)) + discord.UnmuteChannel(id, NOOP_CALLBACK); + else + discord.MuteChannel(id, NOOP_CALLBACK); + }); + m_menu_category.append(m_menu_category_toggle_mute); m_menu_category.append(m_menu_category_copy_id); m_menu_category.show_all(); @@ -177,6 +185,7 @@ ChannelList::ChannelList() m_menu_thread.show_all(); m_menu_guild.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnGuildSubmenuPopup)); + m_menu_category.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnCategorySubmenuPopup)); m_menu_channel.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnChannelSubmenuPopup)); m_menu_thread.signal_popped_up().connect(sigc::mem_fun(*this, &ChannelList::OnThreadSubmenuPopup)); @@ -857,6 +866,16 @@ void ChannelList::OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const m_menu_guild_toggle_mute.set_label("Mute"); } +void ChannelList::OnCategorySubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { + const auto iter = m_model->get_iter(m_path_for_menu); + if (!iter) return; + const auto id = static_cast((*iter)[m_columns.m_id]); + if (Abaddon::Get().GetDiscordClient().IsChannelMuted(id)) + m_menu_category_toggle_mute.set_label("Unmute"); + else + m_menu_category_toggle_mute.set_label("Mute"); +} + void ChannelList::OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y) { const auto iter = m_model->get_iter(m_path_for_menu); if (!iter) return; diff --git a/src/components/channels.hpp b/src/components/channels.hpp index 6970729..ba75be8 100644 --- a/src/components/channels.hpp +++ b/src/components/channels.hpp @@ -114,6 +114,7 @@ protected: Gtk::Menu m_menu_category; Gtk::MenuItem m_menu_category_copy_id; + Gtk::MenuItem m_menu_category_toggle_mute; Gtk::Menu m_menu_channel; Gtk::MenuItem m_menu_channel_copy_id; @@ -131,6 +132,7 @@ protected: Gtk::MenuItem m_menu_thread_unarchive; void OnGuildSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); + void OnCategorySubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnChannelSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); void OnThreadSubmenuPopup(const Gdk::Rectangle *flipped_rect, const Gdk::Rectangle *final_rect, bool flipped_x, bool flipped_y); diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index 325d45a..b998442 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -327,7 +327,11 @@ void CellRendererChannels::render_vfunc_category(const Cairo::RefPtr ChannelData::GetChildIDs() const { + return Abaddon::Get().GetDiscordClient().GetChildChannelIDs(ID); +} + std::optional ChannelData::GetOverwrite(Snowflake id) const { return Abaddon::Get().GetDiscordClient().GetPermissionOverwrite(ID, id); } diff --git a/src/discord/channel.hpp b/src/discord/channel.hpp index fd76d3a..195a09a 100644 --- a/src/discord/channel.hpp +++ b/src/discord/channel.hpp @@ -88,6 +88,8 @@ struct ChannelData { bool IsDM() const noexcept; bool IsThread() const noexcept; bool IsJoinedThread() const; + bool IsCategory() const noexcept; + std::vector GetChildIDs() const; std::optional GetOverwrite(Snowflake id) const; std::vector GetDMRecipients() const; }; diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index 6819375..529da0b 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -307,6 +307,10 @@ void DiscordClient::GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot DiscordClient::GetChildChannelIDs(Snowflake parent_id) const { + return m_store.GetChannelIDsWithParentID(parent_id); +} + bool DiscordClient::IsThreadJoined(Snowflake thread_id) const { return std::find(m_joined_threads.begin(), m_joined_threads.end(), thread_id) != m_joined_threads.end(); } @@ -1184,10 +1188,15 @@ bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) co const auto channels = GetChannelsInGuild(id); for (const auto channel_id : channels) { const auto channel_unread = GetUnreadStateForChannel(channel_id); - if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id)) - has_any_unread = true; if (channel_unread > -1) total_mentions += channel_unread; + + // channels under muted categories wont contribute to unread state + if (const auto iter = m_channel_muted_parent.find(channel_id); iter != m_channel_muted_parent.end()) + continue; + + if (!has_any_unread && channel_unread > -1 && !IsChannelMuted(channel_id)) + has_any_unread = true; } return has_any_unread; } @@ -1974,11 +1983,19 @@ void DiscordClient::HandleGatewayUserGuildSettingsUpdate(const GatewayMessage &m if (now_muted) { m_muted_channels.insert(channel_id); if (!was_muted) { + if (const auto chan = GetChannel(channel_id); chan.has_value() && chan->IsCategory()) + for (const auto child_id : chan->GetChildIDs()) + m_channel_muted_parent.insert(child_id); + m_signal_channel_muted.emit(channel_id); } } else { m_muted_channels.erase(channel_id); if (was_muted) { + if (const auto chan = GetChannel(channel_id); chan.has_value() && chan->IsCategory()) + for (const auto child_id : chan->GetChildIDs()) + m_channel_muted_parent.erase(child_id); + m_signal_channel_unmuted.emit(channel_id); } } @@ -2357,6 +2374,14 @@ void DiscordClient::HandleReadyReadState(const ReadyEventData &data) { } void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { + // i dont like this implementation for muted categories but its rather simple and doesnt use a horriiible amount of ram + + std::unordered_map> category_children; + for (const auto &guild : data.Guilds) + for (const auto &channel : *guild.Channels) + if (channel.ParentID.has_value() && !channel.IsThread()) + category_children[*channel.ParentID].push_back(channel.ID); + const auto now = Snowflake::FromNow(); for (const auto &entry : data.GuildSettings.Entries) { // even if muted is true a guild/channel can be unmuted if the current time passes mute_config.end_time @@ -2371,6 +2396,10 @@ void DiscordClient::HandleReadyGuildSettings(const ReadyEventData &data) { } for (const auto &override : entry.ChannelOverrides) { if (override.Muted) { + if (const auto iter = category_children.find(override.ChannelID); iter != category_children.end()) + for (const auto child : iter->second) + m_channel_muted_parent.insert(child); + if (override.MuteConfig.EndTime.has_value()) { const auto end = Snowflake::FromISO8601(*override.MuteConfig.EndTime); if (end.IsValid() && end > now) diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 44f02bb..69cfcf7 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -82,6 +82,7 @@ public: std::vector GetActiveThreads(Snowflake channel_id) const; void GetArchivedPublicThreads(Snowflake channel_id, sigc::slot callback); void GetArchivedPrivateThreads(Snowflake channel_id, sigc::slot callback); + std::vector GetChildChannelIDs(Snowflake parent_id) const; bool IsThreadJoined(Snowflake thread_id) const; bool HasGuildPermission(Snowflake user_id, Snowflake guild_id, Permission perm) const; @@ -290,6 +291,7 @@ private: std::unordered_set m_muted_guilds; std::unordered_set m_muted_channels; std::unordered_map m_unread; + std::unordered_set m_channel_muted_parent; UserData m_user_data; UserSettings m_user_settings; diff --git a/src/discord/store.cpp b/src/discord/store.cpp index 5e4e3b3..aa97178 100644 --- a/src/discord/store.cpp +++ b/src/discord/store.cpp @@ -571,6 +571,21 @@ std::vector Store::GetActiveThreads(Snowflake channel_id) const { return ret; } +std::vector Store::GetChannelIDsWithParentID(Snowflake channel_id) const { + auto &s = m_stmt_get_chan_ids_parent; + + s->Bind(1, channel_id); + + std::vector ret; + while (s->FetchOne()) { + Snowflake x; + s->Get(0, x); + ret.push_back(x); + } + + return ret; +} + void Store::AddReaction(const MessageReactionAddObject &data, bool byself) { auto &s = m_stmt_add_reaction; @@ -2120,6 +2135,14 @@ bool Store::CreateStatements() { return false; } + m_stmt_get_chan_ids_parent = std::make_unique(m_db, R"( + SELECT id FROM channels WHERE parent_id = ? + )"); + if (!m_stmt_get_chan_ids_parent->OK()) { + fprintf(stderr, "failed to prepare get channel ids for parent statement: %s\n", m_db.ErrStr()); + return false; + } + return true; } diff --git a/src/discord/store.hpp b/src/discord/store.hpp index 715f280..4320807 100644 --- a/src/discord/store.hpp +++ b/src/discord/store.hpp @@ -43,6 +43,7 @@ public: std::vector GetMessagesBefore(Snowflake channel_id, Snowflake message_id, size_t limit) const; std::vector GetPinnedMessages(Snowflake channel_id) const; std::vector GetActiveThreads(Snowflake channel_id) const; // public + std::vector GetChannelIDsWithParentID(Snowflake channel_id) const; void AddReaction(const MessageReactionAddObject &data, bool byself); void RemoveReaction(const MessageReactionRemoveObject &data, bool byself); @@ -300,5 +301,6 @@ private: STMT(add_reaction); STMT(sub_reaction); STMT(get_reactions); + STMT(get_chan_ids_parent); #undef STMT }; -- cgit v1.2.3 From 604f2ffe3dc8978aebd6aa819b73374aa32d2f0e Mon Sep 17 00:00:00 2001 From: ouwou <26526779+ouwou@users.noreply.github.com> Date: Sat, 8 Jan 2022 20:03:12 -0500 Subject: show count of unread dms in header --- src/components/channels.cpp | 1 + src/components/channelscellrenderer.cpp | 7 +++++++ src/discord/discord.cpp | 8 ++++++++ src/discord/discord.hpp | 1 + 4 files changed, 17 insertions(+) (limited to 'src/components/channels.cpp') diff --git a/src/components/channels.cpp b/src/components/channels.cpp index c4ba2c0..28eb288 100644 --- a/src/components/channels.cpp +++ b/src/components/channels.cpp @@ -770,6 +770,7 @@ void ChannelList::UpdateCreateDMChannel(const ChannelData &dm) { void ChannelList::OnMessageAck(const MessageAckData &data) { // trick renderer into redrawing + m_model->row_changed(Gtk::TreeModel::Path("0"), m_model->get_iter("0")); // 0 is always path for dm header auto iter = GetIteratorForChannelFromID(data.ChannelID); if (iter) m_model->row_changed(m_model->get_path(iter), iter); auto channel = Abaddon::Get().GetDiscordClient().GetChannel(data.ChannelID); diff --git a/src/components/channelscellrenderer.cpp b/src/components/channelscellrenderer.cpp index b998442..4578020 100644 --- a/src/components/channelscellrenderer.cpp +++ b/src/components/channelscellrenderer.cpp @@ -465,6 +465,13 @@ void CellRendererChannels::render_vfunc_dmheader(const Cairo::RefPtr(widget.get_ancestor(Gtk::Paned::get_type())); + if (paned != nullptr) { + const auto edge = std::min(paned->get_position(), background_area.get_width()); + if (const auto unread = Abaddon::Get().GetDiscordClient().GetUnreadDMsCount(); unread > 0) + unread_render_mentions(cr, widget, unread, edge, background_area); + } } // dm (basically the same thing as guild) diff --git a/src/discord/discord.cpp b/src/discord/discord.cpp index dce5124..5b3cdb5 100644 --- a/src/discord/discord.cpp +++ b/src/discord/discord.cpp @@ -1202,6 +1202,14 @@ bool DiscordClient::GetUnreadStateForGuild(Snowflake id, int &total_mentions) co return has_any_unread; } +int DiscordClient::GetUnreadDMsCount() const { + const auto channels = GetPrivateChannels(); + int count = 0; + for (const auto channel_id : channels) + if (GetUnreadStateForChannel(channel_id) > -1) count++; + return count; +} + PresenceStatus DiscordClient::GetUserStatus(Snowflake id) const { auto it = m_user_to_status.find(id); if (it != m_user_to_status.end()) diff --git a/src/discord/discord.hpp b/src/discord/discord.hpp index 42c24fd..1a6aa14 100644 --- a/src/discord/discord.hpp +++ b/src/discord/discord.hpp @@ -194,6 +194,7 @@ public: bool IsGuildMuted(Snowflake id) const noexcept; int GetUnreadStateForChannel(Snowflake id) const noexcept; bool GetUnreadStateForGuild(Snowflake id, int &total_mentions) const noexcept; + int GetUnreadDMsCount() const; PresenceStatus GetUserStatus(Snowflake id) const; -- cgit v1.2.3