From 0c5c5d3c42f931f6e387dc03e599433920e045cf Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 2 Jan 2026 17:19:03 +0400 Subject: [PATCH] Migrate all notification settings to builder. --- Telegram/CMakeLists.txt | 2 + .../settings/builder/settings_builder.cpp | 69 ++ .../settings/builder/settings_builder.h | 23 + .../settings_notifications_builder.cpp | 770 ++++++++++++++++++ .../builder/settings_notifications_builder.h | 29 + .../settings/settings_notifications.cpp | 654 +-------------- .../settings/settings_notifications.h | 4 + .../settings/settings_notifications_common.h | 110 +++ 8 files changed, 1029 insertions(+), 632 deletions(-) create mode 100644 Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp create mode 100644 Telegram/SourceFiles/settings/builder/settings_notifications_builder.h create mode 100644 Telegram/SourceFiles/settings/settings_notifications_common.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 5ac4f287e4..99073446fc 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1571,6 +1571,8 @@ PRIVATE settings/builder/settings_builder.h settings/builder/settings_main_builder.cpp settings/builder/settings_main_builder.h + settings/builder/settings_notifications_builder.cpp + settings/builder/settings_notifications_builder.h settings/settings_notifications.cpp settings/settings_notifications.h settings/settings_notifications_type.cpp diff --git a/Telegram/SourceFiles/settings/builder/settings_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_builder.cpp index 3290e56e6c..108f2ea49d 100644 --- a/Telegram/SourceFiles/settings/builder/settings_builder.cpp +++ b/Telegram/SourceFiles/settings/builder/settings_builder.cpp @@ -250,6 +250,75 @@ Ui::SettingsButton *SectionBuilder::addPremiumButton(PremiumButtonArgs &&args) { }); } +Ui::SettingsButton *SectionBuilder::addToggle(ToggleArgs &&args) { + return v::match(_context, [&](const WidgetContext &ctx) + -> Ui::SettingsButton* { + const auto &st = args.st ? *args.st : st::settingsButton; + const auto button = ctx.container->add(CreateButtonWithIcon( + ctx.container, + 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; + }); +} + +Ui::SlideWrap *SectionBuilder::addSlideToggle( + SlideToggleArgs &&args) { + return v::match(_context, [&](const WidgetContext &ctx) + -> Ui::SlideWrap* { + const auto &st = args.st ? *args.st : st::settingsButton; + const auto wrap = ctx.container->add( + object_ptr>( + ctx.container, + CreateButtonWithIcon( + ctx.container, + rpl::duplicate(args.title), + st, + std::move(args.icon)))); + if (args.shown) { + wrap->toggleOn(std::move(args.shown)); + } + const auto button = wrap->entity(); + button->toggleOn(std::move(args.toggled)); + 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; + }); +} + +void SectionBuilder::addSubsectionTitle(rpl::producer text) { + v::match(_context, [&](const WidgetContext &ctx) { + AddSubsectionTitle(ctx.container, std::move(text)); + }, [](const SearchContext &) { + }); +} + +Ui::VerticalLayout *SectionBuilder::container() const { + return v::match(_context, [](const WidgetContext &ctx) { + return ctx.container.get(); + }, [](const SearchContext &) -> Ui::VerticalLayout* { + return nullptr; + }); +} + Window::SessionController *SectionBuilder::controller() const { return v::match(_context, [](const WidgetContext &ctx) { return ctx.controller.get(); diff --git a/Telegram/SourceFiles/settings/builder/settings_builder.h b/Telegram/SourceFiles/settings/builder/settings_builder.h index 965ca900b9..2e2b7071a1 100644 --- a/Telegram/SourceFiles/settings/builder/settings_builder.h +++ b/Telegram/SourceFiles/settings/builder/settings_builder.h @@ -121,11 +121,34 @@ public: }; Ui::SettingsButton *addPremiumButton(PremiumButtonArgs &&args); + struct ToggleArgs { + QString id; + rpl::producer title; + const style::SettingsButton *st = nullptr; + IconDescriptor icon; + rpl::producer toggled; + QStringList keywords; + }; + Ui::SettingsButton *addToggle(ToggleArgs &&args); + + struct SlideToggleArgs { + QString id; + rpl::producer title; + const style::SettingsButton *st = nullptr; + IconDescriptor icon; + rpl::producer toggled; + rpl::producer shown; + QStringList keywords; + }; + Ui::SlideWrap *addSlideToggle(SlideToggleArgs &&args); + + void addSubsectionTitle(rpl::producer text); void addDivider(); void addDividerText(rpl::producer text); void addSkip(); void addSkip(int height); + [[nodiscard]] Ui::VerticalLayout *container() const; [[nodiscard]] Window::SessionController *controller() const; [[nodiscard]] Fn showOther() const; diff --git a/Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp b/Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp new file mode 100644 index 0000000000..9eba3937bf --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_notifications_builder.cpp @@ -0,0 +1,770 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "settings/builder/settings_notifications_builder.h" + +#include "api/api_authorizations.h" +#include "api/api_ringtones.h" +#include "apiwrap.h" +#include "base/platform/base_platform_info.h" +#include "boxes/ringtones_box.h" +#include "core/application.h" +#include "data/data_chat_filters.h" +#include "data/data_session.h" +#include "data/notify/data_notify_settings.h" +#include "data/notify/data_peer_notify_volume.h" +#include "lang/lang_keys.h" +#include "main/main_account.h" +#include "main/main_domain.h" +#include "main/main_session.h" +#include "mainwindow.h" +#include "platform/platform_notifications_manager.h" +#include "platform/platform_specific.h" +#include "settings/builder/settings_builder.h" +#include "settings/settings_notifications_common.h" +#include "settings/settings_notifications_type.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/widgets/continuous_sliders.h" +#include "ui/widgets/discrete_sliders.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "window/notifications_manager.h" +#include "window/window_session_controller.h" +#include "styles/style_settings.h" +#include "styles/style_menu_icons.h" + +#include +#include + +namespace Settings::Builder { +namespace { + +constexpr auto kDefaultDisplayIndex = -1; + +using NotifyView = Core::Settings::NotifyView; +using ChangeType = Window::Notifications::ChangeType; + +void BuildMultiAccountSection( + SectionBuilder &builder, + Window::SessionController *controller) { + if (Core::App().domain().accounts().size() < 2) { + return; + } + + builder.addSubsectionTitle(tr::lng_settings_show_from()); + + const auto container = builder.container(); + if (!container) { + builder.addToggle({ + .id = u"notifications/multi_account"_q, + .title = tr::lng_settings_notify_all(), + .toggled = rpl::single(Core::App().settings().notifyFromAll()), + .keywords = { u"all accounts"_q, u"multiple"_q }, + }); + builder.addSkip(); + builder.addDividerText(tr::lng_settings_notify_all_about()); + builder.addSkip(); + return; + } + + const auto fromAll = builder.addToggle({ + .id = u"notifications/multi_account"_q, + .title = tr::lng_settings_notify_all(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(Core::App().settings().notifyFromAll()), + .keywords = { u"all accounts"_q, u"multiple"_q }, + }); + + if (fromAll) { + fromAll->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().notifyFromAll()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setNotifyFromAll(checked); + Core::App().saveSettingsDelayed(); + if (!checked) { + auto ¬ifications = Core::App().notifications(); + const auto &list = Core::App().domain().accounts(); + for (const auto &[index, account] : list) { + if (account.get() == &Core::App().domain().active()) { + continue; + } else if (const auto session = account->maybeSession()) { + notifications.clearFromSession(session); + } + } + } + }, fromAll->lifetime()); + } + + builder.addSkip(); + builder.addDividerText(tr::lng_settings_notify_all_about()); + builder.addSkip(); +} + +void BuildGlobalNotificationsSection( + SectionBuilder &builder, + Window::SessionController *controller) { + builder.addSubsectionTitle(tr::lng_settings_notify_global()); + + const auto container = builder.container(); + const auto &settings = Core::App().settings(); + + const auto desktopToggles = container + ? container->lifetime().make_state>() + : nullptr; + const auto desktop = builder.addToggle({ + .id = u"notifications/desktop"_q, + .title = tr::lng_settings_desktop_notify(), + .icon = { &st::menuIconNotifications }, + .toggled = desktopToggles + ? desktopToggles->events_starting_with(settings.desktopNotify()) + : rpl::single(settings.desktopNotify()), + .keywords = { u"desktop"_q, u"popup"_q, u"show"_q }, + }); + + const auto flashbounceToggles = container + ? container->lifetime().make_state>() + : nullptr; + const auto flashbounce = builder.addToggle({ + .id = u"notifications/flash"_q, + .title = (Platform::IsWindows() + ? tr::lng_settings_alert_windows + : Platform::IsMac() + ? tr::lng_settings_alert_mac + : tr::lng_settings_alert_linux)(), + .icon = { &st::menuIconDockBounce }, + .toggled = flashbounceToggles + ? flashbounceToggles->events_starting_with(settings.flashBounceNotify()) + : rpl::single(settings.flashBounceNotify()), + .keywords = { u"flash"_q, u"bounce"_q, u"taskbar"_q }, + }); + + const auto soundAllowed = container + ? container->lifetime().make_state>() + : nullptr; + const auto allowed = [=] { + return Core::App().settings().soundNotify(); + }; + const auto sound = builder.addToggle({ + .id = u"notifications/sound"_q, + .title = tr::lng_settings_sound_allowed(), + .icon = { &st::menuIconUnmute }, + .toggled = soundAllowed + ? soundAllowed->events_starting_with(allowed()) + : rpl::single(allowed()), + .keywords = { u"sound"_q, u"audio"_q, u"mute"_q }, + }); + + if (container && controller) { + const auto session = &controller->session(); + Ui::AddRingtonesVolumeSlider( + container, + rpl::single(true), + tr::lng_settings_master_volume_notifications(), + Data::VolumeController{ + .volume = []() -> ushort { + const auto volume + = Core::App().settings().notificationsVolume(); + return volume ? volume : 100; + }, + .saveVolume = [=](ushort volume) { + Core::App().notifications().playSound( + session, + 0, + volume / 100.); + Core::App().settings().setNotificationsVolume(volume); + Core::App().saveSettingsDelayed(); + }}); + } + + builder.addSkip(); + + if (desktop) { + const auto changed = [=](ChangeType change) { + Core::App().saveSettingsDelayed(); + Core::App().notifications().notifySettingsChanged(change); + }; + + desktop->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().desktopNotify()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setDesktopNotify(checked); + changed(ChangeType::DesktopEnabled); + }, desktop->lifetime()); + + if (sound) { + sound->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().soundNotify()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setSoundNotify(checked); + changed(ChangeType::SoundEnabled); + }, sound->lifetime()); + } + + if (flashbounce) { + flashbounce->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().flashBounceNotify()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setFlashBounceNotify(checked); + changed(ChangeType::FlashBounceEnabled); + }, flashbounce->lifetime()); + } + + Core::App().notifications().settingsChanged( + ) | rpl::on_next([=](ChangeType change) { + if (change == ChangeType::DesktopEnabled) { + desktopToggles->fire(Core::App().settings().desktopNotify()); + } else if (change == ChangeType::SoundEnabled) { + soundAllowed->fire(allowed()); + } else if (change == ChangeType::FlashBounceEnabled) { + flashbounceToggles->fire( + Core::App().settings().flashBounceNotify()); + } + }, desktop->lifetime()); + } +} + +void BuildNotifyViewSection( + SectionBuilder &builder, + Window::SessionController *controller, + rpl::lifetime &lifetime) { + const auto container = builder.container(); + if (!container || !controller) { + return; + } + + const auto &settings = Core::App().settings(); + const auto checkboxes = SetupNotifyViewOptions( + controller, + container, + (settings.notifyView() <= NotifyView::ShowName), + (settings.notifyView() <= NotifyView::ShowPreview)); + const auto name = checkboxes.name; + const auto preview = checkboxes.preview; + const auto previewWrap = checkboxes.wrap; + + const auto previewDivider = container->add( + object_ptr>( + container, + object_ptr(container))); + previewWrap->toggle(settings.desktopNotify(), anim::type::instant); + previewDivider->toggle(!settings.desktopNotify(), anim::type::instant); + + const auto changed = [=](ChangeType change) { + Core::App().saveSettingsDelayed(); + Core::App().notifications().notifySettingsChanged(change); + }; + + name->checkedChanges( + ) | rpl::map([=](bool checked) { + if (!checked) { + preview->setChecked(false); + return NotifyView::ShowNothing; + } else if (!preview->checked()) { + return NotifyView::ShowName; + } + return NotifyView::ShowPreview; + }) | rpl::filter([=](NotifyView value) { + return (value != Core::App().settings().notifyView()); + }) | rpl::on_next([=](NotifyView value) { + Core::App().settings().setNotifyView(value); + changed(ChangeType::ViewParams); + }, name->lifetime()); + + preview->checkedChanges( + ) | rpl::map([=](bool checked) { + if (checked) { + name->setChecked(true); + return NotifyView::ShowPreview; + } else if (name->checked()) { + return NotifyView::ShowName; + } + return NotifyView::ShowNothing; + }) | rpl::filter([=](NotifyView value) { + return (value != Core::App().settings().notifyView()); + }) | rpl::on_next([=](NotifyView value) { + Core::App().settings().setNotifyView(value); + changed(ChangeType::ViewParams); + }, preview->lifetime()); + + Core::App().notifications().settingsChanged( + ) | rpl::on_next([=](ChangeType change) { + if (change == ChangeType::DesktopEnabled) { + previewWrap->toggle( + Core::App().settings().desktopNotify(), + anim::type::normal); + previewDivider->toggle( + !Core::App().settings().desktopNotify(), + anim::type::normal); + } + }, lifetime); +} + +void BuildNotifyTypeSection( + SectionBuilder &builder, + Window::SessionController *controller) { + const auto container = builder.container(); + const auto showOther = builder.showOther(); + + builder.addSkip(st::notifyPreviewBottomSkip); + builder.addSubsectionTitle(tr::lng_settings_notify_title()); + + if (container && controller && showOther) { + controller->session().data().notifySettings().loadExceptions(); + AddTypeButton(container, controller, Data::DefaultNotify::User, showOther); + AddTypeButton(container, controller, Data::DefaultNotify::Group, showOther); + AddTypeButton(container, controller, Data::DefaultNotify::Broadcast, showOther); + } else { + builder.addSettingsButton({ + .id = u"notifications/private"_q, + .title = tr::lng_notification_private_chats(), + .icon = { &st::menuIconProfile }, + .keywords = { u"private"_q, u"chats"_q, u"direct"_q }, + }); + builder.addSettingsButton({ + .id = u"notifications/groups"_q, + .title = tr::lng_notification_groups(), + .icon = { &st::menuIconGroups }, + .keywords = { u"groups"_q, u"chats"_q }, + }); + builder.addSettingsButton({ + .id = u"notifications/channels"_q, + .title = tr::lng_notification_channels(), + .icon = { &st::menuIconChannel }, + .keywords = { u"channels"_q, u"broadcast"_q }, + }); + } +} + +void BuildEventNotificationsSection( + SectionBuilder &builder, + Window::SessionController *controller) { + builder.addSkip(st::settingsCheckboxesSkip); + builder.addDivider(); + builder.addSkip(st::settingsCheckboxesSkip); + builder.addSubsectionTitle(tr::lng_settings_events_title()); + + if (!controller) { + builder.addToggle({ + .id = u"notifications/events/joined"_q, + .title = tr::lng_settings_events_joined(), + .icon = { &st::menuIconInvite }, + .toggled = rpl::single(false), + .keywords = { u"joined"_q, u"contacts"_q, u"signup"_q }, + }); + builder.addToggle({ + .id = u"notifications/events/pinned"_q, + .title = tr::lng_settings_events_pinned(), + .icon = { &st::menuIconPin }, + .toggled = rpl::single(Core::App().settings().notifyAboutPinned()), + .keywords = { u"pinned"_q, u"message"_q }, + }); + return; + } + + const auto session = &controller->session(); + const auto &settings = Core::App().settings(); + + auto joinSilent = rpl::single( + session->api().contactSignupSilentCurrent().value_or(false) + ) | rpl::then(session->api().contactSignupSilent()); + + const auto joined = builder.addToggle({ + .id = u"notifications/events/joined"_q, + .title = tr::lng_settings_events_joined(), + .icon = { &st::menuIconInvite }, + .toggled = std::move(joinSilent) | rpl::map([](bool s) { return !s; }), + .keywords = { u"joined"_q, u"contacts"_q, u"signup"_q }, + }); + if (joined) { + joined->toggledChanges( + ) | rpl::filter([=](bool enabled) { + const auto silent = session->api().contactSignupSilentCurrent(); + return (enabled == silent.value_or(false)); + }) | rpl::on_next([=](bool enabled) { + session->api().saveContactSignupSilent(!enabled); + }, joined->lifetime()); + } + + const auto pinned = builder.addToggle({ + .id = u"notifications/events/pinned"_q, + .title = tr::lng_settings_events_pinned(), + .icon = { &st::menuIconPin }, + .toggled = rpl::single( + settings.notifyAboutPinned() + ) | rpl::then(settings.notifyAboutPinnedChanges()), + .keywords = { u"pinned"_q, u"message"_q }, + }); + if (pinned) { + pinned->toggledChanges( + ) | rpl::filter([=](bool notify) { + return (notify != Core::App().settings().notifyAboutPinned()); + }) | rpl::on_next([=](bool notify) { + Core::App().settings().setNotifyAboutPinned(notify); + Core::App().saveSettingsDelayed(); + }, pinned->lifetime()); + } +} + +void BuildCallNotificationsSection( + SectionBuilder &builder, + Window::SessionController *controller) { + builder.addSkip(st::settingsCheckboxesSkip); + builder.addDivider(); + builder.addSkip(st::settingsCheckboxesSkip); + builder.addSubsectionTitle(tr::lng_settings_notifications_calls_title()); + + if (!controller) { + builder.addToggle({ + .id = u"notifications/calls/accept"_q, + .title = tr::lng_settings_call_accept_calls(), + .icon = { &st::menuIconCallsReceive }, + .toggled = rpl::single(true), + .keywords = { u"calls"_q, u"receive"_q, u"incoming"_q }, + }); + return; + } + + const auto session = &controller->session(); + const auto authorizations = &session->api().authorizations(); + authorizations->reload(); + + const auto acceptCalls = builder.addToggle({ + .id = u"notifications/calls/accept"_q, + .title = tr::lng_settings_call_accept_calls(), + .icon = { &st::menuIconCallsReceive }, + .toggled = authorizations->callsDisabledHereValue() + | rpl::map([](bool disabled) { return !disabled; }), + .keywords = { u"calls"_q, u"receive"_q, u"incoming"_q }, + }); + if (acceptCalls) { + const auto container = builder.container(); + acceptCalls->toggledChanges( + ) | rpl::filter([=](bool toggled) { + return (toggled == authorizations->callsDisabledHere()); + }) | rpl::on_next([=](bool toggled) { + authorizations->toggleCallsDisabledHere(!toggled); + }, container->lifetime()); + } +} + +void BuildBadgeCounterSection( + SectionBuilder &builder, + Window::SessionController *controller) { + builder.addSkip(st::settingsCheckboxesSkip); + builder.addDivider(); + builder.addSkip(st::settingsCheckboxesSkip); + builder.addSubsectionTitle(tr::lng_settings_badge_title()); + + const auto &settings = Core::App().settings(); + + const auto muted = builder.addToggle({ + .id = u"notifications/badge/muted"_q, + .title = tr::lng_settings_include_muted(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(settings.includeMutedCounter()), + .keywords = { u"muted"_q, u"badge"_q, u"counter"_q }, + }); + + const auto hasFolders = controller + && controller->session().data().chatsFilters().has(); + const auto mutedFolders = hasFolders ? builder.addToggle({ + .id = u"notifications/badge/muted_folders"_q, + .title = tr::lng_settings_include_muted_folders(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(settings.includeMutedCounterFolders()), + .keywords = { u"muted"_q, u"folders"_q }, + }) : nullptr; + + const auto count = builder.addToggle({ + .id = u"notifications/badge/count"_q, + .title = tr::lng_settings_count_unread(), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(settings.countUnreadMessages()), + .keywords = { u"unread"_q, u"messages"_q, u"count"_q }, + }); + + const auto changed = [=](ChangeType change) { + Core::App().saveSettingsDelayed(); + Core::App().notifications().notifySettingsChanged(change); + }; + + if (muted) { + muted->toggledChanges( + ) | rpl::filter([=](bool checked) { + return (checked != Core::App().settings().includeMutedCounter()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setIncludeMutedCounter(checked); + changed(ChangeType::IncludeMuted); + }, muted->lifetime()); + } + + if (mutedFolders) { + mutedFolders->toggledChanges( + ) | rpl::filter([=](bool checked) { + return (checked + != Core::App().settings().includeMutedCounterFolders()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setIncludeMutedCounterFolders(checked); + changed(ChangeType::IncludeMuted); + }, mutedFolders->lifetime()); + } + + if (count) { + count->toggledChanges( + ) | rpl::filter([=](bool checked) { + return (checked != Core::App().settings().countUnreadMessages()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setCountUnreadMessages(checked); + changed(ChangeType::CountMessages); + }, count->lifetime()); + } +} + +void BuildSystemIntegrationAndAdvancedSection( + SectionBuilder &builder, + Window::SessionController *controller) { + const auto container = builder.container(); + + auto nativeText = [&]() -> rpl::producer { + if (!Platform::Notifications::Supported() + || Core::App().notifications().nativeEnforced()) { + return rpl::producer(); + } else if (Platform::IsWindows()) { + return tr::lng_settings_use_windows(); + } + return tr::lng_settings_use_native_notifications(); + }(); + + if (nativeText) { + builder.addSkip(st::settingsCheckboxesSkip); + builder.addDivider(); + builder.addSkip(st::settingsCheckboxesSkip); + builder.addSubsectionTitle(tr::lng_settings_native_title()); + } + + const auto &settings = Core::App().settings(); + const auto native = nativeText ? builder.addToggle({ + .id = u"notifications/native"_q, + .title = std::move(nativeText), + .st = &st::settingsButtonNoIcon, + .toggled = rpl::single(settings.nativeNotifications()), + .keywords = { u"native"_q, u"system"_q, u"windows"_q }, + }) : nullptr; + + if (Core::App().notifications().nativeEnforced()) { + return; + } + if (!container || !controller) { + return; + } + + const auto advancedSlide = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto advancedWrap = advancedSlide->entity(); + + if (native) { + native->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().nativeNotifications()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setNativeNotifications(checked); + Core::App().saveSettingsDelayed(); + Core::App().notifications().createManager(); + advancedSlide->toggle(!checked, anim::type::normal); + }, native->lifetime()); + } + + if (Platform::IsWindows()) { + const auto skipInFocus = advancedWrap->add(object_ptr( + advancedWrap, + tr::lng_settings_skip_in_focus(), + st::settingsButtonNoIcon + ))->toggleOn(rpl::single(Core::App().settings().skipToastsInFocus())); + + skipInFocus->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().skipToastsInFocus()); + }) | rpl::on_next([=](bool checked) { + Core::App().settings().setSkipToastsInFocus(checked); + Core::App().saveSettingsDelayed(); + if (checked && Platform::Notifications::SkipToastForCustom()) { + Core::App().notifications().notifySettingsChanged( + ChangeType::DesktopEnabled); + } + }, skipInFocus->lifetime()); + } + + const auto screens = QGuiApplication::screens(); + if (screens.size() > 1) { + Ui::AddSkip(advancedWrap, st::settingsCheckboxesSkip); + Ui::AddDivider(advancedWrap); + Ui::AddSkip(advancedWrap, st::settingsCheckboxesSkip); + Ui::AddSubsectionTitle( + advancedWrap, + tr::lng_settings_notifications_display()); + + const auto currentChecksum + = Core::App().settings().notificationsDisplayChecksum(); + auto currentIndex = (currentChecksum == 0) + ? kDefaultDisplayIndex + : 0; + for (auto i = 0; i < screens.size(); ++i) { + if (Platform::ScreenNameChecksum(screens[i]) == currentChecksum) { + currentIndex = i; + break; + } + } + + const auto group = std::make_shared( + currentIndex); + + advancedWrap->add( + object_ptr( + advancedWrap, + group, + kDefaultDisplayIndex, + tr::lng_settings_notifications_display_default(tr::now), + st::settingsSendType), + st::settingsSendTypePadding); + + for (auto i = 0; i < screens.size(); ++i) { + const auto &screen = screens[i]; + const auto name = Platform::ScreenDisplayLabel(screen); + const auto geometry = screen->geometry(); + const auto resolution = QString::number(geometry.width()) + + QChar(0x00D7) + + QString::number(geometry.height()); + const auto label = name.isEmpty() + ? QString("Display (%1)").arg(resolution) + : QString("%1 (%2)").arg(name).arg(resolution); + advancedWrap->add( + object_ptr( + advancedWrap, + group, + i, + label, + st::settingsSendType), + st::settingsSendTypePadding); + } + group->setChangedCallback([=](int selectedIndex) { + if (selectedIndex == kDefaultDisplayIndex) { + Core::App().settings().setNotificationsDisplayChecksum(0); + Core::App().saveSettings(); + Core::App().notifications().notifySettingsChanged( + ChangeType::Corner); + } else { + const auto screens = QGuiApplication::screens(); + if (selectedIndex >= 0 && selectedIndex < screens.size()) { + const auto checksum = Platform::ScreenNameChecksum( + screens[selectedIndex]); + Core::App().settings().setNotificationsDisplayChecksum( + checksum); + Core::App().saveSettings(); + Core::App().notifications().notifySettingsChanged( + ChangeType::Corner); + } + } + }); + } + + Ui::AddSkip(advancedWrap, st::settingsCheckboxesSkip); + Ui::AddDivider(advancedWrap); + Ui::AddSkip(advancedWrap, st::settingsCheckboxesSkip); + Ui::AddSubsectionTitle( + advancedWrap, + tr::lng_settings_notifications_position()); + Ui::AddSkip(advancedWrap, st::settingsCheckboxesSkip); + + const auto position = advancedWrap->add( + object_ptr(advancedWrap, controller)); + + Ui::AddSkip(advancedWrap, st::settingsCheckboxesSkip); + Ui::AddSubsectionTitle(advancedWrap, tr::lng_settings_notifications_count()); + + const auto countSlider = advancedWrap->add( + object_ptr(advancedWrap, st::settingsSlider), + st::settingsBigScalePadding); + for (int i = 0; i != kMaxNotificationsCount; ++i) { + countSlider->addSection(QString::number(i + 1)); + } + countSlider->setActiveSectionFast(CurrentNotificationsCount() - 1); + countSlider->sectionActivated( + ) | rpl::on_next([=](int section) { + position->setCount(section + 1); + }, countSlider->lifetime()); + Ui::AddSkip(advancedWrap, st::settingsCheckboxesSkip); + + if (Core::App().settings().nativeNotifications()) { + advancedSlide->hide(anim::type::instant); + } + + Core::App().notifications().settingsChanged( + ) | rpl::on_next([=](ChangeType change) { + if (change == ChangeType::DesktopEnabled) { + const auto native = Core::App().settings().nativeNotifications(); + advancedSlide->toggle(!native, anim::type::normal); + } + }, advancedSlide->lifetime()); +} + +void BuildNotificationsSectionContent( + SectionBuilder &builder, + Window::SessionController *controller) { + const auto container = builder.container(); + + builder.addSkip(st::settingsPrivacySkip); + + BuildMultiAccountSection(builder, controller); + BuildGlobalNotificationsSection(builder, controller); + + if (container && controller) { + BuildNotifyViewSection(builder, controller, container->lifetime()); + } + + BuildNotifyTypeSection(builder, controller); + BuildEventNotificationsSection(builder, controller); + BuildCallNotificationsSection(builder, controller); + BuildBadgeCounterSection(builder, controller); + BuildSystemIntegrationAndAdvancedSection(builder, controller); +} + +} // namespace + +void BuildNotificationsSection( + not_null container, + not_null controller, + Fn showOther) { + const auto isPaused = Window::PausedIn( + controller, + Window::GifPauseReason::Layer); + auto builder = SectionBuilder(WidgetContext{ + .container = container, + .controller = controller, + .showOther = std::move(showOther), + .isPaused = isPaused, + }); + BuildNotificationsSectionContent(builder, controller); +} + +std::vector BuildNotificationsSectionForSearch() { + auto entries = std::vector(); + auto builder = SectionBuilder(SearchContext{ + .entries = &entries, + }); + BuildNotificationsSectionContent(builder, nullptr); + return entries; +} + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/builder/settings_notifications_builder.h b/Telegram/SourceFiles/settings/builder/settings_notifications_builder.h new file mode 100644 index 0000000000..a6238a1441 --- /dev/null +++ b/Telegram/SourceFiles/settings/builder/settings_notifications_builder.h @@ -0,0 +1,29 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "settings/builder/settings_builder.h" + +namespace Ui { +class VerticalLayout; +} // namespace Ui + +namespace Window { +class SessionController; +} // namespace Window + +namespace Settings::Builder { + +void BuildNotificationsSection( + not_null container, + not_null controller, + Fn showOther); + +[[nodiscard]] std::vector BuildNotificationsSectionForSearch(); + +} // namespace Settings::Builder diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index 109dc62c33..cd595b235c 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -7,124 +7,50 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "settings/settings_notifications.h" -#include "ui/widgets/continuous_sliders.h" +#include "settings/builder/settings_notifications_builder.h" +#include "settings/settings_notifications_common.h" #include "settings/settings_notifications_type.h" #include "ui/boxes/confirm_box.h" +#include "ui/chat/chat_theme.h" #include "ui/controls/chat_service_checkbox.h" #include "ui/effects/animations.h" -#include "data/notify/data_peer_notify_volume.h" -#include "ui/chat/chat_theme.h" -#include "ui/text/text_utilities.h" -#include "ui/wrap/vertical_layout.h" -#include "ui/wrap/slide_wrap.h" -#include "ui/widgets/box_content_divider.h" -#include "ui/widgets/checkbox.h" -#include "ui/widgets/buttons.h" -#include "ui/widgets/discrete_sliders.h" #include "ui/painter.h" -#include "ui/vertical_list.h" #include "ui/ui_utility.h" +#include "ui/widgets/buttons.h" +#include "ui/widgets/checkbox.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "core/application.h" +#include "data/data_session.h" +#include "data/notify/data_notify_settings.h" #include "lang/lang_keys.h" +#include "main/main_session.h" +#include "mainwindow.h" #include "window/notifications_manager.h" #include "window/section_widget.h" #include "window/themes/window_theme.h" #include "window/window_session_controller.h" -#include "platform/platform_specific.h" -#include "platform/platform_notifications_manager.h" -#include "base/platform/base_platform_info.h" -#include "mainwindow.h" -#include "core/application.h" -#include "main/main_session.h" -#include "main/main_account.h" -#include "main/main_domain.h" -#include "api/api_authorizations.h" -#include "api/api_ringtones.h" -#include "data/data_chat_filters.h" -#include "data/data_session.h" -#include "data/data_document.h" -#include "data/notify/data_notify_settings.h" -#include "boxes/ringtones_box.h" -#include "apiwrap.h" -#include "styles/style_settings.h" #include "styles/style_boxes.h" +#include "styles/style_chat.h" +#include "styles/style_dialogs.h" #include "styles/style_layers.h" #include "styles/style_menu_icons.h" -#include "styles/style_chat.h" +#include "styles/style_settings.h" #include "styles/style_window.h" -#include "styles/style_dialogs.h" #include -#include -#include namespace Settings { -namespace { -constexpr auto kMaxNotificationsCount = 5; -constexpr auto kDefaultDisplayIndex = -1; +using ChangeType = Window::Notifications::ChangeType; -[[nodiscard]] int CurrentCount() { +int CurrentNotificationsCount() { return std::clamp( Core::App().settings().notificationsCount(), 1, kMaxNotificationsCount); } -using ChangeType = Window::Notifications::ChangeType; - -class NotificationsCount : public Ui::RpWidget { -public: - NotificationsCount( - QWidget *parent, - not_null controller); - - void setCount(int count); - - ~NotificationsCount(); - -protected: - void paintEvent(QPaintEvent *e) override; - void mousePressEvent(QMouseEvent *e) override; - void mouseMoveEvent(QMouseEvent *e) override; - void leaveEventHook(QEvent *e) override; - void mouseReleaseEvent(QMouseEvent *e) override; - - int resizeGetHeight(int newWidth) override; - -private: - using ScreenCorner = Core::Settings::ScreenCorner; - void setOverCorner(ScreenCorner corner); - void clearOverCorner(); - - class SampleWidget; - void removeSample(SampleWidget *widget); - - QRect getScreenRect() const; - QRect getScreenRect(int width) const; - int getContentLeft() const; - void prepareNotificationSampleSmall(); - void prepareNotificationSampleLarge(); - void prepareNotificationSampleUserpic(); - - const not_null _controller; - - QPixmap _notificationSampleUserpic; - QPixmap _notificationSampleSmall; - QPixmap _notificationSampleLarge; - ScreenCorner _chosenCorner; - std::vector _sampleOpacities; - - bool _isOverCorner = false; - ScreenCorner _overCorner = ScreenCorner::TopLeft; - bool _isDownCorner = false; - ScreenCorner _downCorner = ScreenCorner::TopLeft; - - int _oldCount; - - std::vector _cornerSamples[4]; - -}; - class NotificationsCount::SampleWidget : public QWidget { public: SampleWidget(NotificationsCount *owner, const QPixmap &cache); @@ -306,7 +232,7 @@ NotificationsCount::NotificationsCount( not_null controller) : _controller(controller) , _chosenCorner(Core::App().settings().notificationsCorner()) -, _oldCount(CurrentCount()) { +, _oldCount(CurrentNotificationsCount()) { setMouseTracking(true); _sampleOpacities.resize(kMaxNotificationsCount); @@ -780,12 +706,6 @@ void NotifyPreview::paint(Painter &p, int x, int y) { 2); } -struct NotifyViewCheckboxes { - not_null*> wrap; - not_null name; - not_null preview; -}; - NotifyViewCheckboxes SetupNotifyViewOptions( not_null controller, not_null container, @@ -873,539 +793,6 @@ NotifyViewCheckboxes SetupNotifyViewOptions( }; } -void SetupAdvancedNotifications( - not_null controller, - not_null container) { - if (Platform::IsWindows()) { - const auto skipInFocus = container->add(object_ptr