diff --git a/Telegram/SourceFiles/core/local_url_handlers.cpp b/Telegram/SourceFiles/core/local_url_handlers.cpp index de59f999a..da9879415 100644 --- a/Telegram/SourceFiles/core/local_url_handlers.cpp +++ b/Telegram/SourceFiles/core/local_url_handlers.cpp @@ -39,6 +39,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "main/main_session.h" #include "main/main_session_settings.h" +#include "history/history.h" #include "apiwrap.h" #include @@ -464,6 +465,29 @@ bool ShowInviteLink( return true; } +bool ResolveTestChatTheme( + Window::SessionController *controller, + const Match &match, + const QVariant &context) { + if (!controller) { + return false; + } + const auto params = url_parse_params( + match->captured(1), + qthelp::UrlParamNameTransform::ToLower); + if (const auto history = controller->activeChatCurrent().history()) { + controller->clearCachedChatThemes(); + const auto theme = history->owner().cloudThemes().updateThemeFromLink( + history->peer->themeEmoji(), + params); + if (theme) { + [[maybe_unused]] auto value = controller->cachedChatThemeValue( + *theme); + } + } + return true; +} + } // namespace const std::vector &LocalUrlHandlers() { @@ -524,6 +548,10 @@ const std::vector &LocalUrlHandlers() { qsl("^settings(/folders|/devices|/language)?$"), ResolveSettings }, + { + qsl("^test_chat_theme/?\\?(.+)(#|$)"), + ResolveTestChatTheme, + }, { qsl("^([^\\?]+)(\\?|#|$)"), HandleUnknown diff --git a/Telegram/SourceFiles/data/data_cloud_themes.cpp b/Telegram/SourceFiles/data/data_cloud_themes.cpp index 2ebddb8ce..abe374334 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.cpp +++ b/Telegram/SourceFiles/data/data_cloud_themes.cpp @@ -27,6 +27,8 @@ namespace { constexpr auto kFirstReloadTimeout = 10 * crl::time(1000); constexpr auto kReloadTimeout = 3600 * crl::time(1000); +bool IsTestingColors/* = false*/; + } // namespace CloudTheme CloudTheme::Parse( @@ -395,12 +397,24 @@ std::optional CloudThemes::themeForEmoji( rpl::producer> CloudThemes::themeForEmojiValue( const QString &emoji) { + const auto testing = TestingColors(); if (emoji.isEmpty()) { return rpl::single>(std::nullopt); } else if (auto result = themeForEmoji(emoji)) { + if (testing) { + return rpl::single( + std::move(result) + ) | rpl::then(chatThemesUpdated( + ) | rpl::map([=] { + return themeForEmoji(emoji); + }) | rpl::filter([](const std::optional &theme) { + return theme.has_value(); + })); + } return rpl::single(std::move(result)); } refreshChatThemes(); + const auto limit = testing ? (1 << 20) : 1; return rpl::single>( std::nullopt ) | rpl::then(chatThemesUpdated( @@ -408,7 +422,112 @@ rpl::producer> CloudThemes::themeForEmojiValue( return themeForEmoji(emoji); }) | rpl::filter([](const std::optional &theme) { return theme.has_value(); - }) | rpl::take(1)); + }) | rpl::take(limit)); +} + +bool CloudThemes::TestingColors() { + return IsTestingColors; +} + +void CloudThemes::SetTestingColors(bool testing) { + IsTestingColors = testing; +} + +QString CloudThemes::PrepareTestingLink(const CloudTheme &theme) { + const auto hex = [](int value) { + return QChar((value < 10) ? ('0' + value) : ('a' + (value - 10))); + }; + const auto hex2 = [&](int value) { + return QString() + hex(value / 16) + hex(value % 16); + }; + const auto color = [&](const QColor &color) { + return hex2(color.red()) + hex2(color.green()) + hex2(color.blue()); + }; + const auto colors = [&](const std::vector &colors) { + auto list = QStringList(); + for (const auto &c : colors) { + list.push_back(color(c)); + } + return list.join(","); + }; + auto arguments = QStringList(); + if (theme.basedOnDark) { + arguments.push_back("dark=1"); + } + if (theme.accentColor) { + arguments.push_back("accent=" + color(*theme.accentColor)); + } + if (theme.paper && !theme.paper->backgroundColors().empty()) { + arguments.push_back("bg=" + colors(theme.paper->backgroundColors())); + } + if (theme.outgoingAccentColor) { + arguments.push_back("out_accent" + color(*theme.outgoingAccentColor)); + } + if (!theme.outgoingMessagesColors.empty()) { + arguments.push_back("out_bg=" + colors(theme.outgoingMessagesColors)); + } + return arguments.isEmpty() + ? QString() + : ("tg://test_chat_theme?" + arguments.join("&")); +} + +std::optional CloudThemes::updateThemeFromLink( + const QString &emoji, + const QMap ¶ms) { + if (!TestingColors()) { + return std::nullopt; + } + const auto i = ranges::find(_chatThemes, emoji, &ChatTheme::emoji); + if (i == end(_chatThemes)) { + return std::nullopt; + } + const auto hex = [](const QString &value) { + return (value.size() != 1) + ? std::nullopt + : (value[0] >= 'a' && value[0] <= 'f') + ? std::make_optional(10 + int(value[0].unicode() - 'a')) + : (value[0] >= '0' && value[0] <= '9') + ? std::make_optional(int(value[0].unicode() - '0')) + : std::nullopt; + }; + const auto hex2 = [&](const QString &value) { + const auto first = hex(value.mid(0, 1)); + const auto second = hex(value.mid(1, 1)); + return (first && second) + ? std::make_optional((*first) * 16 + (*second)) + : std::nullopt; + }; + const auto color = [&](const QString &value) { + const auto red = hex2(value.mid(0, 2)); + const auto green = hex2(value.mid(2, 2)); + const auto blue = hex2(value.mid(4, 2)); + return (red && green && blue) + ? std::make_optional(QColor(*red, *green, *blue)) + : std::nullopt; + }; + const auto colors = [&](const QString &value) { + auto list = value.split(","); + auto result = std::vector(); + for (const auto &single : list) { + if (const auto c = color(single)) { + result.push_back(*c); + } else { + return std::vector(); + } + } + return (result.size() > 4) ? std::vector() : result; + }; + + auto &applyTo = params["dark"].isEmpty() ? i->light : i->dark; + applyTo.accentColor = color(params["accent"]); + const auto bg = colors(params["bg"]); + applyTo.paper = (applyTo.paper && !bg.empty()) + ? std::make_optional(applyTo.paper->withBackgroundColors(bg)) + : std::nullopt; + applyTo.outgoingAccentColor = color(params["out_accent"]); + applyTo.outgoingMessagesColors = colors(params["out_bg"]); + _chatThemesUpdates.fire({}); + return applyTo; } void CloudThemes::parseChatThemes(const QVector &list) { diff --git a/Telegram/SourceFiles/data/data_cloud_themes.h b/Telegram/SourceFiles/data/data_cloud_themes.h index b684d76a1..22db36461 100644 --- a/Telegram/SourceFiles/data/data_cloud_themes.h +++ b/Telegram/SourceFiles/data/data_cloud_themes.h @@ -75,6 +75,13 @@ public: [[nodiscard]] rpl::producer> themeForEmojiValue( const QString &emoji); + [[nodiscard]] static bool TestingColors(); + [[nodiscard]] static void SetTestingColors(bool testing); + [[nodiscard]] static QString PrepareTestingLink(const CloudTheme &theme); + [[nodiscard]] std::optional updateThemeFromLink( + const QString &emoji, + const QMap ¶ms); + void applyUpdate(const MTPTheme &theme); void resolve( diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 0b748fa60..c6c919693 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -632,6 +632,7 @@ HistoryWidget::HistoryWidget( | PeerUpdateFlag::Slowmode | PeerUpdateFlag::BotStartToken | PeerUpdateFlag::MessagesTTL + | PeerUpdateFlag::ChatThemeEmoji ) | rpl::filter([=](const Data::PeerUpdate &update) { return (update.peer.get() == _peer); }) | rpl::map([](const Data::PeerUpdate &update) { @@ -675,6 +676,31 @@ HistoryWidget::HistoryWidget( if (flags & PeerUpdateFlag::MessagesTTL) { checkMessagesTTL(); } + if ((flags & PeerUpdateFlag::ChatThemeEmoji) && _list) { + const auto emoji = _peer->themeEmoji(); + if (Data::CloudThemes::TestingColors() && !emoji.isEmpty()) { + _peer->owner().cloudThemes().themeForEmojiValue( + emoji + ) | rpl::filter_optional( + ) | rpl::take( + 1 + ) | rpl::start_with_next([=](const Data::ChatTheme &theme) { + auto text = QStringList(); + const auto push = [&](QString label, const auto &theme) { + using namespace Data; + const auto l = CloudThemes::PrepareTestingLink(theme); + if (!l.isEmpty()) { + text.push_back(label + ": " + l); + } + }; + push("Light", theme.light); + push("Dark", theme.dark); + if (!text.isEmpty()) { + _field->setText(text.join("\n\n")); + } + }, _list->lifetime()); + } + } }, lifetime()); rpl::merge( diff --git a/Telegram/SourceFiles/settings/settings_codes.cpp b/Telegram/SourceFiles/settings/settings_codes.cpp index cbfd994d5..3ce089635 100644 --- a/Telegram/SourceFiles/settings/settings_codes.cpp +++ b/Telegram/SourceFiles/settings/settings_codes.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "mainwidget.h" #include "mainwindow.h" #include "data/data_session.h" +#include "data/data_cloud_themes.h" #include "main/main_session.h" #include "main/main_account.h" #include "main/main_domain.h" @@ -274,6 +275,11 @@ auto GenerateCodes() { }); }); }); + codes.emplace(qsl("testchatcolors"), [](SessionController *window) { + const auto now = !Data::CloudThemes::TestingColors(); + Data::CloudThemes::SetTestingColors(now); + Ui::Toast::Show(now ? "Testing chat theme colors!" : "Not testing.."); + }); return codes; } diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index 2ce860f41..b4ab89537 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -1399,13 +1399,14 @@ auto SessionController::cachedChatThemeValue( if (i == end(_customChatThemes)) { cacheChatTheme(data); } + const auto limit = Data::CloudThemes::TestingColors() ? (1 << 20) : 1; using namespace rpl::mappers; return rpl::single( _defaultChatTheme ) | rpl::then(_cachedThemesStream.events( ) | rpl::filter([=](const std::shared_ptr &theme) { return (theme->key() == key); - }) | rpl::take(1)); + }) | rpl::take(limit)); } void SessionController::setChatStyleTheme( @@ -1417,6 +1418,10 @@ void SessionController::setChatStyleTheme( _chatStyle->apply(theme.get()); } +void SessionController::clearCachedChatThemes() { + _customChatThemes.clear(); +} + void SessionController::pushDefaultChatBackground() { const auto background = Theme::Background(); const auto &paper = background->paper(); diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index d02bfe851..6904dbded 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -407,6 +407,7 @@ public: const Data::CloudTheme &data) -> rpl::producer>; void setChatStyleTheme(const std::shared_ptr &theme); + void clearCachedChatThemes(); struct PaintContextArgs { not_null theme;