// // Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2024 // // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // #include "td/telegram/ThemeManager.h" #include "td/telegram/AuthManager.h" #include "td/telegram/BackgroundInfo.hpp" #include "td/telegram/Global.h" #include "td/telegram/logevent/LogEvent.h" #include "td/telegram/net/NetQueryCreator.h" #include "td/telegram/OptionManager.h" #include "td/telegram/Td.h" #include "td/telegram/TdDb.h" #include "td/telegram/telegram_api.h" #include "td/utils/algorithm.h" #include "td/utils/buffer.h" #include "td/utils/emoji.h" #include "td/utils/JsonBuilder.h" #include "td/utils/logging.h" #include "td/utils/Slice.h" #include "td/utils/tl_helpers.h" namespace td { static bool are_colors_valid(const vector &colors, size_t min_size, size_t max_size) { if (min_size > colors.size() || colors.size() > max_size) { return false; } for (auto &color : colors) { if (color < 0 || color > 0xFFFFFF) { return false; } } return true; } class GetChatThemesQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetChatThemesQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(int64 hash) { send_query(G()->net_query_creator().create(telegram_api::account_getChatThemes(hash))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetPeerColorsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetPeerColorsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(int32 hash) { send_query(G()->net_query_creator().create(telegram_api::help_getPeerColors(hash))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; class GetPeerProfileColorsQuery final : public Td::ResultHandler { Promise> promise_; public: explicit GetPeerProfileColorsQuery(Promise> &&promise) : promise_(std::move(promise)) { } void send(int32 hash) { send_query(G()->net_query_creator().create(telegram_api::help_getPeerProfileColors(hash))); } void on_result(BufferSlice packet) final { auto result_ptr = fetch_result(packet); if (result_ptr.is_error()) { return on_error(result_ptr.move_as_error()); } promise_.set_value(result_ptr.move_as_ok()); } void on_error(Status status) final { promise_.set_error(std::move(status)); } }; bool operator==(const ThemeManager::ThemeSettings &lhs, const ThemeManager::ThemeSettings &rhs) { return lhs.accent_color == rhs.accent_color && lhs.message_accent_color == rhs.message_accent_color && lhs.background_info == rhs.background_info && lhs.base_theme == rhs.base_theme && lhs.message_colors == rhs.message_colors && lhs.animate_message_colors == rhs.animate_message_colors; } bool operator!=(const ThemeManager::ThemeSettings &lhs, const ThemeManager::ThemeSettings &rhs) { return !(lhs == rhs); } bool operator==(const ThemeManager::ProfileAccentColor &lhs, const ThemeManager::ProfileAccentColor &rhs) { return lhs.palette_colors_ == rhs.palette_colors_ && lhs.background_colors_ == rhs.background_colors_ && lhs.story_colors_ == rhs.story_colors_; } bool operator!=(const ThemeManager::ProfileAccentColor &lhs, const ThemeManager::ProfileAccentColor &rhs) { return !(lhs == rhs); } template void ThemeManager::ThemeSettings::store(StorerT &storer) const { using td::store; bool has_message_accent_color = message_accent_color != accent_color; bool has_background = background_info.is_valid(); BEGIN_STORE_FLAGS(); STORE_FLAG(animate_message_colors); STORE_FLAG(has_message_accent_color); STORE_FLAG(has_background); END_STORE_FLAGS(); store(accent_color, storer); if (has_message_accent_color) { store(message_accent_color, storer); } if (has_background) { store(background_info, storer); } store(base_theme, storer); store(message_colors, storer); } template void ThemeManager::ThemeSettings::parse(ParserT &parser) { using td::parse; bool has_message_accent_color; bool has_background; BEGIN_PARSE_FLAGS(); PARSE_FLAG(animate_message_colors); PARSE_FLAG(has_message_accent_color); PARSE_FLAG(has_background); END_PARSE_FLAGS(); parse(accent_color, parser); if (has_message_accent_color) { parse(message_accent_color, parser); } else { message_accent_color = accent_color; } if (has_background) { parse(background_info, parser); } parse(base_theme, parser); parse(message_colors, parser); } template void ThemeManager::ChatTheme::store(StorerT &storer) const { BEGIN_STORE_FLAGS(); END_STORE_FLAGS(); td::store(emoji, storer); td::store(id, storer); td::store(light_theme, storer); td::store(dark_theme, storer); } template void ThemeManager::ChatTheme::parse(ParserT &parser) { BEGIN_PARSE_FLAGS(); END_PARSE_FLAGS(); td::parse(emoji, parser); td::parse(id, parser); td::parse(light_theme, parser); td::parse(dark_theme, parser); } template void ThemeManager::ChatThemes::store(StorerT &storer) const { td::store(hash, storer); td::store(themes, storer); } template void ThemeManager::ChatThemes::parse(ParserT &parser) { td::parse(hash, parser); td::parse(themes, parser); } template void ThemeManager::AccentColors::store(StorerT &storer) const { bool has_hash = hash_ != 0; bool has_min_broadcast_boost_levels = !min_broadcast_boost_levels_.empty(); bool has_min_megagroup_boost_levels = !min_megagroup_boost_levels_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_hash); STORE_FLAG(has_min_broadcast_boost_levels); STORE_FLAG(has_min_megagroup_boost_levels); END_STORE_FLAGS(); td::store(static_cast(light_colors_.size()), storer); for (auto &it : light_colors_) { td::store(it.first, storer); td::store(it.second, storer); } td::store(static_cast(dark_colors_.size()), storer); for (auto &it : dark_colors_) { td::store(it.first, storer); td::store(it.second, storer); } td::store(accent_color_ids_, storer); if (has_hash) { td::store(hash_, storer); } if (has_min_broadcast_boost_levels) { td::store(min_broadcast_boost_levels_, storer); } if (has_min_megagroup_boost_levels) { td::store(min_megagroup_boost_levels_, storer); } } template void ThemeManager::AccentColors::parse(ParserT &parser) { bool has_hash; bool has_min_broadcast_boost_levels; bool has_min_megagroup_boost_levels; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_hash); PARSE_FLAG(has_min_broadcast_boost_levels); PARSE_FLAG(has_min_megagroup_boost_levels); END_PARSE_FLAGS(); int32 size; td::parse(size, parser); for (int32 i = 0; i < size; i++) { AccentColorId accent_color_id; vector colors; td::parse(accent_color_id, parser); td::parse(colors, parser); CHECK(accent_color_id.is_valid()); light_colors_.emplace(accent_color_id, std::move(colors)); } td::parse(size, parser); for (int32 i = 0; i < size; i++) { AccentColorId accent_color_id; vector colors; td::parse(accent_color_id, parser); td::parse(colors, parser); CHECK(accent_color_id.is_valid()); dark_colors_.emplace(accent_color_id, std::move(colors)); } td::parse(accent_color_ids_, parser); if (has_hash) { td::parse(hash_, parser); } if (has_min_broadcast_boost_levels) { td::parse(min_broadcast_boost_levels_, parser); } else { hash_ = 0; } if (has_min_megagroup_boost_levels) { td::parse(min_megagroup_boost_levels_, parser); } } template void ThemeManager::ProfileAccentColor::store(StorerT &storer) const { td::store(palette_colors_, storer); td::store(background_colors_, storer); td::store(story_colors_, storer); } template void ThemeManager::ProfileAccentColor::parse(ParserT &parser) { td::parse(palette_colors_, parser); td::parse(background_colors_, parser); td::parse(story_colors_, parser); } template void ThemeManager::ProfileAccentColors::store(StorerT &storer) const { bool has_hash = hash_ != 0; bool has_min_broadcast_boost_levels = !min_broadcast_boost_levels_.empty(); bool has_min_megagroup_boost_levels = !min_megagroup_boost_levels_.empty(); BEGIN_STORE_FLAGS(); STORE_FLAG(has_hash); STORE_FLAG(has_min_broadcast_boost_levels); STORE_FLAG(has_min_megagroup_boost_levels); END_STORE_FLAGS(); td::store(static_cast(light_colors_.size()), storer); for (auto &it : light_colors_) { td::store(it.first, storer); td::store(it.second, storer); } td::store(static_cast(dark_colors_.size()), storer); for (auto &it : dark_colors_) { td::store(it.first, storer); td::store(it.second, storer); } td::store(accent_color_ids_, storer); if (has_hash) { td::store(hash_, storer); } if (has_min_broadcast_boost_levels) { td::store(min_broadcast_boost_levels_, storer); } if (has_min_megagroup_boost_levels) { td::store(min_megagroup_boost_levels_, storer); } } template void ThemeManager::ProfileAccentColors::parse(ParserT &parser) { bool has_hash; bool has_min_broadcast_boost_levels; bool has_min_megagroup_boost_levels; BEGIN_PARSE_FLAGS(); PARSE_FLAG(has_hash); PARSE_FLAG(has_min_broadcast_boost_levels); PARSE_FLAG(has_min_megagroup_boost_levels); END_PARSE_FLAGS(); int32 size; td::parse(size, parser); for (int32 i = 0; i < size; i++) { AccentColorId accent_color_id; ProfileAccentColor colors; td::parse(accent_color_id, parser); td::parse(colors, parser); CHECK(accent_color_id.is_valid()); light_colors_.emplace(accent_color_id, std::move(colors)); } td::parse(size, parser); for (int32 i = 0; i < size; i++) { AccentColorId accent_color_id; ProfileAccentColor colors; td::parse(accent_color_id, parser); td::parse(colors, parser); CHECK(accent_color_id.is_valid()); dark_colors_.emplace(accent_color_id, std::move(colors)); } td::parse(accent_color_ids_, parser); if (has_hash) { td::parse(hash_, parser); } if (has_min_broadcast_boost_levels) { td::parse(min_broadcast_boost_levels_, parser); } else { hash_ = 0; } if (has_min_megagroup_boost_levels) { td::parse(min_megagroup_boost_levels_, parser); } else { hash_ = 0; } } ThemeManager::ThemeManager(Td *td, ActorShared<> parent) : td_(td), parent_(std::move(parent)) { load_accent_colors(); load_profile_accent_colors(); } void ThemeManager::start_up() { init(); } void ThemeManager::load_chat_themes() { // must not be called in constructor, because uses other managers if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { return; } auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_chat_themes_database_key()); if (!log_event_string.empty()) { auto status = log_event_parse(chat_themes_, log_event_string); if (status.is_ok()) { send_update_chat_themes(); } else { LOG(ERROR) << "Failed to parse chat themes from binlog: " << status; chat_themes_ = ChatThemes(); } } } void ThemeManager::load_accent_colors() { if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { return; } auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_accent_colors_database_key()); if (!log_event_string.empty()) { auto status = log_event_parse(accent_colors_, log_event_string); if (status.is_ok()) { send_update_accent_colors(); } else { LOG(ERROR) << "Failed to parse accent colors from binlog: " << status; accent_colors_ = AccentColors(); } } } void ThemeManager::load_profile_accent_colors() { if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { return; } auto log_event_string = G()->td_db()->get_binlog_pmc()->get(get_profile_accent_colors_database_key()); if (!log_event_string.empty()) { auto status = log_event_parse(profile_accent_colors_, log_event_string); if (status.is_ok()) { send_update_profile_accent_colors(); } else { LOG(ERROR) << "Failed to parse profile accent colors from binlog: " << status; profile_accent_colors_ = ProfileAccentColors(); } } } void ThemeManager::init() { load_chat_themes(); if (td_->auth_manager_->is_authorized() && !td_->auth_manager_->is_bot()) { if (chat_themes_.hash == 0) { reload_chat_themes(); } if (accent_colors_.hash_ == 0) { reload_accent_colors(); } if (profile_accent_colors_.hash_ == 0) { reload_profile_accent_colors(); } } } void ThemeManager::tear_down() { parent_.reset(); } ThemeManager::DialogBoostAvailableCounts ThemeManager::get_dialog_boost_available_count(int32 level, bool for_megagroup) { DialogBoostAvailableCounts result; if (level >= td_->option_manager_->get_option_integer(for_megagroup ? Slice("group_wallpaper_level_min") : Slice("channel_wallpaper_level_min"))) { result.chat_theme_count_ = static_cast(chat_themes_.themes.size()); } { auto &min_boost_levels = for_megagroup ? accent_colors_.min_megagroup_boost_levels_ : accent_colors_.min_broadcast_boost_levels_; for (size_t i = 0; i < min_boost_levels.size(); i++) { if (level >= min_boost_levels[i] && min_boost_levels[i] != 0) { result.accent_color_count_++; if (accent_colors_.accent_color_ids_[i].is_built_in()) { result.title_color_count_++; continue; } auto it = accent_colors_.light_colors_.find(accent_colors_.accent_color_ids_[i]); CHECK(it != accent_colors_.light_colors_.end()); if (it->second.size() == 1) { result.title_color_count_++; } } } } { auto &min_profile_boost_levels = for_megagroup ? profile_accent_colors_.min_megagroup_boost_levels_ : profile_accent_colors_.min_broadcast_boost_levels_; for (size_t i = 0; i < min_profile_boost_levels.size(); i++) { if (level >= min_profile_boost_levels[i] && min_profile_boost_levels[i] != 0) { result.profile_accent_color_count_++; } } } return result; } bool ThemeManager::is_dark_base_theme(BaseTheme base_theme) { switch (base_theme) { case BaseTheme::Classic: case BaseTheme::Day: case BaseTheme::Arctic: return false; case BaseTheme::Night: case BaseTheme::Tinted: return true; default: UNREACHABLE(); return false; } } void ThemeManager::on_update_theme(telegram_api::object_ptr &&theme, Promise &&promise) { CHECK(theme != nullptr); bool is_changed = false; bool was_light = false; bool was_dark = false; for (auto &chat_theme : chat_themes_.themes) { if (chat_theme.id == theme->id_) { for (auto &settings : theme->settings_) { auto theme_settings = get_chat_theme_settings(std::move(settings)); if (theme_settings.message_colors.empty()) { continue; } if (is_dark_base_theme(theme_settings.base_theme)) { if (!was_dark) { was_dark = true; if (chat_theme.dark_theme != theme_settings) { chat_theme.dark_theme = std::move(theme_settings); is_changed = true; } } } else { if (!was_light) { was_light = true; if (chat_theme.light_theme != theme_settings) { chat_theme.light_theme = std::move(theme_settings); is_changed = true; } } } } } } if (is_changed) { save_chat_themes(); send_update_chat_themes(); } promise.set_value(Unit()); } bool ThemeManager::on_update_accent_colors(FlatHashMap, AccentColorIdHash> light_colors, FlatHashMap, AccentColorIdHash> dark_colors, vector accent_color_ids, vector min_broadcast_boost_levels, vector min_megagroup_boost_levels) { auto are_equal = [](const FlatHashMap, AccentColorIdHash> &lhs, const FlatHashMap, AccentColorIdHash> &rhs) { for (auto &lhs_it : lhs) { auto rhs_it = rhs.find(lhs_it.first); if (rhs_it == rhs.end() || rhs_it->second != lhs_it.second) { return false; } } return true; }; if (accent_color_ids == accent_colors_.accent_color_ids_ && min_broadcast_boost_levels == accent_colors_.min_broadcast_boost_levels_ && min_megagroup_boost_levels == accent_colors_.min_megagroup_boost_levels_ && are_equal(light_colors, accent_colors_.light_colors_) && are_equal(dark_colors, accent_colors_.dark_colors_)) { return false; } for (auto &it : light_colors) { accent_colors_.light_colors_[it.first] = std::move(it.second); } for (auto &it : dark_colors) { accent_colors_.dark_colors_[it.first] = std::move(it.second); } accent_colors_.accent_color_ids_ = std::move(accent_color_ids); accent_colors_.min_broadcast_boost_levels_ = std::move(min_broadcast_boost_levels); accent_colors_.min_megagroup_boost_levels_ = std::move(min_megagroup_boost_levels); save_accent_colors(); send_update_accent_colors(); return true; } bool ThemeManager::on_update_profile_accent_colors( FlatHashMap light_colors, FlatHashMap dark_colors, vector accent_color_ids, vector min_broadcast_boost_levels, vector min_megagroup_boost_levels) { auto are_equal = [](const FlatHashMap &lhs, const FlatHashMap &rhs) { for (auto &lhs_it : lhs) { auto rhs_it = rhs.find(lhs_it.first); if (rhs_it == rhs.end() || rhs_it->second != lhs_it.second) { return false; } } return true; }; if (accent_color_ids == profile_accent_colors_.accent_color_ids_ && min_broadcast_boost_levels == profile_accent_colors_.min_broadcast_boost_levels_ && min_megagroup_boost_levels == profile_accent_colors_.min_megagroup_boost_levels_ && are_equal(light_colors, profile_accent_colors_.light_colors_) && are_equal(dark_colors, profile_accent_colors_.dark_colors_)) { return false; } for (auto &it : light_colors) { profile_accent_colors_.light_colors_[it.first] = std::move(it.second); } for (auto &it : dark_colors) { profile_accent_colors_.dark_colors_[it.first] = std::move(it.second); } profile_accent_colors_.accent_color_ids_ = std::move(accent_color_ids); profile_accent_colors_.min_broadcast_boost_levels_ = std::move(min_broadcast_boost_levels); profile_accent_colors_.min_megagroup_boost_levels_ = std::move(min_megagroup_boost_levels); save_profile_accent_colors(); send_update_profile_accent_colors(); return true; } namespace { template static auto get_color_json(int32 color); template <> auto get_color_json(int32 color) { return static_cast(static_cast(color) | 0xFF000000); } template <> auto get_color_json(int32 color) { string res(7, '#'); const char *hex = "0123456789abcdef"; for (int i = 0; i < 3; i++) { int32 num = (color >> (i * 8)) & 0xFF; res[2 * i + 1] = hex[num >> 4]; res[2 * i + 2] = hex[num & 15]; } return res; } template string get_theme_parameters_json_string_impl(const td_api::object_ptr &theme) { if (for_web_view && theme == nullptr) { return "null"; } return json_encode(json_object([&theme](auto &o) { auto get_color = &get_color_json; o("bg_color", get_color(theme->background_color_)); o("secondary_bg_color", get_color(theme->secondary_background_color_)); o("text_color", get_color(theme->text_color_)); o("hint_color", get_color(theme->hint_color_)); o("link_color", get_color(theme->link_color_)); o("button_color", get_color(theme->button_color_)); o("button_text_color", get_color(theme->button_text_color_)); o("header_bg_color", get_color(theme->header_background_color_)); o("section_bg_color", get_color(theme->section_background_color_)); o("accent_text_color", get_color(theme->accent_text_color_)); o("section_header_text_color", get_color(theme->section_header_text_color_)); o("subtitle_text_color", get_color(theme->subtitle_text_color_)); o("destructive_text_color", get_color(theme->destructive_text_color_)); })); } } // namespace string ThemeManager::get_theme_parameters_json_string(const td_api::object_ptr &theme, bool for_web_view) { if (for_web_view) { return get_theme_parameters_json_string_impl(theme); } else { return get_theme_parameters_json_string_impl(theme); } } int32 ThemeManager::get_accent_color_id_object(AccentColorId accent_color_id, AccentColorId fallback_accent_color_id) const { CHECK(accent_color_id.is_valid()); if (td_->auth_manager_->is_bot() || accent_color_id.is_built_in() || accent_colors_.light_colors_.count(accent_color_id) != 0) { return accent_color_id.get(); } if (!fallback_accent_color_id.is_valid()) { return 5; // blue } CHECK(fallback_accent_color_id.is_built_in()); return fallback_accent_color_id.get(); } int32 ThemeManager::get_profile_accent_color_id_object(AccentColorId accent_color_id) const { if (!accent_color_id.is_valid()) { return -1; } if (td_->auth_manager_->is_bot() || profile_accent_colors_.light_colors_.count(accent_color_id) != 0) { return accent_color_id.get(); } return -1; } td_api::object_ptr ThemeManager::get_theme_settings_object(const ThemeSettings &settings) const { auto fill = [colors = settings.message_colors]() mutable -> td_api::object_ptr { if (colors.size() >= 3) { return td_api::make_object(std::move(colors)); } CHECK(!colors.empty()); if (colors.size() == 1 || colors[0] == colors[1]) { return td_api::make_object(colors[0]); } return td_api::make_object(colors[1], colors[0], 0); }(); // ignore settings.base_theme for now return td_api::make_object( settings.accent_color, settings.background_info.get_background_object(td_), std::move(fill), settings.animate_message_colors, settings.message_accent_color); } td_api::object_ptr ThemeManager::get_chat_theme_object(const ChatTheme &theme) const { return td_api::make_object(theme.emoji, get_theme_settings_object(theme.light_theme), get_theme_settings_object(theme.dark_theme)); } td_api::object_ptr ThemeManager::get_update_chat_themes_object() const { return td_api::make_object( transform(chat_themes_.themes, [this](const ChatTheme &theme) { return get_chat_theme_object(theme); })); } td_api::object_ptr ThemeManager::get_update_accent_colors_object() const { return accent_colors_.get_update_accent_colors_object(); } td_api::object_ptr ThemeManager::AccentColors::get_update_accent_colors_object() const { vector> colors; int32 base_colors[] = {0xDF2020, 0xDFA520, 0xA040A0, 0x208020, 0x20DFDF, 0x2044DF, 0xDF1493}; auto get_distance = [](int32 lhs_color, int32 rhs_color) { auto get_color_distance = [](int32 lhs, int32 rhs) { auto diff = max(lhs & 255, 0) - max(rhs & 255, 0); return diff * diff; }; return get_color_distance(lhs_color, rhs_color) + get_color_distance(lhs_color >> 8, rhs_color >> 8) + get_color_distance(lhs_color >> 16, rhs_color >> 16); }; FlatHashMap min_broadcast_boost_levels; for (size_t i = 0; i < min_broadcast_boost_levels_.size(); i++) { CHECK(i < accent_color_ids_.size()); min_broadcast_boost_levels[accent_color_ids_[i]] = min_broadcast_boost_levels_[i]; } for (auto &it : light_colors_) { auto light_colors = it.second; auto dark_it = dark_colors_.find(it.first); auto dark_colors = dark_it != dark_colors_.end() ? dark_it->second : light_colors; CHECK(!light_colors.empty()); CHECK(!dark_colors.empty()); auto first_color = light_colors[0]; int best_index = 0; int32 best_distance = get_distance(base_colors[0], first_color); for (int i = 1; i < 7; i++) { auto cur_distance = get_distance(base_colors[i], first_color); if (cur_distance < best_distance) { best_distance = cur_distance; best_index = i; } } colors.push_back(td_api::make_object(it.first.get(), best_index, std::move(light_colors), std::move(dark_colors), min_broadcast_boost_levels[it.first])); } auto available_accent_color_ids = transform(accent_color_ids_, [](AccentColorId accent_color_id) { return accent_color_id.get(); }); return td_api::make_object(std::move(colors), std::move(available_accent_color_ids)); } td_api::object_ptr ThemeManager::get_update_profile_accent_colors_object() const { return profile_accent_colors_.get_update_profile_accent_colors_object(); } bool ThemeManager::ProfileAccentColor::is_valid() const { return are_colors_valid(palette_colors_, 1, 2) && are_colors_valid(background_colors_, 1, 2) && are_colors_valid(story_colors_, 2, 2); } td_api::object_ptr ThemeManager::ProfileAccentColor::get_profile_accent_colors_object() const { return td_api::make_object( vector(palette_colors_), vector(background_colors_), vector(story_colors_)); } td_api::object_ptr ThemeManager::ProfileAccentColors::get_update_profile_accent_colors_object() const { FlatHashMap min_broadcast_boost_levels; for (size_t i = 0; i < min_broadcast_boost_levels_.size(); i++) { CHECK(i < accent_color_ids_.size()); min_broadcast_boost_levels[accent_color_ids_[i]] = min_broadcast_boost_levels_[i]; } FlatHashMap min_megagroup_boost_levels; for (size_t i = 0; i < min_megagroup_boost_levels_.size(); i++) { CHECK(i < accent_color_ids_.size()); min_megagroup_boost_levels[accent_color_ids_[i]] = min_megagroup_boost_levels_[i]; } vector> colors; for (auto &it : light_colors_) { auto light_colors = it.second.get_profile_accent_colors_object(); auto dark_it = dark_colors_.find(it.first); auto dark_colors = dark_it != dark_colors_.end() ? dark_it->second.get_profile_accent_colors_object() : it.second.get_profile_accent_colors_object(); colors.push_back(td_api::make_object( it.first.get(), std::move(light_colors), std::move(dark_colors), min_megagroup_boost_levels[it.first], min_broadcast_boost_levels[it.first])); } auto available_accent_color_ids = transform(accent_color_ids_, [](AccentColorId accent_color_id) { return accent_color_id.get(); }); return td_api::make_object(std::move(colors), std::move(available_accent_color_ids)); } string ThemeManager::get_chat_themes_database_key() { return "chat_themes"; } string ThemeManager::get_accent_colors_database_key() { return "accent_colors"; } string ThemeManager::get_profile_accent_colors_database_key() { return "profile_accent_colors"; } void ThemeManager::save_chat_themes() { G()->td_db()->get_binlog_pmc()->set(get_chat_themes_database_key(), log_event_store(chat_themes_).as_slice().str()); } void ThemeManager::save_accent_colors() { G()->td_db()->get_binlog_pmc()->set(get_accent_colors_database_key(), log_event_store(accent_colors_).as_slice().str()); } void ThemeManager::save_profile_accent_colors() { G()->td_db()->get_binlog_pmc()->set(get_profile_accent_colors_database_key(), log_event_store(profile_accent_colors_).as_slice().str()); } void ThemeManager::send_update_chat_themes() const { send_closure(G()->td(), &Td::send_update, get_update_chat_themes_object()); } void ThemeManager::send_update_accent_colors() const { send_closure(G()->td(), &Td::send_update, get_update_accent_colors_object()); } void ThemeManager::send_update_profile_accent_colors() const { send_closure(G()->td(), &Td::send_update, get_update_profile_accent_colors_object()); } void ThemeManager::reload_chat_themes() { auto request_promise = PromiseCreator::lambda( [actor_id = actor_id(this)](Result> result) { send_closure(actor_id, &ThemeManager::on_get_chat_themes, std::move(result)); }); td_->create_handler(std::move(request_promise))->send(chat_themes_.hash); } void ThemeManager::on_get_chat_themes(Result> result) { if (result.is_error()) { return; } auto chat_themes_ptr = result.move_as_ok(); LOG(DEBUG) << "Receive " << to_string(chat_themes_ptr); if (chat_themes_ptr->get_id() == telegram_api::account_themesNotModified::ID) { return; } CHECK(chat_themes_ptr->get_id() == telegram_api::account_themes::ID); auto chat_themes = telegram_api::move_object_as(chat_themes_ptr); chat_themes_.hash = chat_themes->hash_; chat_themes_.themes.clear(); for (auto &theme : chat_themes->themes_) { if (!is_emoji(theme->emoticon_) || !theme->for_chat_) { LOG(ERROR) << "Receive " << to_string(theme); continue; } bool was_light = false; bool was_dark = false; ChatTheme chat_theme; chat_theme.emoji = std::move(theme->emoticon_); chat_theme.id = theme->id_; for (auto &settings : theme->settings_) { auto theme_settings = get_chat_theme_settings(std::move(settings)); if (theme_settings.message_colors.empty()) { continue; } if (is_dark_base_theme(theme_settings.base_theme)) { if (!was_dark) { was_dark = true; if (chat_theme.dark_theme != theme_settings) { chat_theme.dark_theme = std::move(theme_settings); } } } else { if (!was_light) { was_light = true; if (chat_theme.light_theme != theme_settings) { chat_theme.light_theme = std::move(theme_settings); } } } } if (chat_theme.light_theme.message_colors.empty() || chat_theme.dark_theme.message_colors.empty()) { continue; } chat_themes_.themes.push_back(std::move(chat_theme)); } save_chat_themes(); send_update_chat_themes(); } void ThemeManager::reload_accent_colors() { auto request_promise = PromiseCreator::lambda( [actor_id = actor_id(this)](Result> result) { send_closure(actor_id, &ThemeManager::on_get_accent_colors, std::move(result)); }); td_->create_handler(std::move(request_promise))->send(accent_colors_.hash_); } void ThemeManager::on_get_accent_colors(Result> result) { if (result.is_error()) { return; } auto peer_colors_ptr = result.move_as_ok(); LOG(DEBUG) << "Receive " << to_string(peer_colors_ptr); if (peer_colors_ptr->get_id() == telegram_api::help_peerColorsNotModified::ID) { return; } CHECK(peer_colors_ptr->get_id() == telegram_api::help_peerColors::ID); auto peer_colors = telegram_api::move_object_as(peer_colors_ptr); FlatHashMap, AccentColorIdHash> light_colors; FlatHashMap, AccentColorIdHash> dark_colors; vector accent_color_ids; vector min_broadcast_boost_levels; vector min_megagroup_boost_levels; for (auto &option : peer_colors->colors_) { if ((option->colors_ != nullptr && option->colors_->get_id() != telegram_api::help_peerColorSet::ID) || (option->dark_colors_ != nullptr && option->dark_colors_->get_id() != telegram_api::help_peerColorSet::ID)) { LOG(ERROR) << "Receive " << to_string(option); continue; } AccentColorId accent_color_id(option->color_id_); if (!accent_color_id.is_valid() || td::contains(accent_color_ids, accent_color_id) || (accent_color_id.is_built_in() && (option->colors_ != nullptr || option->dark_colors_ != nullptr)) || (!accent_color_id.is_built_in() && option->colors_ == nullptr)) { LOG(ERROR) << "Receive " << to_string(option); continue; } bool is_valid = true; vector current_light_colors; vector current_dark_colors; if (option->colors_ != nullptr) { auto colors = telegram_api::move_object_as(option->colors_); current_light_colors = std::move(colors->colors_); if (!are_colors_valid(current_light_colors, 1, 3)) { is_valid = false; } } if (option->dark_colors_ != nullptr) { auto colors = telegram_api::move_object_as(option->dark_colors_); current_dark_colors = std::move(colors->colors_); if (!are_colors_valid(current_dark_colors, 1, 3)) { is_valid = false; } } if (!is_valid) { LOG(ERROR) << "Receive invalid colors for " << accent_color_id; continue; } if (!option->hidden_) { accent_color_ids.push_back(accent_color_id); min_broadcast_boost_levels.push_back(max(0, option->channel_min_level_)); min_megagroup_boost_levels.push_back(max(0, option->group_min_level_)); } if (!current_light_colors.empty()) { light_colors[accent_color_id] = std::move(current_light_colors); } if (!current_dark_colors.empty()) { dark_colors[accent_color_id] = std::move(current_dark_colors); } } bool is_changed = false; if (accent_colors_.hash_ != peer_colors->hash_) { accent_colors_.hash_ = peer_colors->hash_; is_changed = true; } if (!on_update_accent_colors(std::move(light_colors), std::move(dark_colors), std::move(accent_color_ids), std::move(min_broadcast_boost_levels), std::move(min_megagroup_boost_levels)) && is_changed) { save_accent_colors(); } } void ThemeManager::reload_profile_accent_colors() { auto request_promise = PromiseCreator::lambda( [actor_id = actor_id(this)](Result> result) { send_closure(actor_id, &ThemeManager::on_get_profile_accent_colors, std::move(result)); }); td_->create_handler(std::move(request_promise))->send(profile_accent_colors_.hash_); } ThemeManager::ProfileAccentColor ThemeManager::get_profile_accent_color( telegram_api::object_ptr &&color_set) const { CHECK(color_set != nullptr); CHECK(color_set->get_id() == telegram_api::help_peerColorProfileSet::ID); auto colors = telegram_api::move_object_as(color_set); ProfileAccentColor color; color.palette_colors_ = std::move(colors->palette_colors_); color.background_colors_ = std::move(colors->bg_colors_); color.story_colors_ = std::move(colors->story_colors_); return color; } void ThemeManager::on_get_profile_accent_colors( Result> result) { if (result.is_error()) { return; } auto peer_colors_ptr = result.move_as_ok(); LOG(DEBUG) << "Receive " << to_string(peer_colors_ptr); if (peer_colors_ptr->get_id() == telegram_api::help_peerColorsNotModified::ID) { return; } CHECK(peer_colors_ptr->get_id() == telegram_api::help_peerColors::ID); auto peer_colors = telegram_api::move_object_as(peer_colors_ptr); FlatHashMap light_colors; FlatHashMap dark_colors; vector accent_color_ids; vector min_broadcast_boost_levels; vector min_megagroup_boost_levels; for (auto &option : peer_colors->colors_) { AccentColorId accent_color_id(option->color_id_); if (option->colors_ == nullptr || option->colors_->get_id() != telegram_api::help_peerColorProfileSet::ID || option->dark_colors_ == nullptr || option->dark_colors_->get_id() != telegram_api::help_peerColorProfileSet::ID || !accent_color_id.is_valid() || td::contains(accent_color_ids, accent_color_id)) { LOG(ERROR) << "Receive " << to_string(option); continue; } auto current_light_color = get_profile_accent_color(std::move(option->colors_)); auto current_dark_color = get_profile_accent_color(std::move(option->dark_colors_)); if (!current_light_color.is_valid() || !current_dark_color.is_valid()) { LOG(ERROR) << "Receive invalid colors for " << accent_color_id; continue; } if (!option->hidden_) { accent_color_ids.push_back(accent_color_id); min_broadcast_boost_levels.push_back(max(0, option->channel_min_level_)); min_megagroup_boost_levels.push_back(max(0, option->group_min_level_)); } light_colors[accent_color_id] = std::move(current_light_color); dark_colors[accent_color_id] = std::move(current_dark_color); } bool is_changed = false; if (profile_accent_colors_.hash_ != peer_colors->hash_) { profile_accent_colors_.hash_ = peer_colors->hash_; is_changed = true; } if (!on_update_profile_accent_colors(std::move(light_colors), std::move(dark_colors), std::move(accent_color_ids), std::move(min_broadcast_boost_levels), std::move(min_megagroup_boost_levels)) && is_changed) { save_profile_accent_colors(); } } ThemeManager::BaseTheme ThemeManager::get_base_theme( const telegram_api::object_ptr &base_theme) { CHECK(base_theme != nullptr); switch (base_theme->get_id()) { case telegram_api::baseThemeClassic::ID: return BaseTheme::Classic; case telegram_api::baseThemeDay::ID: return BaseTheme::Day; case telegram_api::baseThemeNight::ID: return BaseTheme::Night; case telegram_api::baseThemeTinted::ID: return BaseTheme::Tinted; case telegram_api::baseThemeArctic::ID: return BaseTheme::Arctic; default: UNREACHABLE(); return BaseTheme::Classic; } } ThemeManager::ThemeSettings ThemeManager::get_chat_theme_settings( telegram_api::object_ptr settings) { ThemeSettings result; if (settings != nullptr && !settings->message_colors_.empty() && settings->message_colors_.size() <= 4) { result.accent_color = settings->accent_color_; bool has_outbox_accent_color = (settings->flags_ & telegram_api::themeSettings::OUTBOX_ACCENT_COLOR_MASK) != 0; result.message_accent_color = (has_outbox_accent_color ? settings->outbox_accent_color_ : result.accent_color); result.background_info = BackgroundInfo(td_, std::move(settings->wallpaper_), true); result.base_theme = get_base_theme(settings->base_theme_); result.message_colors = std::move(settings->message_colors_); result.animate_message_colors = settings->message_colors_animated_; } return result; } void ThemeManager::get_current_state(vector> &updates) const { if (!td_->auth_manager_->is_authorized() || td_->auth_manager_->is_bot()) { return; } if (!chat_themes_.themes.empty()) { updates.push_back(get_update_chat_themes_object()); } if (!accent_colors_.accent_color_ids_.empty()) { updates.push_back(get_update_accent_colors_object()); } if (!profile_accent_colors_.accent_color_ids_.empty()) { updates.push_back(get_update_profile_accent_colors_object()); } } } // namespace td