diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index f15f85198..8de1d1e8b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1261,6 +1261,8 @@ PRIVATE settings/settings_main.h settings/settings_notifications.cpp settings/settings_notifications.h + settings/settings_notifications_type.cpp + settings/settings_notifications_type.h settings/settings_power_saving.cpp settings/settings_power_saving.h settings/settings_premium.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 31f384f0a..c01e2f6d8 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -451,6 +451,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_show_from" = "Show notifications from"; "lng_settings_notify_all" = "All accounts"; "lng_settings_notify_all_about" = "Turn this off if you want to receive notifications only from the account you are currently using."; +"lng_settings_notify_global" = "Global settings"; "lng_settings_notify_title" = "Notifications for chats"; "lng_settings_desktop_notify" = "Desktop notifications"; "lng_settings_native_title" = "Native notifications"; @@ -458,8 +459,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_settings_use_native_notifications" = "Use native notifications"; "lng_settings_notifications_position" = "Location on the screen"; "lng_settings_notifications_count" = "Notifications count"; -"lng_settings_sound_notify" = "Play sound"; -"lng_settings_sound_notify_off" = "Off"; +"lng_settings_sound_allowed" = "Allow sound"; "lng_settings_alert_windows" = "Flash the taskbar icon"; "lng_settings_alert_mac" = "Bounce the dock icon"; "lng_settings_alert_linux" = "Draw attention to the window"; @@ -480,6 +480,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_notification_hide_all" = "Hide all"; "lng_notification_sample" = "This is a sample notification"; "lng_notification_reminder" = "Reminder"; +"lng_notification_private_chats" = "Private chats"; +"lng_notification_groups" = "Groups"; +"lng_notification_channels" = "Channels"; +"lng_notification_click_to_change" = "Click here to change"; +"lng_notification_on" = "On, {exceptions}"; +"lng_notification_off" = "Off, {exceptions}"; +"lng_notification_exceptions#one" = "{count} exception"; +"lng_notification_exceptions#other" = "{count} exceptions"; +"lng_notification_exceptions_title" = "Exceptions"; +"lng_notification_title_private_chats" = "Notifications for private chats"; +"lng_notification_about_private_chats#one" = "Please note that **{count} chat** is listed as an exception and won't be affected by this change."; +"lng_notification_about_private_chats#other" = "Please note that **{count} chats** are listed as exceptions and won't be affected by this change."; +"lng_notification_title_groups" = "Notifications for groups"; +"lng_notification_about_groups#one" = "Please note that **{count} group** is listed as an exception and won't be affected by this change."; +"lng_notification_about_groups#other" = "Please note that **{count} groups** are listed as exceptions and won't be affected by this change."; +"lng_notification_title_channels" = "Notifications for channels"; +"lng_notification_about_channels#one" = "Please note that **{count} channel** is listed as an exception and won't be affected by this change."; +"lng_notification_about_channels#other" = "Please note that **{count} channels** are listed as exceptions and won't be affected by this change."; +"lng_notification_enable" = "Enable notifications"; +"lng_notification_sound" = "Sound"; +"lng_notification_tone" = "Notification tone"; +"lng_notification_exceptions_muted" = "Muted"; +"lng_notification_exceptions_unmuted" = "Unmuted"; +"lng_notification_exceptions_add" = "Add an exception"; +"lng_notification_exceptions_clear" = "Delete all exceptions"; "lng_reaction_text" = "{reaction} to your «{text}»"; "lng_reaction_notext" = "{reaction} to your message"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp index 4c33b38fc..97a86be3b 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_permissions_box.cpp @@ -319,16 +319,15 @@ not_null AddInnerToggle( button->geometryValue( ) | rpl::start_with_next([=](const QRect &r) { const auto w = st::rightsButtonToggleWidth; - constexpr auto kLineWidth = int(1); toggleButton->setGeometry( r.x() + r.width() - w, r.y(), w, r.height()); separator->setGeometry( - toggleButton->x() - kLineWidth, + toggleButton->x() - st::lineWidth, r.y() + (r.height() - separatorHeight) / 2, - kLineWidth, + st::lineWidth, separatorHeight); }, toggleButton->lifetime()); diff --git a/Telegram/SourceFiles/settings/settings.style b/Telegram/SourceFiles/settings/settings.style index 4f53c9a33..15bf57077 100644 --- a/Telegram/SourceFiles/settings/settings.style +++ b/Telegram/SourceFiles/settings/settings.style @@ -528,6 +528,14 @@ settingsBlockedList: PeerList(peerListBox) { padding: margins(0px, 0px, 0px, membersMarginBottom); } +settingsNotificationType: SettingsButton(settingsButton) { + height: 40px; + padding: margins(60px, 4px, 22px, 4px); +} +settingsNotificationTypeDetails: FlatLabel(defaultFlatLabel) { + textFg: windowSubTextFg; +} + requestPeerRestriction: FlatLabel(defaultFlatLabel) { minWidth: 240px; textFg: membersAboutLimitFg; diff --git a/Telegram/SourceFiles/settings/settings_notifications.cpp b/Telegram/SourceFiles/settings/settings_notifications.cpp index 87abcbc01..cbe3f6c8a 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.cpp +++ b/Telegram/SourceFiles/settings/settings_notifications.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "settings/settings_notifications.h" #include "settings/settings_common.h" +#include "settings/settings_notifications_type.h" #include "ui/controls/chat_service_checkbox.h" #include "ui/effects/animations.h" #include "ui/wrap/vertical_layout.h" @@ -140,6 +141,106 @@ private: }; +void AddTypeButton( + not_null container, + not_null controller, + Data::DefaultNotify type, + Fn showOther) { + using Type = Data::DefaultNotify; + auto label = [&] { + switch (type) { + case Type::User: return tr::lng_notification_private_chats(); + case Type::Group: return tr::lng_notification_groups(); + case Type::Broadcast: return tr::lng_notification_channels(); + } + Unexpected("Type value in AddTypeButton."); + }(); + const auto icon = [&] { + switch (type) { + case Type::User: return &st::menuIconProfile; + case Type::Group: return &st::menuIconGroups; + case Type::Broadcast: return &st::menuIconChannel; + } + Unexpected("Type value in AddTypeButton."); + }(); + const auto button = AddButton( + container, + std::move(label), + st::settingsNotificationType, + { icon }); + button->setClickedCallback([=] { + showOther(NotificationsTypeId(type)); + }); + + const auto &st = st::settingsNotificationType; + const auto details = Ui::CreateChild( + button.get(), + tr::lng_notification_click_to_change(), + st::settingsNotificationTypeDetails); + details->show(); + details->moveToLeft( + st.padding.left(), + st.padding.top() + st.height - details->height()); + details->setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto session = &controller->session(); + const auto toggleButton = Ui::CreateChild( + container.get(), + nullptr, + st); + const auto checkView = toggleButton->lifetime().make_state( + st.toggle, + NotificationsEnabledForType(session, type), + [=] { toggleButton->update(); }); + + const auto separator = Ui::CreateChild(container.get()); + separator->paintRequest( + ) | rpl::start_with_next([=, bg = st.textBgOver] { + auto p = QPainter(separator); + p.fillRect(separator->rect(), bg); + }, separator->lifetime()); + const auto separatorHeight = st.height - 2 * st.toggle.border; + button->geometryValue( + ) | rpl::start_with_next([=](const QRect &r) { + const auto w = st::rightsButtonToggleWidth; + toggleButton->setGeometry( + r.x() + r.width() - w, + r.y(), + w, + r.height()); + separator->setGeometry( + toggleButton->x() - st::lineWidth, + r.y() + (r.height() - separatorHeight) / 2, + st::lineWidth, + separatorHeight); + }, toggleButton->lifetime()); + + const auto checkWidget = Ui::CreateChild(toggleButton); + checkWidget->resize(checkView->getSize()); + checkWidget->paintRequest( + ) | rpl::start_with_next([=] { + auto p = QPainter(checkWidget); + checkView->paint(p, 0, 0, checkWidget->width()); + }, checkWidget->lifetime()); + toggleButton->sizeValue( + ) | rpl::start_with_next([=](const QSize &s) { + checkWidget->moveToRight( + st.toggleSkip, + (s.height() - checkWidget->height()) / 2); + }, toggleButton->lifetime()); + + toggleButton->clicks( + ) | rpl::start_with_next([=] { + const auto enabled = !checkView->checked(); + const auto settings = &session->data().notifySettings(); + checkView->setChecked(enabled, anim::type::normal); + settings->defaultUpdate(type, Data::MuteValue{ + .unmute = enabled, + .forever = !enabled, + }); + }, toggleButton->lifetime()); +} + NotificationsCount::NotificationsCount( QWidget *parent, not_null controller) @@ -788,15 +889,16 @@ void SetupMultiAccountNotifications( void SetupNotificationsContent( not_null controller, - not_null container) { + not_null container, + Fn showOther) { using namespace rpl::mappers; - AddSkip(container); + AddSkip(container, st::settingsPrivacySkip); using NotifyView = Core::Settings::NotifyView; SetupMultiAccountNotifications(controller, container); - AddSubsectionTitle(container, tr::lng_settings_notify_title()); + AddSubsectionTitle(container, tr::lng_settings_notify_global()); const auto session = &controller->session(); const auto checkbox = [&]( @@ -842,41 +944,15 @@ void SetupNotificationsContent( flashbounceToggles->events_starting_with( settings.flashBounceNotify())); - const auto soundLabel = container->lifetime( - ).make_state>(); - const auto soundValue = [=] { - const auto owner = &controller->session().data(); - const auto &settings = owner->notifySettings().defaultSettings( - Data::DefaultNotify::User); - return !Core::App().settings().soundNotify() - ? Data::NotifySound{ .none = true } - : settings.sound().value_or(Data::NotifySound()); + const auto soundAllowed = container->lifetime( + ).make_state>(); + const auto allowed = [=] { + return Core::App().settings().soundNotify(); }; - const auto label = [=] { - const auto now = soundValue(); - const auto owner = &controller->session().data(); - return now.none - ? tr::lng_settings_sound_notify_off(tr::now) - : !now.id - ? tr::lng_ringtones_box_default(tr::now) - : ExtractRingtoneName(owner->document(now.id)); - }; - controller->session().data().notifySettings().defaultUpdates( - Data::DefaultNotify::User - ) | rpl::start_with_next([=] { - soundLabel->fire(label()); - }, container->lifetime()); - controller->session().api().ringtones().listUpdates( - ) | rpl::start_with_next([=] { - soundLabel->fire(label()); - }, container->lifetime()); - - const auto sound = AddButtonWithLabel( - container, - tr::lng_settings_sound_notify(), - soundLabel->events_starting_with(label()), - st::settingsButton, - { &st::menuIconSoundOn }); + const auto sound = addCheckbox( + tr::lng_settings_sound_allowed(), + { &st::menuIconUnmute }, + soundAllowed->events_starting_with(allowed())); AddSkip(container); @@ -896,6 +972,17 @@ void SetupNotificationsContent( previewDivider->toggle(!settings.desktopNotify(), anim::type::instant); AddSkip(container, st::notifyPreviewBottomSkip); + AddSubsectionTitle(container, tr::lng_settings_notify_title()); + const auto addType = [&](Data::DefaultNotify type) { + AddTypeButton(container, controller, type, showOther); + }; + addType(Data::DefaultNotify::User); + addType(Data::DefaultNotify::Group); + addType(Data::DefaultNotify::Broadcast); + + AddSkip(container, st::settingsCheckboxesSkip); + AddDivider(container); + AddSkip(container, st::settingsCheckboxesSkip); AddSubsectionTitle(container, tr::lng_settings_events_title()); auto joinSilent = rpl::single( @@ -1016,6 +1103,14 @@ void SetupNotificationsContent( changed(Change::DesktopEnabled); }, desktop->lifetime()); + sound->toggledChanges( + ) | rpl::filter([](bool checked) { + return (checked != Core::App().settings().soundNotify()); + }) | rpl::start_with_next([=](bool checked) { + Core::App().settings().setSoundNotify(checked); + changed(Change::SoundEnabled); + }, sound->lifetime()); + name->checkedChanges( ) | rpl::map([=](bool checked) { if (!checked) { @@ -1048,25 +1143,6 @@ void SetupNotificationsContent( changed(Change::ViewParams); }, preview->lifetime()); - sound->setClickedCallback([=] { - controller->show(Box(RingtonesBox, session, soundValue(), [=]( - Data::NotifySound sound) { - Core::App().settings().setSoundNotify(!sound.none); - if (!sound.none) { - using Type = Data::DefaultNotify; - const auto owner = &controller->session().data(); - auto &settings = owner->notifySettings(); - const auto updateType = [&](Type type) { - settings.defaultUpdate(type, {}, {}, sound); - }; - updateType(Type::User); - updateType(Type::Group); - updateType(Type::Broadcast); - } - changed(Change::SoundEnabled); - })); - }); - flashbounce->toggledChanges( ) | rpl::filter([](bool checked) { return (checked != Core::App().settings().flashBounceNotify()); @@ -1104,7 +1180,7 @@ void SetupNotificationsContent( } else if (change == Change::ViewParams) { // } else if (change == Change::SoundEnabled) { - soundLabel->fire(label()); + soundAllowed->fire(allowed()); } else if (change == Change::FlashBounceEnabled) { flashbounceToggles->fire( Core::App().settings().flashBounceNotify()); @@ -1131,8 +1207,9 @@ void SetupNotificationsContent( void SetupNotifications( not_null controller, - not_null container) { - SetupNotificationsContent(controller, container); + not_null container, + Fn showOther) { + SetupNotificationsContent(controller, container, std::move(showOther)); } } // namespace @@ -1148,11 +1225,17 @@ rpl::producer Notifications::title() { return tr::lng_settings_section_notify(); } +rpl::producer Notifications::sectionShowOther() { + return _showOther.events(); +} + void Notifications::setupContent( not_null controller) { const auto content = Ui::CreateChild(this); - SetupNotifications(controller, content); + SetupNotifications(controller, content, [=](Type type) { + _showOther.fire_copy(type); + }); Ui::ResizeFitChild(this, content); } diff --git a/Telegram/SourceFiles/settings/settings_notifications.h b/Telegram/SourceFiles/settings/settings_notifications.h index d895e20f2..d75318230 100644 --- a/Telegram/SourceFiles/settings/settings_notifications.h +++ b/Telegram/SourceFiles/settings/settings_notifications.h @@ -19,9 +19,13 @@ public: [[nodiscard]] rpl::producer title() override; + rpl::producer sectionShowOther() override; + private: void setupContent(not_null controller); + rpl::event_stream _showOther; + }; } // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.cpp b/Telegram/SourceFiles/settings/settings_notifications_type.cpp new file mode 100644 index 000000000..29699a398 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_notifications_type.cpp @@ -0,0 +1,225 @@ +/* +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/settings_notifications_type.h" + +#include "api/api_ringtones.h" +#include "apiwrap.h" +#include "base/unixtime.h" +#include "boxes/ringtones_box.h" +#include "data/notify/data_notify_settings.h" +#include "data/data_session.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +namespace Settings { +namespace { + +using Notify = Data::DefaultNotify; + +template +[[nodiscard]] Type Id() { + return &NotificationsTypeMetaImplementation::Meta; +} + +[[nodiscard]] rpl::producer Title(Notify type) { + switch (type) { + case Notify::User: return tr::lng_notification_title_private_chats(); + case Notify::Group: return tr::lng_notification_title_groups(); + case Notify::Broadcast: return tr::lng_notification_title_channels(); + } + Unexpected("Type in Title."); +} + +void SetupChecks( + not_null container, + not_null controller, + Notify type) { + AddSubsectionTitle(container, Title(type)); + + const auto session = &controller->session(); + const auto settings = &session->data().notifySettings(); + + const auto enabled = container->add( + CreateButton( + container, + tr::lng_notification_enable(), + st::settingsButton, + { &st::menuIconNotifications })); + enabled->toggleOn(NotificationsEnabledForTypeValue(session, type)); + + const auto soundWrap = container->add( + object_ptr>( + container, + object_ptr(container))); + soundWrap->toggleOn(enabled->toggledValue()); + soundWrap->finishAnimating(); + + const auto soundInner = soundWrap->entity(); + const auto soundValue = [=] { + const auto sound = settings->defaultSettings(type).sound(); + return !sound || !sound->none; + }; + const auto sound = soundInner->add( + CreateButton( + soundInner, + tr::lng_notification_sound(), + st::settingsButton, + { &st::menuIconUnmute })); + sound->toggleOn(rpl::single( + soundValue() + ) | rpl::then(settings->defaultUpdates( + type + ) | rpl::map([=] { return soundValue(); }))); + + const auto toneWrap = soundInner->add( + object_ptr>( + container, + object_ptr(container))); + toneWrap->toggleOn(sound->toggledValue()); + toneWrap->finishAnimating(); + + const auto toneInner = toneWrap->entity(); + const auto toneLabel = toneInner->lifetime( + ).make_state>(); + const auto toneValue = [=] { + const auto sound = settings->defaultSettings(type).sound(); + return sound.value_or(Data::NotifySound()); + }; + const auto label = [=] { + const auto now = toneValue(); + return !now.id + ? tr::lng_ringtones_box_default(tr::now) + : ExtractRingtoneName(session->data().document(now.id)); + }; + settings->defaultUpdates( + Data::DefaultNotify::User + ) | rpl::start_with_next([=] { + toneLabel->fire(label()); + }, toneInner->lifetime()); + session->api().ringtones().listUpdates( + ) | rpl::start_with_next([=] { + toneLabel->fire(label()); + }, toneInner->lifetime()); + + const auto tone = AddButtonWithLabel( + toneInner, + tr::lng_notification_tone(), + toneLabel->events_starting_with(label()), + st::settingsButton, + { &st::menuIconSoundOn }); + + enabled->toggledValue( + ) | rpl::filter([=](bool value) { + return (value != NotificationsEnabledForType(session, type)); + }) | rpl::start_with_next([=](bool value) { + settings->defaultUpdate(type, Data::MuteValue{ + .unmute = value, + .forever = !value, + }); + }, sound->lifetime()); + + sound->toggledValue( + ) | rpl::filter([=](bool enabled) { + const auto sound = settings->defaultSettings(type).sound(); + return (!sound || !sound->none) != enabled; + }) | rpl::start_with_next([=](bool enabled) { + const auto value = Data::NotifySound{ .none = !enabled }; + settings->defaultUpdate(type, {}, {}, value); + }, sound->lifetime()); + + tone->setClickedCallback([=] { + controller->show(Box(RingtonesBox, session, toneValue(), [=]( + Data::NotifySound sound) { + settings->defaultUpdate(type, {}, {}, sound); + })); + }); +} + +void SetupExceptions( + not_null container, + not_null controller, + Notify type) { +} + +} // namespace + +Type NotificationsTypeId(Notify type) { + switch (type) { + case Notify::User: return Id(); + case Notify::Group: return Id(); + case Notify::Broadcast: return Id(); + } + Unexpected("Type in NotificationTypeId."); +} + +NotificationsType::NotificationsType( + QWidget *parent, + not_null controller, + Notify type) +: AbstractSection(parent) +, _type(type) { + setupContent(controller); +} + +rpl::producer NotificationsType::title() { + switch (_type) { + case Notify::User: return tr::lng_notification_private_chats(); + case Notify::Group: return tr::lng_notification_groups(); + case Notify::Broadcast: return tr::lng_notification_channels(); + } + Unexpected("Type in NotificationsType."); +} + +Type NotificationsType::id() const { + return NotificationsTypeId(_type); +} + +void NotificationsType::setupContent( + not_null controller) { + const auto container = Ui::CreateChild(this); + + AddSkip(container, st::settingsPrivacySkip); + SetupChecks(container, controller, _type); + + AddSkip(container); + AddDivider(container); + AddSkip(container); + + SetupExceptions(container, controller, _type); + + Ui::ResizeFitChild(this, container); +} + +bool NotificationsEnabledForType( + not_null session, + Notify type) { + const auto settings = &session->data().notifySettings(); + const auto until = settings->defaultSettings(type).muteUntil(); + return until && (*until <= base::unixtime::now()); +} + +rpl::producer NotificationsEnabledForTypeValue( + not_null session, + Data::DefaultNotify type) { + const auto settings = &session->data().notifySettings(); + return rpl::single( + rpl::empty + ) | rpl::then( + settings->defaultUpdates(type) + ) | rpl::map([=] { + return NotificationsEnabledForType(session, type); + }); +} + +} // namespace Settings diff --git a/Telegram/SourceFiles/settings/settings_notifications_type.h b/Telegram/SourceFiles/settings/settings_notifications_type.h new file mode 100644 index 000000000..4cb04c7a2 --- /dev/null +++ b/Telegram/SourceFiles/settings/settings_notifications_type.h @@ -0,0 +1,64 @@ +/* +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/settings_common.h" +#include "data/notify/data_notify_settings.h" + +namespace Data { +enum class DefaultNotify; +} // namespace Data + +namespace Settings { + +class NotificationsType; + +template +struct NotificationsTypeMetaImplementation : SectionMeta { + object_ptr create( + not_null parent, + not_null controller + ) const final override { + return object_ptr(parent, controller, kType); + } + + [[nodiscard]] static not_null Meta() { + static NotificationsTypeMetaImplementation result; + return &result; + } +}; + +[[nodiscard]] Type NotificationsTypeId(Data::DefaultNotify type); + +class NotificationsType : public AbstractSection { +public: + NotificationsType( + QWidget *parent, + not_null controller, + Data::DefaultNotify type); + + [[nodiscard]] rpl::producer title() override; + + [[nodiscard]] Type id() const final override; + +private: + void setupContent(not_null controller); + + Data::DefaultNotify _type; + +}; + +[[nodiscard]] bool NotificationsEnabledForType( + not_null session, + Data::DefaultNotify type); + +[[nodiscard]] rpl::producer NotificationsEnabledForTypeValue( + not_null session, + Data::DefaultNotify type); + +} // namespace Settings