From df1410d84d78ab2bf89a491d35e287c14b53e02e Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 13 Jan 2026 14:28:31 +0400 Subject: [PATCH] Search results look nicer now. --- .../builder/settings_advanced_builder.cpp | 86 ++++---- .../settings/builder/settings_builder.cpp | 202 ++++++++---------- .../settings/builder/settings_builder.h | 51 +++-- .../builder/settings_chat_builder.cpp | 16 +- .../builder/settings_main_builder.cpp | 68 +++--- .../settings_notifications_builder.cpp | 36 ++-- .../settings_privacy_security_builder.cpp | 16 +- Telegram/SourceFiles/settings/settings.style | 10 + .../SourceFiles/settings/settings_common.cpp | 4 +- .../SourceFiles/settings/settings_common.h | 2 +- .../settings/settings_privacy_security.cpp | 6 + .../settings/settings_privacy_security.h | 4 + .../SourceFiles/settings/settings_search.cpp | 196 +++++++++++++---- .../SourceFiles/settings/settings_search.h | 10 +- 14 files changed, 428 insertions(+), 279 deletions(-) diff --git a/Telegram/SourceFiles/settings/builder/settings_advanced_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_advanced_builder.cpp index 8aba33234c..cf513683b2 100644 --- a/Telegram/SourceFiles/settings/builder/settings_advanced_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_advanced_builder.cpp @@ -168,7 +168,7 @@ void BuildDataStorageSection( .keywords = { u"downloads"_q, u"files"_q }, }); - const auto askDownloadPath = builder.addToggle({ + const auto askDownloadPath = builder.addButton({ .id = u"advanced/ask_download"_q, .title = tr::lng_download_path_ask(), .st = &st::settingsButtonNoIcon, @@ -375,19 +375,19 @@ void BuildSystemIntegrationSection( : nullptr; const auto monochrome = Platform::HasMonochromeSetting() - ? builder.addSlideCheckbox({ + ? builder.addCheckbox({ .id = u"advanced/monochrome_icon"_q, .title = tr::lng_settings_monochrome_icon(), .checked = settings->trayIconMonochrome(), + .keywords = { u"monochrome"_q, u"icon"_q, u"tray"_q }, .shown = tray ? tray->checkedValue() : rpl::single(trayEnabled()), - .keywords = { u"monochrome"_q, u"icon"_q, u"tray"_q }, }) : nullptr; - if (monochrome && monochrome->entity()) { - monochrome->entity()->checkedChanges( + if (monochrome) { + monochrome->checkedChanges( ) | rpl::filter([=](bool value) { return (value != settings->trayIconMonochrome()); }) | rpl::on_next([=](bool value) { @@ -501,17 +501,17 @@ void BuildSystemIntegrationSection( }, container->lifetime()); } - const auto closeToTaskbar = builder.addSlideCheckbox({ + const auto closeToTaskbar = builder.addCheckbox({ .id = u"advanced/close_to_taskbar"_q, .title = tr::lng_settings_close_to_taskbar(), .checked = settings->closeBehavior() == Behavior::CloseToTaskbar, + .keywords = { u"close"_q, u"taskbar"_q, u"minimize"_q }, .shown = closeToTaskbarShown ? closeToTaskbarShown->value() : rpl::single(false), - .keywords = { u"close"_q, u"taskbar"_q, u"minimize"_q }, }); - if (closeToTaskbar && closeToTaskbar->entity()) { - closeToTaskbar->entity()->checkedChanges( + if (closeToTaskbar) { + closeToTaskbar->checkedChanges( ) | rpl::map([=](bool checked) { return checked ? Behavior::CloseToTaskbar : Behavior::Quit; }) | rpl::filter([=](Behavior value) { @@ -537,14 +537,14 @@ void BuildSystemIntegrationSection( .keywords = { u"autostart"_q, u"startup"_q, u"boot"_q }, }); - const auto minimized = builder.addSlideCheckbox({ + const auto minimized = builder.addCheckbox({ .id = u"advanced/start_minimized"_q, .title = tr::lng_settings_start_min(), .checked = minimizedToggled(), + .keywords = { u"minimized"_q, u"startup"_q, u"hidden"_q }, .shown = autostart ? autostart->checkedValue() : rpl::single(cAutoStart()), - .keywords = { u"minimized"_q, u"startup"_q, u"hidden"_q }, }); if (autostart) { @@ -563,10 +563,10 @@ void BuildSystemIntegrationSection( Ui::PostponeCall(autostart, [=] { autostart->setChecked(enabled); }); - if (enabled || !minimized || !minimized->entity()->checked()) { + if (enabled || !minimized || !minimized->checked()) { Local::writeSettings(); } else if (minimized) { - minimized->entity()->setChecked(false); + minimized->setChecked(false); } })); }, autostart->lifetime()); @@ -578,13 +578,13 @@ void BuildSystemIntegrationSection( } } - if (minimized && minimized->entity() && controller) { - minimized->entity()->checkedChanges( + if (minimized && controller) { + minimized->checkedChanges( ) | rpl::filter([=](bool checked) { return (checked != minimizedToggled()); }) | rpl::on_next([=](bool checked) { if (controller->session().domain().local().hasLocalPasscode()) { - minimized->entity()->setChecked(false); + minimized->setChecked(false); controller->show(Ui::MakeInformBox( tr::lng_error_start_minimized_passcoded())); } else { @@ -595,7 +595,7 @@ void BuildSystemIntegrationSection( controller->session().domain().local().localPasscodeChanged( ) | rpl::on_next([=] { - minimized->entity()->setChecked(minimizedToggled()); + minimized->setChecked(minimizedToggled()); }, minimized->lifetime()); } } @@ -703,7 +703,7 @@ void BuildANGLEOption( void BuildOpenGLOption( SectionBuilder &builder, Window::SessionController *controller) { - const auto opengl = builder.addToggle({ + const auto opengl = builder.addButton({ .id = u"advanced/opengl"_q, .title = tr::lng_settings_enable_opengl(), .st = &st::settingsButtonNoIcon, @@ -750,7 +750,7 @@ void BuildPerformanceSection( .keywords = { u"power"_q, u"saving"_q, u"battery"_q, u"animation"_q }, }); - const auto hwAccel = builder.addToggle({ + const auto hwAccel = builder.addButton({ .id = u"advanced/hw_accel"_q, .title = tr::lng_settings_enable_hwaccel(), .st = &st::settingsButtonNoIcon, @@ -794,7 +794,7 @@ void BuildSpellcheckerSection( builder.addSkip(); builder.addSubsectionTitle(tr::lng_settings_spellchecker()); - const auto spellchecker = builder.addToggle({ + const auto spellchecker = builder.addButton({ .id = u"advanced/spellchecker"_q, .title = isSystem ? tr::lng_settings_system_spellchecker() @@ -823,7 +823,7 @@ void BuildSpellcheckerSection( const auto inner = sliding ? sliding->entity() : nullptr; if (!isSystem) { - const auto autoDownload = builder.addToggle({ + const auto autoDownload = builder.addButton({ .id = u"advanced/auto_download_dictionaries"_q, .title = tr::lng_settings_auto_download_dictionaries(), .st = &st::settingsButtonNoIcon, @@ -898,7 +898,7 @@ void BuildUpdateSection( ? Ui::CreateChild>(container) : nullptr; - const auto toggle = builder.addToggle({ + const auto toggle = builder.addButton({ .id = u"advanced/auto_update"_q, .title = tr::lng_settings_update_automatically(), .st = &st::settingsUpdateToggle, @@ -933,7 +933,7 @@ void BuildUpdateSection( const auto install = cAlphaVersion() ? nullptr - : builder.addToggle({ + : builder.addButton({ .id = u"advanced/install_beta"_q, .title = tr::lng_settings_install_beta(), .st = &st::settingsButtonNoIcon, @@ -1112,25 +1112,29 @@ void BuildExportSection( }); } -const auto kMeta = BuildHelper(Advanced::Id(), [](SectionBuilder &builder) { - const auto controller = builder.controller(); - const auto showOther = builder.showOther(); - const auto autoUpdate = cAutoUpdate(); +const auto kMeta = BuildHelper( + Advanced::Id(), + tr::lng_settings_advanced, + [](SectionBuilder &builder) { + const auto controller = builder.controller(); + const auto showOther = builder.showOther(); + const auto autoUpdate = cAutoUpdate(); - if (!autoUpdate) { - BuildUpdateSection(builder, controller, true); - } - BuildDataStorageSection(builder, controller); - BuildAutoDownloadSection(builder, controller); - BuildWindowTitleSection(builder, controller); - BuildSystemIntegrationSection(builder, controller); - BuildPerformanceSection(builder, controller); - BuildSpellcheckerSection(builder, controller); - if (autoUpdate) { - BuildUpdateSection(builder, controller, false); - } - BuildExportSection(builder, controller, showOther); -}, Main::Id()); + if (!autoUpdate) { + BuildUpdateSection(builder, controller, true); + } + BuildDataStorageSection(builder, controller); + BuildAutoDownloadSection(builder, controller); + BuildWindowTitleSection(builder, controller); + BuildSystemIntegrationSection(builder, controller); + BuildPerformanceSection(builder, controller); + BuildSpellcheckerSection(builder, controller); + if (autoUpdate) { + BuildUpdateSection(builder, controller, false); + } + BuildExportSection(builder, controller, showOther); + }, + Main::Id()); } // namespace diff --git a/Telegram/SourceFiles/settings/builder/settings_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_builder.cpp index edd9bae24a..5b2bfe43d6 100644 --- a/Telegram/SourceFiles/settings/builder/settings_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_builder.cpp @@ -7,6 +7,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/builder/settings_builder.h" +#include "apiwrap.h" +#include "lang/lang_keys.h" +#include "api/api_user_privacy.h" #include "boxes/edit_privacy_box.h" #include "main/main_session.h" #include "settings/settings_common.h" @@ -37,6 +40,7 @@ namespace { BuildHelper::BuildHelper( Type sectionId, + tr::phrase<> sectionTitle, FnMut method, Type parentSectionId) : build([=]( @@ -50,7 +54,10 @@ BuildHelper::BuildHelper( controller, Window::GifPauseReason::Layer); auto builder = SectionBuilder(WidgetContext{ - .container = container, + .container = static_cast( + container->add(object_ptr( + container, + object_ptr(container)))->entity()), .controller = controller, .showOther = std::move(showOther), .isPaused = isPaused, @@ -74,8 +81,10 @@ BuildHelper::BuildHelper( , _method(std::move(method)) { Expects(_method != nullptr); - SearchRegistry::Instance().add( + auto ®istry = SearchRegistry::Instance(); + registry.add( sectionId, + sectionTitle, parentSectionId, [=](not_null<::Main::Session*> session) { return index(session); }); } @@ -87,10 +96,12 @@ SearchRegistry &SearchRegistry::Instance() { void SearchRegistry::add( Type sectionId, + tr::phrase<> title, Type parentSectionId, SearchEntriesIndexer indexer) { - _indexers.push_back({ sectionId, std::move(indexer) }); + _sectionTitles[sectionId] = title; _parentSections[sectionId] = parentSectionId; + _indexers.push_back({ sectionId, std::move(indexer) }); } std::vector SearchRegistry::collectAll( @@ -103,6 +114,24 @@ std::vector SearchRegistry::collectAll( return result; } +QString SearchRegistry::sectionTitle(Type sectionId) const { + const auto it = _sectionTitles.find(sectionId); + return (it != _sectionTitles.end()) ? it->second(tr::now) : QString(); +} + +QString SearchRegistry::sectionPath(Type sectionId) const { + auto parts = QStringList(); + auto current = sectionId; + while (current) { + if (const auto title = sectionTitle(current); !title.isEmpty()) { + parts.prepend(title); + } + const auto it = _parentSections.find(current); + current = (it != _parentSections.end()) ? it->second : nullptr; + } + return parts.join(u" > "_q); +} + std::vector BuildHelper::index( not_null session) const { auto entries = std::vector(); @@ -208,17 +237,20 @@ Ui::RpWidget *SectionBuilder::addControl(ControlArgs &&args) { .align = args.align, .highlight = std::move(args.highlight), }; - }, [&] { + }, [&]() mutable { return SearchEntry{ .id = std::move(args.id), .title = ResolveTitle(std::move(args.title)), .keywords = std::move(args.keywords), + .icon = std::move(args.searchIcon), + .checkIcon = args.searchCheckIcon, }; }); } Ui::SettingsButton *SectionBuilder::addButton(ButtonArgs &&args) { const auto &st = args.st ? *args.st : st::settingsButton; + auto iconForSearch = IconDescriptor{ args.icon.icon }; const auto factory = [&](not_null container) { auto button = CreateButtonWithIcon( args.container ? args.container : container.get(), @@ -235,15 +267,20 @@ Ui::SettingsButton *SectionBuilder::addButton(ButtonArgs &&args) { st, rpl::duplicate(args.title)); } + if (args.toggled) { + button->toggleOn(std::move(args.toggled)); + } return std::move(button); }; return static_cast(addControl({ .factory = factory, .id = std::move(args.id), .title = rpl::duplicate(args.title), - .keywords = std::move(args.keywords), .highlight = std::move(args.highlight), .shown = std::move(args.shown), + + .keywords = std::move(args.keywords), + .searchIcon = std::move(iconForSearch), })); } @@ -297,7 +334,7 @@ Ui::SettingsButton *SectionBuilder::addPremiumButton(PremiumButtonArgs &&args) { .keywords = std::move(args.keywords), }); if (result) { - [[maybe_unused]] const auto decorated = AddPremiumStar( + AddPremiumStar( result, args.credits, v::get(_context).isPaused); @@ -306,120 +343,65 @@ Ui::SettingsButton *SectionBuilder::addPremiumButton(PremiumButtonArgs &&args) { } Ui::SettingsButton *SectionBuilder::addPrivacyButton(PrivacyButtonArgs &&args) { - const auto id = args.id; - const auto premium = args.premium; - auto title = std::move(args.title); - return v::match(_context, [&](const WidgetContext &ctx) - -> Ui::SettingsButton* { - const auto button = AddPrivacyButton( - ctx.controller, - ctx.container, - rpl::duplicate(title), - {}, - args.key, - std::move(args.controllerFactory)); + const auto controller = this->controller(); + const auto session = this->session(); + + const auto button = addButton({ + .id = args.id, + .title = rpl::duplicate(args.title), + .st = &st::settingsButtonNoIcon, + .label = PrivacyButtonLabel(session, args.key), + .keywords = args.keywords, + }); + if (button) { + const auto id = args.id; + const auto premium = args.premium; + const auto shower = Ui::CreateChild(button); + const auto factory = args.controllerFactory; + button->addClickHandler([=, key = args.key] { + *shower = session->api().userPrivacy().value( + key + ) | rpl::take( + 1 + ) | rpl::on_next(crl::guard(controller, [=]( + const Api::UserPrivacy::Rule &value) { + controller->show(Box( + controller, + factory(), + value)); + })); + }); if (premium) { AddPrivacyPremiumStar( button, - &ctx.controller->session(), - rpl::duplicate(title), + session, + std::move(args.title), st::settingsButtonNoIcon.padding); } - if (!id.isEmpty()) { - registerHighlight(id, button, {}); - } - return button; - }, [&](const SearchContext &ctx) -> Ui::SettingsButton* { - if (!args.id.isEmpty()) { - ctx.entries->push_back({ - .id = std::move(args.id), - .title = ResolveTitle(std::move(title)), - .keywords = std::move(args.keywords), - }); - } - return nullptr; - }); -} - -Ui::SettingsButton *SectionBuilder::addToggle(ToggleArgs &&args) { - auto highlight = std::move(args.highlight); - const auto id = args.id; - const auto button = v::match(_context, [&](const WidgetContext &ctx) - -> Ui::SettingsButton* { - const auto &st = args.st ? *args.st : st::settingsButton; - const auto target = args.container ? args.container : ctx.container.get(); - const auto button = target->add(CreateButtonWithIcon( - target, - rpl::duplicate(args.title), - st, - std::move(args.icon))); - button->toggleOn(std::move(args.toggled)); - return button; - }, [&](const SearchContext &ctx) -> Ui::SettingsButton* { - if (!args.id.isEmpty()) { - ctx.entries->push_back({ - .id = std::move(args.id), - .title = ResolveTitle(std::move(args.title)), - .keywords = std::move(args.keywords), - }); - } - return nullptr; - }); - if (button && !id.isEmpty()) { - registerHighlight(id, button, std::move(highlight)); } return button; } Ui::Checkbox *SectionBuilder::addCheckbox(CheckboxArgs &&args) { - return v::match(_context, [&](const WidgetContext &ctx) -> Ui::Checkbox* { - const auto checkbox = ctx.container->add( - object_ptr( - ctx.container, - ResolveTitle(rpl::duplicate(args.title)), - args.checked, - st::settingsCheckbox), - st::settingsCheckboxPadding); - return checkbox; - }, [&](const SearchContext &ctx) -> Ui::Checkbox* { - if (!args.id.isEmpty()) { - ctx.entries->push_back({ - .id = std::move(args.id), - .title = ResolveTitle(std::move(args.title)), - .keywords = std::move(args.keywords), - }); - } - return nullptr; - }); -} - -Ui::SlideWrap *SectionBuilder::addSlideCheckbox( - SlideCheckboxArgs &&args) { - return v::match(_context, [&](const WidgetContext &ctx) - -> Ui::SlideWrap* { - const auto wrap = ctx.container->add( - object_ptr>( - ctx.container, - object_ptr( - ctx.container, - ResolveTitle(rpl::duplicate(args.title)), - args.checked, - st::settingsCheckbox), - st::settingsCheckboxPadding)); - if (args.shown) { - wrap->toggleOn(std::move(args.shown)); - } - return wrap; - }, [&](const SearchContext &ctx) -> Ui::SlideWrap* { - if (!args.id.isEmpty()) { - ctx.entries->push_back({ - .id = std::move(args.id), - .title = ResolveTitle(std::move(args.title)), - .keywords = std::move(args.keywords), - }); - } - return nullptr; - }); + const auto factory = [&](not_null container) { + return object_ptr( + container, + ResolveTitle(rpl::duplicate(args.title)), + args.checked, + st::settingsCheckbox); + }; + return static_cast(addControl({ + .factory = factory, + .id = std::move(args.id), + .title = rpl::duplicate(args.title), + .margin = st::settingsCheckboxPadding, + .highlight = std::move(args.highlight), + .shown = std::move(args.shown), + .keywords = std::move(args.keywords), + .searchCheckIcon = (args.checked + ? SearchEntryCheckIcon::Checked + : SearchEntryCheckIcon::Unchecked), + })); } void SectionBuilder::addSubsectionTitle(rpl::producer text) { diff --git a/Telegram/SourceFiles/settings/builder/settings_builder.h b/Telegram/SourceFiles/settings/builder/settings_builder.h index 4fb156de60..66bbc706d7 100644 --- a/Telegram/SourceFiles/settings/builder/settings_builder.h +++ b/Telegram/SourceFiles/settings/builder/settings_builder.h @@ -36,20 +36,36 @@ namespace style { struct SettingsButton; } // namespace style +namespace st { +extern const int &boxRadius; +} // namespace st + namespace Ui { template class SlideWrap; } // namespace Ui +namespace tr { +template +struct phrase; +} // namespace tr + namespace Settings::Builder { class SectionBuilder; +enum class SearchEntryCheckIcon { + None, + Checked, + Unchecked, +}; struct SearchEntry { QString id; QString title; QStringList keywords; Type section; + IconDescriptor icon; + SearchEntryCheckIcon checkIcon = SearchEntryCheckIcon::None; explicit operator bool() const { return !id.isEmpty(); @@ -70,15 +86,20 @@ public: void add( Type sectionId, + tr::phrase<> title, Type parentSectionId, SearchEntriesIndexer indexer); [[nodiscard]] std::vector collectAll( not_null<::Main::Session*> session) const; + [[nodiscard]] QString sectionTitle(Type sectionId) const; + [[nodiscard]] QString sectionPath(Type sectionId) const; + private: std::vector _indexers; base::flat_map _parentSections; + base::flat_map> _sectionTitles; }; @@ -86,6 +107,7 @@ class BuildHelper { public: BuildHelper( Type sectionId, + tr::phrase<> sectionTitle, FnMut method, Type parentSectionId = nullptr); @@ -147,11 +169,14 @@ public: Fn(not_null)> factory; QString id; rpl::producer title; - QStringList keywords; style::margins margin; style::align align = style::al_left; HighlightArgs highlight; rpl::producer shown; + + QStringList keywords; + IconDescriptor searchIcon; + SearchEntryCheckIcon searchCheckIcon = SearchEntryCheckIcon::None; }; Ui::RpWidget *addControl(ControlArgs &&args); @@ -162,6 +187,7 @@ public: IconDescriptor icon; Ui::VerticalLayout *container = nullptr; rpl::producer label; + rpl::producer toggled; Fn onClick; QStringList keywords; HighlightArgs highlight; @@ -198,35 +224,16 @@ public: }; Ui::SettingsButton *addPrivacyButton(PrivacyButtonArgs &&args); - struct ToggleArgs { - QString id; - rpl::producer title; - const style::SettingsButton *st = nullptr; - IconDescriptor icon; - Ui::VerticalLayout *container = nullptr; - rpl::producer toggled; - QStringList keywords; - HighlightArgs highlight; - }; - Ui::SettingsButton *addToggle(ToggleArgs &&args); - struct CheckboxArgs { QString id; rpl::producer title; bool checked = false; QStringList keywords; + HighlightArgs highlight = { .radius = st::boxRadius }; + rpl::producer shown; }; Ui::Checkbox *addCheckbox(CheckboxArgs &&args); - struct SlideCheckboxArgs { - QString id; - rpl::producer title; - bool checked = false; - rpl::producer shown; - QStringList keywords; - }; - Ui::SlideWrap *addSlideCheckbox(SlideCheckboxArgs &&args); - void addSubsectionTitle(rpl::producer text); void addDivider(); void addDividerText(rpl::producer text); diff --git a/Telegram/SourceFiles/settings/builder/settings_chat_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_chat_builder.cpp index ca6fda5a47..b354c30b4e 100644 --- a/Telegram/SourceFiles/settings/builder/settings_chat_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_chat_builder.cpp @@ -54,12 +54,16 @@ void BuildChatSectionContent( SetupArchive(controller, container, showOther); } -const auto kMeta = BuildHelper(Chat::Id(), [](SectionBuilder &builder) { - const auto controller = builder.controller(); - const auto showOther = builder.showOther(); - const auto highlights = builder.highlights(); - BuildChatSectionContent(builder, controller, showOther, highlights); -}, Main::Id()); +const auto kMeta = BuildHelper( + Chat::Id(), + tr::lng_settings_section_chat_settings, + [](SectionBuilder &builder) { + const auto controller = builder.controller(); + const auto showOther = builder.showOther(); + const auto highlights = builder.highlights(); + BuildChatSectionContent(builder, controller, showOther, highlights); + }, + Main::Id()); } // namespace diff --git a/Telegram/SourceFiles/settings/builder/settings_main_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_main_builder.cpp index e4af14c310..9ba58eea43 100644 --- a/Telegram/SourceFiles/settings/builder/settings_main_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_main_builder.cpp @@ -138,14 +138,13 @@ void BuildSectionButtons(SectionBuilder &builder) { .keywords = { u"sessions"_q, u"calls"_q }, }); - const auto window = controller ? &controller->window() : nullptr; builder.addButton({ .id = u"main/power"_q, .title = tr::lng_settings_power_menu(), .icon = { &st::menuIconPowerUsage }, - .onClick = window - ? Fn([=] { window->show(Box(PowerSavingBox, PowerSaving::Flags())); }) - : Fn(nullptr), + .onClick = [=] { + controller->show(Box(PowerSavingBox, PowerSaving::Flags())); + }, .keywords = { u"battery"_q, u"animations"_q, u"power"_q, u"saving"_q }, }); @@ -158,10 +157,10 @@ void BuildSectionButtons(SectionBuilder &builder) { ) | rpl::then( Lang::GetInstance().idChanges() ) | rpl::map([] { return Lang::GetInstance().nativeName(); }), - .onClick = controller ? Fn([=] { + .onClick = [=] { static auto Guard = base::binary_guard(); Guard = LanguageBox::Show(controller); - }) : Fn(nullptr), + }, .keywords = { u"translate"_q, u"localization"_q, u"language"_q }, }); } @@ -205,10 +204,10 @@ void BuildPremiumSection(SectionBuilder &builder) { builder.addPremiumButton({ .id = u"main/premium"_q, .title = tr::lng_premium_summary_title(), - .onClick = controller ? Fn([=] { + .onClick = [=] { controller->setPremiumRef("settings"); showOther(PremiumId()); - }) : Fn(nullptr), + }, .keywords = { u"subscription"_q }, }); @@ -217,16 +216,16 @@ void BuildPremiumSection(SectionBuilder &builder) { .id = u"main/credits"_q, .title = tr::lng_settings_credits(), .label = session->credits().balanceValue( - ) | rpl::map([](CreditsAmount c) { - return c - ? Lang::FormatCreditsAmountToShort(c).string - : QString(); - }), + ) | rpl::map([](CreditsAmount c) { + return c + ? Lang::FormatCreditsAmountToShort(c).string + : QString(); + }), .credits = true, - .onClick = controller ? Fn([=] { + .onClick = [=] { controller->setPremiumRef("settings"); showOther(CreditsId()); - }) : Fn(nullptr), + }, .keywords = { u"stars"_q, u"balance"_q }, }); @@ -237,14 +236,12 @@ void BuildPremiumSection(SectionBuilder &builder) { .icon = { &st::menuIconTon }, .label = session->credits().tonBalanceValue( ) | rpl::map([](CreditsAmount c) { - return c - ? Lang::FormatCreditsAmountToShort(c).string - : QString(); + return c ? Lang::FormatCreditsAmountToShort(c).string : u""_q; }), - .onClick = controller ? Fn([=] { + .onClick = [=] { controller->setPremiumRef("settings"); showOther(CurrencyId()); - }) : Fn(nullptr), + }, .keywords = { u"ton"_q, u"crypto"_q, u"wallet"_q }, .shown = session->credits().tonBalanceValue( ) | rpl::map([](CreditsAmount c) { return !c.empty(); }), @@ -254,9 +251,7 @@ void BuildPremiumSection(SectionBuilder &builder) { .id = u"main/business"_q, .title = tr::lng_business_title(), .icon = { .icon = &st::menuIconShop }, - .onClick = showOther - ? Fn([=] { showOther(BusinessId()); }) - : Fn(nullptr), + .onClick = [=] { showOther(BusinessId()); }, .keywords = { u"work"_q, u"company"_q }, }); @@ -265,9 +260,7 @@ void BuildPremiumSection(SectionBuilder &builder) { .id = u"main/send-gift"_q, .title = tr::lng_settings_gift_premium(), .icon = { .icon = &st::menuIconGiftPremium, .newBadge = true }, - .onClick = controller - ? Fn([=] { Ui::ChooseStarGiftRecipient(controller); }) - : Fn(nullptr), + .onClick = [=] { Ui::ChooseStarGiftRecipient(controller); }, .keywords = { u"present"_q, u"send"_q }, }); } @@ -327,19 +320,22 @@ void BuildValidationSuggestions(SectionBuilder &builder) { }); } -const auto kMeta = BuildHelper(Main::Id(), [](SectionBuilder &builder) { - builder.addDivider(); - builder.addSkip(); +const auto kMeta = BuildHelper( + Main::Id(), + tr::lng_menu_settings, + [](SectionBuilder &builder) { + builder.addDivider(); + builder.addSkip(); - BuildValidationSuggestions(builder); - BuildSectionButtons(builder); + BuildValidationSuggestions(builder); + BuildSectionButtons(builder); - builder.addSkip(); + builder.addSkip(); - BuildInterfaceScale(builder); - BuildPremiumSection(builder); - BuildHelpSection(builder); -}); + BuildInterfaceScale(builder); + BuildPremiumSection(builder); + BuildHelpSection(builder); + }); } // namespace diff --git a/Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp index e2e1595db7..ab40adcba9 100644 --- a/Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp @@ -63,7 +63,7 @@ void BuildMultiAccountSection( const auto container = builder.container(); if (!container) { - builder.addToggle({ + builder.addButton({ .id = u"notifications/multi_account"_q, .title = tr::lng_settings_notify_all(), .toggled = rpl::single(Core::App().settings().notifyFromAll()), @@ -75,7 +75,7 @@ void BuildMultiAccountSection( return; } - const auto fromAll = builder.addToggle({ + const auto fromAll = builder.addButton({ .id = u"notifications/accounts"_q, .title = tr::lng_settings_notify_all(), .st = &st::settingsButtonNoIcon, @@ -120,7 +120,7 @@ void BuildGlobalNotificationsSection( const auto desktopToggles = container ? container->lifetime().make_state>() : nullptr; - const auto desktop = builder.addToggle({ + const auto desktop = builder.addButton({ .id = u"notifications/desktop"_q, .title = tr::lng_settings_desktop_notify(), .icon = { &st::menuIconNotifications }, @@ -133,7 +133,7 @@ void BuildGlobalNotificationsSection( const auto flashbounceToggles = container ? container->lifetime().make_state>() : nullptr; - const auto flashbounce = builder.addToggle({ + const auto flashbounce = builder.addButton({ .id = u"notifications/flash"_q, .title = (Platform::IsWindows() ? tr::lng_settings_alert_windows @@ -153,7 +153,7 @@ void BuildGlobalNotificationsSection( const auto allowed = [=] { return Core::App().settings().soundNotify(); }; - const auto sound = builder.addToggle({ + const auto sound = builder.addButton({ .id = u"notifications/sound"_q, .title = tr::lng_settings_sound_allowed(), .icon = { &st::menuIconUnmute }, @@ -366,7 +366,7 @@ void BuildEventNotificationsSection( session->api().contactSignupSilentCurrent().value_or(false) ) | rpl::then(session->api().contactSignupSilent()); - const auto joined = builder.addToggle({ + const auto joined = builder.addButton({ .id = u"notifications/events/joined"_q, .title = tr::lng_settings_events_joined(), .icon = { &st::menuIconInvite }, @@ -383,7 +383,7 @@ void BuildEventNotificationsSection( }, joined->lifetime()); } - const auto pinned = builder.addToggle({ + const auto pinned = builder.addButton({ .id = u"notifications/events/pinned"_q, .title = tr::lng_settings_events_pinned(), .icon = { &st::menuIconPin }, @@ -419,7 +419,7 @@ void BuildCallNotificationsSection( const auto authorizations = &session->api().authorizations(); authorizations->reload(); - const auto acceptCalls = builder.addToggle({ + const auto acceptCalls = builder.addButton({ .id = u"notifications/calls/accept"_q, .title = tr::lng_settings_call_accept_calls(), .icon = { &st::menuIconCallsReceive }, @@ -448,7 +448,7 @@ void BuildBadgeCounterSection( const auto &settings = Core::App().settings(); - const auto muted = builder.addToggle({ + const auto muted = builder.addButton({ .id = u"notifications/include-muted-chats"_q, .title = tr::lng_settings_include_muted(), .st = &st::settingsButtonNoIcon, @@ -458,7 +458,7 @@ void BuildBadgeCounterSection( const auto hasFolders = controller && controller->session().data().chatsFilters().has(); - const auto mutedFolders = hasFolders ? builder.addToggle({ + const auto mutedFolders = hasFolders ? builder.addButton({ .id = u"notifications/badge/muted_folders"_q, .title = tr::lng_settings_include_muted_folders(), .st = &st::settingsButtonNoIcon, @@ -466,7 +466,7 @@ void BuildBadgeCounterSection( .keywords = { u"muted"_q, u"folders"_q }, }) : nullptr; - const auto count = builder.addToggle({ + const auto count = builder.addButton({ .id = u"notifications/count-unread-messages"_q, .title = tr::lng_settings_count_unread(), .st = &st::settingsButtonNoIcon, @@ -534,7 +534,7 @@ void BuildSystemIntegrationAndAdvancedSection( } const auto &settings = Core::App().settings(); - const auto native = nativeText ? builder.addToggle({ + const auto native = nativeText ? builder.addButton({ .id = u"notifications/native"_q, .title = std::move(nativeText), .st = &st::settingsButtonNoIcon, @@ -721,10 +721,14 @@ void BuildNotificationsSectionContent( BuildSystemIntegrationAndAdvancedSection(builder, controller); } -const auto kMeta = BuildHelper(Notifications::Id(), [](SectionBuilder &builder) { - const auto controller = builder.controller(); - BuildNotificationsSectionContent(builder, controller); -}, Main::Id()); +const auto kMeta = BuildHelper( + Notifications::Id(), + tr::lng_settings_section_notify, + [](SectionBuilder &builder) { + const auto controller = builder.controller(); + BuildNotificationsSectionContent(builder, controller); + }, + Main::Id()); } // namespace diff --git a/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.cpp index ec100d8f18..e37af78889 100644 --- a/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_privacy_security_builder.cpp @@ -484,7 +484,7 @@ void BuildTopPeersSection( builder.addSkip(); builder.addSubsectionTitle(tr::lng_settings_top_peers_title()); - const auto toggle = builder.addToggle({ + const auto toggle = builder.addButton({ .id = u"privacy/top_peers"_q, .title = tr::lng_settings_top_peers_suggest(), .st = &st::settingsButtonNoIcon, @@ -569,11 +569,15 @@ void BuildPrivacySecuritySectionContent( BuildSelfDestructionSection(builder, controller, trigger()); } -const auto kMeta = BuildHelper(PrivacySecurity::Id(), [](SectionBuilder &builder) { - const auto controller = builder.controller(); - const auto showOther = builder.showOther(); - BuildPrivacySecuritySectionContent(builder, controller, showOther); -}, Main::Id()); +const auto kMeta = BuildHelper( + PrivacySecurity::Id(), + tr::lng_settings_section_privacy, + [](SectionBuilder &builder) { + const auto controller = builder.controller(); + const auto showOther = builder.showOther(); + BuildPrivacySecuritySectionContent(builder, controller, showOther); + }, + Main::Id()); } // namespace diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 8e4e5ef1e4..8a3187ade7 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -535,6 +535,16 @@ settingsNotificationTypeDetails: FlatLabel(defaultFlatLabel) { textFg: windowSubTextFg; } +settingsSearchResult: SettingsButton(settingsButton) { + height: 40px; + padding: margins(60px, 4px, 22px, 4px); +} +settingsSearchResultNoIcon: SettingsButton(settingsSearchResult) { + padding: margins(22px, 4px, 22px, 4px); +} +settingsSearchResultDetails: FlatLabel(settingsNotificationTypeDetails) { +} + powerSavingButton: SettingsButton(settingsButton) { style: boxTextStyle; padding: margins(57px, 8px, 22px, 8px); diff --git a/Telegram/SourceFiles/settings/settings_common.cpp b/Telegram/SourceFiles/settings/settings_common.cpp index d4bdddc518..addab0de05 100644 --- a/Telegram/SourceFiles/settings/settings_common.cpp +++ b/Telegram/SourceFiles/settings/settings_common.cpp @@ -641,7 +641,7 @@ void AddLottieIconWithCircle( }, circle->lifetime()); } -not_null AddPremiumStar( +void AddPremiumStar( not_null button, bool credits, Fn isPaused) { @@ -716,8 +716,6 @@ not_null AddPremiumStar( ministarsContainer->resize(fullHeight, fullHeight); ministars->setCenter(ministarsContainer->rect()); - - return button; } } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_common.h b/Telegram/SourceFiles/settings/settings_common.h index 1d81dc2bfc..7a0d159db1 100644 --- a/Telegram/SourceFiles/settings/settings_common.h +++ b/Telegram/SourceFiles/settings/settings_common.h @@ -291,7 +291,7 @@ void AddLottieIconWithCircle( QMargins iconPadding, QSize circleSize); -[[nodiscard]] not_null AddPremiumStar( +void AddPremiumStar( not_null button, bool credits, Fn isPaused); diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.cpp b/Telegram/SourceFiles/settings/settings_privacy_security.cpp index da1befcdf0..e18e42fecc 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.cpp +++ b/Telegram/SourceFiles/settings/settings_privacy_security.cpp @@ -277,6 +277,12 @@ rpl::producer BlockedPeersCount(not_null<::Main::Session*> session) { } // namespace +rpl::producer PrivacyButtonLabel( + not_null<::Main::Session*> session, + Privacy::Key key) { + return PrivacyString(session, key); +} + void AddPrivacyPremiumStar( not_null button, not_null session, diff --git a/Telegram/SourceFiles/settings/settings_privacy_security.h b/Telegram/SourceFiles/settings/settings_privacy_security.h index 8c18c493d1..8daad3f9dc 100644 --- a/Telegram/SourceFiles/settings/settings_privacy_security.h +++ b/Telegram/SourceFiles/settings/settings_privacy_security.h @@ -40,6 +40,10 @@ not_null AddPrivacyButton( Fn()> controllerFactory, const style::SettingsButton *stOverride = nullptr); +[[nodiscard]] rpl::producer PrivacyButtonLabel( + not_null<::Main::Session*> session, + Api::UserPrivacy::Key key); + void AddPrivacyPremiumStar( not_null button, not_null<::Main::Session*> session, diff --git a/Telegram/SourceFiles/settings/settings_search.cpp b/Telegram/SourceFiles/settings/settings_search.cpp index b0d009e1d1..5a3ed47c9e 100644 --- a/Telegram/SourceFiles/settings/settings_search.cpp +++ b/Telegram/SourceFiles/settings/settings_search.cpp @@ -10,9 +10,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "settings/builder/settings_builder.h" #include "settings/settings_common.h" +#include "ui/painter.h" #include "ui/search_field_controller.h" #include "ui/vertical_list.h" #include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" #include "ui/widgets/fields/input_field.h" #include "ui/widgets/labels.h" #include "ui/wrap/padding_wrap.h" @@ -22,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_layers.h" #include "styles/style_menu_icons.h" #include "styles/style_settings.h" +#include "styles/style_widgets.h" namespace Settings { namespace { @@ -75,6 +78,70 @@ struct SearchResult { return results; } +void SetupCheckIcon( + not_null button, + Builder::SearchEntryCheckIcon checkIcon, + const style::SettingsButton &st) { + struct CheckWidget { + CheckWidget(QWidget *parent, bool checked) + : widget(parent) + , view(st::defaultCheck, checked) { + view.finishAnimating(); + } + Ui::RpWidget widget; + Ui::CheckView view; + }; + const auto checked = (checkIcon == Builder::SearchEntryCheckIcon::Checked); + const auto check = button->lifetime().make_state( + button, + checked); + check->widget.setAttribute(Qt::WA_TransparentForMouseEvents); + check->widget.resize(check->view.getSize()); + check->widget.show(); + + button->sizeValue( + ) | rpl::on_next([=, left = st.iconLeft](QSize size) { + check->widget.moveToLeft( + left, + (size.height() - check->widget.height()) / 2, + size.width()); + }, check->widget.lifetime()); + + check->widget.paintRequest( + ) | rpl::on_next([=] { + auto p = QPainter(&check->widget); + p.setOpacity(0.5); + check->view.paint(p, 0, 0, check->widget.width()); + }, check->widget.lifetime()); +} + +[[nodiscard]] not_null CreateSearchResultButton( + not_null container, + const QString &title, + const QString &subtitle, + const style::SettingsButton &st, + IconDescriptor &&icon, + Builder::SearchEntryCheckIcon checkIcon) { + const auto button = AddButtonWithIcon( + container, + rpl::single(title), + st, + std::move(icon)); + if (checkIcon != Builder::SearchEntryCheckIcon::None) { + SetupCheckIcon(button, checkIcon, st); + } + const auto details = Ui::CreateChild( + button.get(), + subtitle, + st::settingsSearchResultDetails); + details->show(); + details->moveToLeft( + st.padding.left(), + st.padding.top() + st.height - details->height()); + details->setAttribute(Qt::WA_TransparentForMouseEvents); + return button; +} + } // namespace Search::Search( @@ -89,57 +156,87 @@ rpl::producer Search::title() { } void Search::setInnerFocus() { - _searchField->setFocus(); + if (_searchField) { + _searchField->setFocus(); + } +} + +base::weak_qptr Search::createPinnedToTop( + not_null parent) { + _searchController = std::make_unique(""); + auto rowView = _searchController->createRowView( + parent, + st::infoLayerMediaSearch); + _searchField = rowView.field; + + const auto searchContainer = Ui::CreateChild( + parent.get(), + st::infoLayerMediaSearch.height); + const auto wrap = rowView.wrap.release(); + wrap->setParent(searchContainer); + wrap->show(); + + searchContainer->widthValue( + ) | rpl::on_next([=](int width) { + wrap->resizeToWidth(width); + wrap->moveToLeft(0, 0); + }, searchContainer->lifetime()); + + _searchController->queryChanges() | rpl::on_next([=](QString &&query) { + rebuildResults(std::move(query)); + }, searchContainer->lifetime()); + + return base::make_weak(not_null{ searchContainer }); } void Search::setupContent() { const auto content = Ui::CreateChild(this); - auto searchController = std::make_unique(""); - auto rowView = searchController->createRowView( - content, - st::infoLayerMediaSearch); - _searchField = rowView.field; - - const auto searchContainer = content->add( - object_ptr( - content, - st::infoLayerMediaSearch.height)); - _searchWrap = std::move(rowView.wrap); - _searchWrap->setParent(searchContainer); - _searchWrap->show(); - - searchContainer->widthValue( - ) | rpl::on_next([wrap = _searchWrap.get()](int width) { - wrap->resizeToWidth(width); - wrap->moveToLeft(0, 0); - }, searchContainer->lifetime()); - _resultsContainer = content->add( object_ptr(content)); - searchController->queryChanges() | rpl::on_next([=](QString &&query) { - rebuildResults(std::move(query)); - }, content->lifetime()); - - _searchController = std::move(searchController); + setupCustomizations(); + rebuildResults(QString()); Ui::ResizeFitChild(this, content); } +void Search::setupCustomizations() { + const auto isPaused = Window::PausedIn( + controller(), + Window::GifPauseReason::Layer); + const auto add = [&](const QString &id, ResultCustomization value) { + _customizations[id] = std::move(value); + }; + + add(u"main/credits"_q, { + .hook = [=](not_null b) { + AddPremiumStar(b, true, isPaused); + }, + .st = &st::settingsSearchResult, + }); + add(u"main/premium"_q, { + .hook = [=](not_null b) { + AddPremiumStar(b, false, isPaused); + }, + .st = &st::settingsSearchResult, + }); +} + void Search::rebuildResults(const QString &query) { while (_resultsContainer->count() > 0) { delete _resultsContainer->widgetAt(0); } - if (query.trimmed().isEmpty()) { - _resultsContainer->resizeToWidth(_resultsContainer->width()); - return; - } - const auto entries = Builder::SearchRegistry::Instance().collectAll( &controller()->session()); - const auto results = FilterAndSort(entries, query); + const auto results = query.trimmed().isEmpty() + ? ranges::views::all(entries) + | ranges::views::transform([](const auto &e) { + return SearchResult{ e, 0 }; + }) + | ranges::to>() + : FilterAndSort(entries, query); if (results.empty()) { _resultsContainer->add( @@ -150,13 +247,38 @@ void Search::rebuildResults(const QString &query) { st::defaultSubsectionTitlePadding); } else { const auto showOther = showOtherMethod(); + const auto ®istry = Builder::SearchRegistry::Instance(); + for (const auto &result : results) { - const auto entry = result.entry; - const auto button = AddButtonWithIcon( + const auto &entry = result.entry; + const auto subtitle = registry.sectionPath(entry.section); + const auto hasIcon = entry.icon.icon != nullptr; + const auto hasCheckIcon = !hasIcon + && (entry.checkIcon != Builder::SearchEntryCheckIcon::None); + + const auto it = _customizations.find(entry.id); + const auto custom = (it != _customizations.end()) + ? &it->second + : nullptr; + + const auto &st = custom && custom->st + ? *custom->st + : (hasIcon || hasCheckIcon) + ? st::settingsSearchResult + : st::settingsSearchResultNoIcon; + + const auto button = CreateSearchResultButton( _resultsContainer, - rpl::single(entry.title), - st::settingsButton, - { &st::menuIconInfo }); + entry.title, + subtitle, + st, + IconDescriptor{ entry.icon.icon }, + hasCheckIcon ? entry.checkIcon : Builder::SearchEntryCheckIcon::None); + + if (custom && custom->hook) { + custom->hook(button); + } + const auto targetSection = entry.section; const auto controlId = entry.id; button->addClickHandler([=] { diff --git a/Telegram/SourceFiles/settings/settings_search.h b/Telegram/SourceFiles/settings/settings_search.h index 68408d50cb..22a7abbb7b 100644 --- a/Telegram/SourceFiles/settings/settings_search.h +++ b/Telegram/SourceFiles/settings/settings_search.h @@ -29,15 +29,23 @@ public: [[nodiscard]] rpl::producer title() override; void setInnerFocus() override; + [[nodiscard]] base::weak_qptr createPinnedToTop( + not_null parent) override; private: + struct ResultCustomization { + Fn)> hook; + const style::SettingsButton *st = nullptr; + }; + void setupContent(); + void setupCustomizations(); void rebuildResults(const QString &query); std::unique_ptr _searchController; - base::unique_qptr _searchWrap; Ui::InputField *_searchField = nullptr; Ui::VerticalLayout *_resultsContainer = nullptr; + base::flat_map _customizations; };