Start default notification settings.

This commit is contained in:
John Preston 2023-08-22 19:13:21 +02:00
parent e7be8e1c60
commit 610e0e7913
8 changed files with 475 additions and 65 deletions

View File

@ -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

View File

@ -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";

View File

@ -319,16 +319,15 @@ not_null<Ui::RpWidget*> 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());

View File

@ -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;

View File

@ -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<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Data::DefaultNotify type,
Fn<void(Type)> 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<Ui::FlatLabel>(
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<Ui::SettingsButton>(
container.get(),
nullptr,
st);
const auto checkView = toggleButton->lifetime().make_state<Ui::ToggleView>(
st.toggle,
NotificationsEnabledForType(session, type),
[=] { toggleButton->update(); });
const auto separator = Ui::CreateChild<Ui::RpWidget>(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<Ui::RpWidget>(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<Window::SessionController*> controller)
@ -788,15 +889,16 @@ void SetupMultiAccountNotifications(
void SetupNotificationsContent(
not_null<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
not_null<Ui::VerticalLayout*> container,
Fn<void(Type)> 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<rpl::event_stream<QString>>();
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<rpl::event_stream<bool>>();
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<Window::SessionController*> controller,
not_null<Ui::VerticalLayout*> container) {
SetupNotificationsContent(controller, container);
not_null<Ui::VerticalLayout*> container,
Fn<void(Type)> showOther) {
SetupNotificationsContent(controller, container, std::move(showOther));
}
} // namespace
@ -1148,11 +1225,17 @@ rpl::producer<QString> Notifications::title() {
return tr::lng_settings_section_notify();
}
rpl::producer<Type> Notifications::sectionShowOther() {
return _showOther.events();
}
void Notifications::setupContent(
not_null<Window::SessionController*> controller) {
const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
SetupNotifications(controller, content);
SetupNotifications(controller, content, [=](Type type) {
_showOther.fire_copy(type);
});
Ui::ResizeFitChild(this, content);
}

View File

@ -19,9 +19,13 @@ public:
[[nodiscard]] rpl::producer<QString> title() override;
rpl::producer<Type> sectionShowOther() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
rpl::event_stream<Type> _showOther;
};
} // namespace Settings

View File

@ -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 <Notify kType>
[[nodiscard]] Type Id() {
return &NotificationsTypeMetaImplementation<kType>::Meta;
}
[[nodiscard]] rpl::producer<QString> 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<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> 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<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(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<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
toneWrap->toggleOn(sound->toggledValue());
toneWrap->finishAnimating();
const auto toneInner = toneWrap->entity();
const auto toneLabel = toneInner->lifetime(
).make_state<rpl::event_stream<QString>>();
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<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Notify type) {
}
} // namespace
Type NotificationsTypeId(Notify type) {
switch (type) {
case Notify::User: return Id<Notify::User>();
case Notify::Group: return Id<Notify::Group>();
case Notify::Broadcast: return Id<Notify::Broadcast>();
}
Unexpected("Type in NotificationTypeId.");
}
NotificationsType::NotificationsType(
QWidget *parent,
not_null<Window::SessionController*> controller,
Notify type)
: AbstractSection(parent)
, _type(type) {
setupContent(controller);
}
rpl::producer<QString> 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<Window::SessionController*> controller) {
const auto container = Ui::CreateChild<Ui::VerticalLayout>(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<Main::Session*> session,
Notify type) {
const auto settings = &session->data().notifySettings();
const auto until = settings->defaultSettings(type).muteUntil();
return until && (*until <= base::unixtime::now());
}
rpl::producer<bool> NotificationsEnabledForTypeValue(
not_null<Main::Session*> 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

View File

@ -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 <Data::DefaultNotify kType>
struct NotificationsTypeMetaImplementation : SectionMeta {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller
) const final override {
return object_ptr<NotificationsType>(parent, controller, kType);
}
[[nodiscard]] static not_null<SectionMeta*> Meta() {
static NotificationsTypeMetaImplementation result;
return &result;
}
};
[[nodiscard]] Type NotificationsTypeId(Data::DefaultNotify type);
class NotificationsType : public AbstractSection {
public:
NotificationsType(
QWidget *parent,
not_null<Window::SessionController*> controller,
Data::DefaultNotify type);
[[nodiscard]] rpl::producer<QString> title() override;
[[nodiscard]] Type id() const final override;
private:
void setupContent(not_null<Window::SessionController*> controller);
Data::DefaultNotify _type;
};
[[nodiscard]] bool NotificationsEnabledForType(
not_null<Main::Session*> session,
Data::DefaultNotify type);
[[nodiscard]] rpl::producer<bool> NotificationsEnabledForTypeValue(
not_null<Main::Session*> session,
Data::DefaultNotify type);
} // namespace Settings