Added ability to change notify settings for reactions and poll votes.

This commit is contained in:
23rd
2026-03-29 20:16:12 +03:00
parent 90191819bd
commit b86ed7775a
6 changed files with 497 additions and 61 deletions
+2
View File
@@ -1662,6 +1662,8 @@ PRIVATE
settings/sections/settings_notifications.h
settings/sections/settings_privacy_security.cpp
settings/sections/settings_privacy_security.h
settings/sections/settings_notifications_reactions.cpp
settings/sections/settings_notifications_reactions.h
settings/sections/settings_notifications_type.cpp
settings/sections/settings_notifications_type.h
settings/sections/settings_passkeys.cpp
+13
View File
@@ -565,6 +565,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_notification_private_chats" = "Private chats";
"lng_notification_groups" = "Groups";
"lng_notification_channels" = "Channels";
"lng_notification_reactions" = "Reactions";
"lng_notification_reactions_title" = "Notifications for reactions";
"lng_notification_reactions_notify_about" = "Notify me about";
"lng_notification_reactions_messages" = "Messages";
"lng_notification_reactions_messages_full" = "Reactions to my messages";
"lng_notification_reactions_poll_votes" = "Poll votes";
"lng_notification_reactions_poll_votes_full" = "Votes in my polls";
"lng_notification_reactions_from" = "Notify about reactions from";
"lng_notification_reactions_from_nobody" = "Off";
"lng_notification_reactions_from_contacts" = "From my contacts";
"lng_notification_reactions_from_all" = "From everyone";
"lng_notification_reactions_settings" = "Settings";
"lng_notification_reactions_show_sender" = "Show sender's name";
"lng_notification_click_to_change" = "Click here to change";
"lng_notification_on" = "On, {exceptions}";
"lng_notification_off" = "Off, {exceptions}";
@@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_common_session.h"
#include "api/api_authorizations.h"
#include "api/api_reactions_notify_settings.h"
#include "api/api_ringtones.h"
#include "apiwrap.h"
#include "base/platform/base_platform_info.h"
@@ -29,6 +30,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "settings/settings_builder.h"
#include "settings/sections/settings_main.h"
#include "settings/settings_notifications_common.h"
#include "settings/sections/settings_notifications_reactions.h"
#include "settings/sections/settings_notifications_type.h"
#include "ui/boxes/confirm_box.h"
#include "ui/chat/chat_theme.h"
@@ -92,6 +94,78 @@ private:
};
SplitToggle SetupSplitToggle(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title,
const style::icon *icon,
bool checked,
rpl::producer<QString> details) {
const auto button = AddButtonWithIcon(
container,
std::move(title),
st::settingsNotificationType,
{ icon });
const auto &st = st::settingsNotificationType;
const auto label = Ui::CreateChild<Ui::FlatLabel>(
button.get(),
std::move(details),
st::settingsNotificationTypeDetails);
label->show();
label->moveToLeft(
st.padding.left(),
st.padding.top() + st.height - label->height());
label->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto toggle = Ui::CreateChild<Ui::SettingsButton>(
container.get(),
nullptr,
st);
const auto checkView = button->lifetime().make_state<Ui::ToggleView>(
st.toggle,
checked,
[=] { toggle->update(); });
const auto separator = Ui::CreateChild<Ui::RpWidget>(container.get());
separator->paintRequest(
) | rpl::on_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::on_next([=](const QRect &r) {
const auto w = st::rightsButtonToggleWidth;
toggle->setGeometry(
r.x() + r.width() - w,
r.y(),
w,
r.height());
separator->setGeometry(
toggle->x() - st::lineWidth,
r.y() + (r.height() - separatorHeight) / 2,
st::lineWidth,
separatorHeight);
}, toggle->lifetime());
const auto checkWidget = Ui::CreateChild<Ui::RpWidget>(toggle);
checkWidget->resize(checkView->getSize());
checkWidget->paintRequest(
) | rpl::on_next([=] {
auto p = QPainter(checkWidget);
checkView->paint(p, 0, 0, checkWidget->width());
}, checkWidget->lifetime());
toggle->sizeValue(
) | rpl::on_next([=](const QSize &s) {
checkWidget->moveToRight(
st.toggleSkip,
(s.height() - checkWidget->height()) / 2);
}, toggle->lifetime());
return { button, toggle, checkView };
}
[[nodiscard]] not_null<Ui::SettingsButton*> AddTypeButton(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
@@ -114,18 +188,9 @@ private:
}
Unexpected("Type value in AddTypeButton.");
}();
const auto button = AddButtonWithIcon(
container,
std::move(label),
st::settingsNotificationType,
{ icon });
button->setClickedCallback([=] {
showOther(NotificationsType::Id(type));
});
const auto session = &controller->session();
const auto settings = &session->data().notifySettings();
const auto &st = st::settingsNotificationType;
auto status = rpl::combine(
NotificationsEnabledForTypeValue(session, type),
rpl::single(
@@ -144,60 +209,16 @@ private:
lt_count,
rpl::single(float64(count))));
}) | rpl::flatten_latest();
const auto details = Ui::CreateChild<Ui::FlatLabel>(
button.get(),
std::move(status),
st::settingsNotificationTypeDetails);
details->show();
details->moveToLeft(
st.padding.left(),
st.padding.top() + st.height - details->height());
details->setAttribute(Qt::WA_TransparentForMouseEvents);
const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
container.get(),
nullptr,
st);
const auto checkView = button->lifetime().make_state<Ui::ToggleView>(
st.toggle,
const auto [button, toggleButton, checkView] = SetupSplitToggle(
container,
std::move(label),
icon,
NotificationsEnabledForType(session, type),
[=] { toggleButton->update(); });
const auto separator = Ui::CreateChild<Ui::RpWidget>(container.get());
separator->paintRequest(
) | rpl::on_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::on_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::on_next([=] {
auto p = QPainter(checkWidget);
checkView->paint(p, 0, 0, checkWidget->width());
}, checkWidget->lifetime());
toggleButton->sizeValue(
) | rpl::on_next([=](const QSize &s) {
checkWidget->moveToRight(
st.toggleSkip,
(s.height() - checkWidget->height()) / 2);
}, toggleButton->lifetime());
std::move(status));
button->setClickedCallback([=] {
showOther(NotificationsType::Id(type));
});
const auto toggle = crl::guard(toggleButton, [=] {
const auto enabled = !checkView->checked();
@@ -247,6 +268,57 @@ private:
return button;
}
[[nodiscard]] not_null<Ui::SettingsButton*> AddReactionsButton(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Fn<void(Type)> showOther) {
const auto session = &controller->session();
auto &rs = session->api().reactionsNotifySettings();
rs.reload();
using From = Api::ReactionsNotifyFrom;
auto status = rpl::combine(
rs.messagesFrom(),
rs.pollVotesFrom()
) | rpl::map([](From messages, From pollVotes) {
auto parts = QStringList();
if (messages != From::None) {
parts.push_back(
tr::lng_notification_reactions_messages(tr::now));
}
if (pollVotes != From::None) {
parts.push_back(
tr::lng_notification_reactions_poll_votes(tr::now));
}
return parts.isEmpty()
? tr::lng_notification_click_to_change(tr::now)
: parts.join(u", "_q);
});
const auto [button, toggleButton, checkView] = SetupSplitToggle(
container,
tr::lng_notification_reactions(),
&st::menuIconGroupReactions,
rs.enabledCurrent(),
std::move(status));
button->setClickedCallback([=] {
showOther(NotificationsReactions::Id());
});
rs.enabled(
) | rpl::on_next([=](bool enabled) {
checkView->setChecked(enabled, anim::type::normal);
}, button->lifetime());
toggleButton->clicks(
) | rpl::on_next([=] {
const auto enabled = !checkView->checked();
const auto from = enabled ? From::All : From::None;
session->api().reactionsNotifySettings().setAllFrom(from);
}, toggleButton->lifetime());
return button;
}
NotificationsCount::NotificationsCount(
QWidget *parent,
not_null<Window::SessionController*> controller)
@@ -1104,6 +1176,10 @@ void BuildNotifyTypeSection(SectionBuilder &builder) {
controller,
Data::DefaultNotify::Broadcast,
showOther);
const auto reactions = AddReactionsButton(
ctx.container,
controller,
showOther);
if (ctx.highlights) {
ctx.highlights->push_back({
u"notifications/private"_q,
@@ -1117,6 +1193,10 @@ void BuildNotifyTypeSection(SectionBuilder &builder) {
u"notifications/channels"_q,
{ channels.get(), { .rippleShape = true } },
});
ctx.highlights->push_back({
u"notifications/reactions"_q,
{ reactions.get(), { .rippleShape = true } },
});
}
return SectionBuilder::WidgetToAdd{};
}, [] {
@@ -1143,6 +1223,14 @@ void BuildNotifyTypeSection(SectionBuilder &builder) {
.icon = { &st::menuIconChannel },
};
});
builder.add(nullptr, [] {
return SearchEntry{
.id = u"notifications/reactions"_q,
.title = tr::lng_notification_reactions(tr::now),
.keywords = { u"reactions"_q },
.icon = { &st::menuIconGroupReactions },
};
});
}
void BuildEventNotificationsSection(SectionBuilder &builder) {
@@ -0,0 +1,292 @@
/*
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/sections/settings_notifications_reactions.h"
#include "api/api_reactions_notify_settings.h"
#include "apiwrap.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "settings/sections/settings_notifications.h"
#include "settings/settings_builder.h"
#include "settings/settings_common.h"
#include "settings/settings_notifications_common.h"
#include "ui/layers/generic_box.h"
#include "ui/vertical_list.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h"
#include "ui/wrap/vertical_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
#include "styles/style_menu_icons.h"
#include "styles/style_settings.h"
namespace Settings {
namespace {
using NotifyFrom = Api::ReactionsNotifyFrom;
using namespace Builder;
[[nodiscard]] rpl::producer<QString> FromLabel(NotifyFrom from) {
switch (from) {
case NotifyFrom::None:
return tr::lng_notification_reactions_from_nobody();
case NotifyFrom::Contacts:
return tr::lng_notification_reactions_from_contacts();
case NotifyFrom::All:
return tr::lng_notification_reactions_from_all();
}
Unexpected("Value in FromLabel.");
}
void ShowFromBox(
not_null<Window::SessionController*> controller,
NotifyFrom current,
Fn<void(NotifyFrom)> done) {
controller->show(Box([=](not_null<Ui::GenericBox*> box) {
box->setTitle(tr::lng_notification_reactions_from());
const auto initial = (current == NotifyFrom::None)
? int(NotifyFrom::All)
: int(current);
const auto group = std::make_shared<Ui::RadiobuttonGroup>(initial);
const auto addOption = [&](NotifyFrom value, const QString &label) {
box->addRow(
object_ptr<Ui::Radiobutton>(
box,
group,
int(value),
label,
st::defaultBoxCheckbox),
st::boxOptionListPadding
+ QMargins(
st::boxPadding.left(),
0,
st::boxPadding.right(),
st::boxOptionListSkip));
};
addOption(
NotifyFrom::All,
tr::lng_notification_reactions_from_all(tr::now));
addOption(
NotifyFrom::Contacts,
tr::lng_notification_reactions_from_contacts(tr::now));
box->addButton(tr::lng_box_ok(), [=] {
done(NotifyFrom(group->current()));
box->closeBox();
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}));
}
void AddToggleRow(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
rpl::producer<QString> title,
const style::icon *icon,
rpl::producer<NotifyFrom> fromValue,
Fn<NotifyFrom()> fromCurrent,
Fn<void(NotifyFrom)> updateFrom) {
auto forToggle = rpl::duplicate(fromValue);
auto status = std::move(
fromValue
) | rpl::map([](NotifyFrom from) {
return FromLabel(from);
}) | rpl::flatten_latest();
const auto [button, toggleButton, checkView] = SetupSplitToggle(
container,
std::move(title),
icon,
fromCurrent() != NotifyFrom::None,
std::move(status));
std::move(
forToggle
) | rpl::on_next([=](NotifyFrom from) {
checkView->setChecked(
from != NotifyFrom::None,
anim::type::normal);
}, button->lifetime());
toggleButton->clicks(
) | rpl::on_next([=] {
const auto enabled = !checkView->checked();
updateFrom(enabled ? NotifyFrom::All : NotifyFrom::None);
}, toggleButton->lifetime());
button->setClickedCallback([=] {
if (fromCurrent() == NotifyFrom::None) {
updateFrom(NotifyFrom::All);
return;
}
ShowFromBox(controller, fromCurrent(), [=](NotifyFrom from) {
updateFrom(from);
});
});
}
void BuildNotificationsReactionsContent(SectionBuilder &builder) {
builder.addSkip(st::settingsCheckboxesSkip);
builder.addSubsectionTitle({
.id = u"notifications/reactions/about"_q,
.title = tr::lng_notification_reactions_notify_about(),
});
builder.add([](const WidgetContext &ctx) {
const auto session = &ctx.controller->session();
auto &rs = session->api().reactionsNotifySettings();
AddToggleRow(
ctx.container,
ctx.controller,
tr::lng_notification_reactions_messages_full(),
&st::menuIconMarkUnread,
rs.messagesFrom(),
[session] {
return session->api().reactionsNotifySettings()
.messagesFromCurrent();
},
[session](NotifyFrom from) {
session->api().reactionsNotifySettings()
.updateMessagesFrom(from);
});
AddToggleRow(
ctx.container,
ctx.controller,
tr::lng_notification_reactions_poll_votes_full(),
&st::menuIconCreatePoll,
rs.pollVotesFrom(),
[session] {
return session->api().reactionsNotifySettings()
.pollVotesFromCurrent();
},
[session](NotifyFrom from) {
session->api().reactionsNotifySettings()
.updatePollVotesFrom(from);
});
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"notifications/reactions/messages"_q,
.title = tr::lng_notification_reactions_messages_full(tr::now),
.keywords = { u"reactions"_q, u"messages"_q },
};
});
builder.addSkip(st::settingsCheckboxesSkip);
builder.addDivider();
builder.addSkip(st::settingsCheckboxesSkip);
builder.addSubsectionTitle({
.id = u"notifications/reactions/settings"_q,
.title = tr::lng_notification_reactions_settings(),
});
builder.add([](const WidgetContext &ctx) {
const auto session = &ctx.controller->session();
auto &rs = session->api().reactionsNotifySettings();
const auto showSender = AddButtonWithIcon(
ctx.container,
tr::lng_notification_reactions_show_sender(),
st::settingsButtonNoIcon
)->toggleOn(rs.showPreviews());
showSender->toggledChanges(
) | rpl::filter([session](bool checked) {
return (checked
!= session->api().reactionsNotifySettings()
.showPreviewsCurrent());
}) | rpl::on_next([session](bool checked) {
session->api().reactionsNotifySettings()
.updateShowPreviews(checked);
}, showSender->lifetime());
return SectionBuilder::WidgetToAdd{};
}, [] {
return SearchEntry{
.id = u"notifications/reactions/preview"_q,
.title = tr::lng_notification_reactions_show_sender(tr::now),
.keywords = { u"sender"_q, u"preview"_q },
};
});
}
const auto kMeta = BuildHelper({
.id = NotificationsReactions::Id(),
.parentId = NotificationsId(),
.title = &tr::lng_notification_reactions,
.icon = &st::menuIconGroupReactions,
}, [](SectionBuilder &builder) {
BuildNotificationsReactionsContent(builder);
});
} // namespace
NotificationsReactions::NotificationsReactions(
QWidget *parent,
not_null<Window::SessionController*> controller)
: Section(parent, controller) {
setupContent(controller);
}
rpl::producer<QString> NotificationsReactions::title() {
return tr::lng_notification_reactions_title();
}
void NotificationsReactions::setupContent(
not_null<Window::SessionController*> controller) {
const auto container = Ui::CreateChild<Ui::VerticalLayout>(this);
const SectionBuildMethod buildMethod = [](
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,
Fn<void(Type)> showOther,
rpl::producer<> showFinished) {
auto &lifetime = container->lifetime();
const auto highlights
= lifetime.make_state<HighlightRegistry>();
const auto session = &controller->session();
auto &rs = session->api().reactionsNotifySettings();
rs.reload();
auto builder = SectionBuilder(WidgetContext{
.container = container,
.controller = controller,
.showOther = std::move(showOther),
.isPaused = Window::PausedIn(
controller,
Window::GifPauseReason::Layer),
.highlights = highlights,
});
BuildNotificationsReactionsContent(builder);
std::move(showFinished) | rpl::on_next([=] {
for (const auto &[id, entry] : *highlights) {
if (entry.widget) {
controller->checkHighlightControl(
id,
entry.widget,
base::duplicate(entry.args));
}
}
}, lifetime);
};
build(container, buildMethod);
Ui::ResizeFitChild(this, container);
}
} // namespace Settings
@@ -0,0 +1,27 @@
/*
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_session.h"
namespace Settings {
class NotificationsReactions : public Section<NotificationsReactions> {
public:
NotificationsReactions(
QWidget *parent,
not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<QString> title() override;
private:
void setupContent(not_null<Window::SessionController*> controller);
};
} // namespace Settings
@@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class Checkbox;
class SettingsButton;
class ToggleView;
class VerticalLayout;
template <typename Widget>
class SlideWrap;
@@ -102,6 +103,19 @@ struct NotifyViewCheckboxes {
bool nameShown,
bool previewShown);
struct SplitToggle {
not_null<Ui::SettingsButton*> button;
not_null<Ui::SettingsButton*> toggle;
not_null<Ui::ToggleView*> checkView;
};
[[nodiscard]] SplitToggle SetupSplitToggle(
not_null<Ui::VerticalLayout*> container,
rpl::producer<QString> title,
const style::icon *icon,
bool checked,
rpl::producer<QString> details);
[[nodiscard]] not_null<Ui::SettingsButton*> AddTypeButton(
not_null<Ui::VerticalLayout*> container,
not_null<Window::SessionController*> controller,