Manage notifications exceptions in Settings.
This commit is contained in:
parent
518f0e22cd
commit
b80f5f9706
|
@ -498,6 +498,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_notification_title_channels" = "Notifications for channels";
|
"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#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_about_channels#other" = "Please note that **{count} channels** are listed as exceptions and won't be affected by this change.";
|
||||||
|
"lng_notification_exceptions_view" = "View exceptions";
|
||||||
"lng_notification_enable" = "Enable notifications";
|
"lng_notification_enable" = "Enable notifications";
|
||||||
"lng_notification_sound" = "Sound";
|
"lng_notification_sound" = "Sound";
|
||||||
"lng_notification_tone" = "Notification tone";
|
"lng_notification_tone" = "Notification tone";
|
||||||
|
@ -505,6 +506,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
"lng_notification_exceptions_unmuted" = "Unmuted";
|
"lng_notification_exceptions_unmuted" = "Unmuted";
|
||||||
"lng_notification_exceptions_add" = "Add an exception";
|
"lng_notification_exceptions_add" = "Add an exception";
|
||||||
"lng_notification_exceptions_clear" = "Delete all exceptions";
|
"lng_notification_exceptions_clear" = "Delete all exceptions";
|
||||||
|
"lng_notification_exceptions_clear_sure" = "Are you sure you want to delete all exceptions?";
|
||||||
|
"lng_notification_exceptions_clear_button" = "Delete";
|
||||||
|
"lng_notification_exceptions_remove" = "Remove";
|
||||||
|
"lng_notification_context_remove" = "Remove exception";
|
||||||
|
|
||||||
"lng_reaction_text" = "{reaction} to your «{text}»";
|
"lng_reaction_text" = "{reaction} to your «{text}»";
|
||||||
"lng_reaction_notext" = "{reaction} to your message";
|
"lng_reaction_notext" = "{reaction} to your message";
|
||||||
|
|
|
@ -1887,17 +1887,8 @@ void ApiWrap::sendNotifySettingsUpdates() {
|
||||||
}
|
}
|
||||||
const auto &settings = session().data().notifySettings();
|
const auto &settings = session().data().notifySettings();
|
||||||
for (const auto type : base::take(_updateNotifyDefaults)) {
|
for (const auto type : base::take(_updateNotifyDefaults)) {
|
||||||
const auto input = [&] {
|
|
||||||
switch (type) {
|
|
||||||
case Data::DefaultNotify::User: return MTP_inputNotifyUsers();
|
|
||||||
case Data::DefaultNotify::Group: return MTP_inputNotifyChats();
|
|
||||||
case Data::DefaultNotify::Broadcast:
|
|
||||||
return MTP_inputNotifyBroadcasts();
|
|
||||||
}
|
|
||||||
Unexpected("Default notify type in sendNotifySettingsUpdates");
|
|
||||||
}();
|
|
||||||
request(MTPaccount_UpdateNotifySettings(
|
request(MTPaccount_UpdateNotifySettings(
|
||||||
input,
|
Data::DefaultNotifyToMTP(type),
|
||||||
settings.defaultSettings(type).serialize()
|
settings.defaultSettings(type).serialize()
|
||||||
)).afterDelay(kSmallDelayMs).send();
|
)).afterDelay(kSmallDelayMs).send();
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
*/
|
*/
|
||||||
#include "boxes/peer_list_box.h"
|
#include "boxes/peer_list_box.h"
|
||||||
|
|
||||||
|
#include "history/history.h" // chatListNameSortKey.
|
||||||
#include "main/session/session_show.h"
|
#include "main/session/session_show.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "mainwidget.h"
|
#include "mainwidget.h"
|
||||||
|
@ -396,6 +397,27 @@ void PeerListController::setSearchNoResultsText(const QString &text) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeerListController::sortByName() {
|
||||||
|
auto keys = base::flat_map<PeerListRowId, QString>();
|
||||||
|
keys.reserve(delegate()->peerListFullRowsCount());
|
||||||
|
const auto key = [&](const PeerListRow &row) {
|
||||||
|
const auto id = row.id();
|
||||||
|
const auto i = keys.find(id);
|
||||||
|
if (i != end(keys)) {
|
||||||
|
return i->second;
|
||||||
|
}
|
||||||
|
const auto peer = row.peer();
|
||||||
|
const auto history = peer->owner().history(peer);
|
||||||
|
return keys.emplace(
|
||||||
|
id,
|
||||||
|
history->chatListNameSortKey()).first->second;
|
||||||
|
};
|
||||||
|
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
|
||||||
|
return (key(a).compare(key(b)) < 0);
|
||||||
|
};
|
||||||
|
delegate()->peerListSortRows(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu(
|
base::unique_qptr<Ui::PopupMenu> PeerListController::rowContextMenu(
|
||||||
QWidget *parent,
|
QWidget *parent,
|
||||||
not_null<PeerListRow*> row) {
|
not_null<PeerListRow*> row) {
|
||||||
|
|
|
@ -560,6 +560,8 @@ protected:
|
||||||
delegate()->peerListSetSearchNoResults(std::move(noResults));
|
delegate()->peerListSetSearchNoResults(std::move(noResults));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sortByName();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PeerListDelegate *_delegate = nullptr;
|
PeerListDelegate *_delegate = nullptr;
|
||||||
std::unique_ptr<PeerListSearchController> _searchController = nullptr;
|
std::unique_ptr<PeerListSearchController> _searchController = nullptr;
|
||||||
|
|
|
@ -594,25 +594,6 @@ void ContactsBoxController::sort() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContactsBoxController::sortByName() {
|
|
||||||
auto keys = base::flat_map<PeerListRowId, QString>();
|
|
||||||
keys.reserve(delegate()->peerListFullRowsCount());
|
|
||||||
const auto key = [&](const PeerListRow &row) {
|
|
||||||
const auto id = row.id();
|
|
||||||
const auto i = keys.find(id);
|
|
||||||
if (i != end(keys)) {
|
|
||||||
return i->second;
|
|
||||||
}
|
|
||||||
const auto peer = row.peer();
|
|
||||||
const auto history = peer->owner().history(peer);
|
|
||||||
return keys.emplace(id, history->chatListNameSortKey()).first->second;
|
|
||||||
};
|
|
||||||
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
|
|
||||||
return (key(a).compare(key(b)) < 0);
|
|
||||||
};
|
|
||||||
delegate()->peerListSortRows(predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ContactsBoxController::sortByOnline() {
|
void ContactsBoxController::sortByOnline() {
|
||||||
const auto now = base::unixtime::now();
|
const auto now = base::unixtime::now();
|
||||||
const auto key = [&](const PeerListRow &row) {
|
const auto key = [&](const PeerListRow &row) {
|
||||||
|
|
|
@ -192,7 +192,6 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void sort();
|
void sort();
|
||||||
void sortByName();
|
|
||||||
void sortByOnline();
|
void sortByOnline();
|
||||||
void rebuildRows();
|
void rebuildRows();
|
||||||
void checkForEmptyRows();
|
void checkForEmptyRows();
|
||||||
|
|
|
@ -42,11 +42,39 @@ constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000);
|
||||||
return (result > 0);
|
return (result > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] bool SkipAddException(not_null<PeerData*> peer) {
|
||||||
|
if (const auto user = peer->asUser()) {
|
||||||
|
return user->isInaccessible();
|
||||||
|
} else if (const auto chat = peer->asChat()) {
|
||||||
|
return chat->isDeactivated() || chat->isForbidden();
|
||||||
|
} else if (const auto channel = peer->asChannel()) {
|
||||||
|
return channel->isForbidden();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
DefaultNotify DefaultNotifyType(not_null<const PeerData*> peer) {
|
||||||
|
return peer->isUser()
|
||||||
|
? DefaultNotify::User
|
||||||
|
: (peer->isChat() || peer->isMegagroup())
|
||||||
|
? DefaultNotify::Group
|
||||||
|
: DefaultNotify::Broadcast;
|
||||||
|
}
|
||||||
|
|
||||||
|
MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type) {
|
||||||
|
switch (type) {
|
||||||
|
case DefaultNotify::User: return MTP_inputNotifyUsers();
|
||||||
|
case DefaultNotify::Group: return MTP_inputNotifyChats();
|
||||||
|
case DefaultNotify::Broadcast: return MTP_inputNotifyBroadcasts();
|
||||||
|
}
|
||||||
|
Unexpected("Default notify type in sendNotifySettingsUpdates");
|
||||||
|
}
|
||||||
|
|
||||||
NotifySettings::NotifySettings(not_null<Session*> owner)
|
NotifySettings::NotifySettings(not_null<Session*> owner)
|
||||||
: _owner(owner)
|
: _owner(owner)
|
||||||
, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
|
, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::request(not_null<PeerData*> peer) {
|
void NotifySettings::request(not_null<PeerData*> peer) {
|
||||||
|
@ -63,7 +91,7 @@ void NotifySettings::request(not_null<PeerData*> peer) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::request(not_null<Data::Thread*> thread) {
|
void NotifySettings::request(not_null<Thread*> thread) {
|
||||||
if (const auto topic = thread->asTopic()) {
|
if (const auto topic = thread->asTopic()) {
|
||||||
if (topic->notify().settingsUnknown()) {
|
if (topic->notify().settingsUnknown()) {
|
||||||
topic->session().api().requestNotifySettings(
|
topic->session().api().requestNotifySettings(
|
||||||
|
@ -145,6 +173,7 @@ void NotifySettings::apply(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
const MTPPeerNotifySettings &settings) {
|
const MTPPeerNotifySettings &settings) {
|
||||||
if (peer->notify().change(settings)) {
|
if (peer->notify().change(settings)) {
|
||||||
|
updateException(peer);
|
||||||
updateLocal(peer);
|
updateLocal(peer);
|
||||||
Core::App().notifications().checkDelayed();
|
Core::App().notifications().checkDelayed();
|
||||||
}
|
}
|
||||||
|
@ -162,7 +191,7 @@ void NotifySettings::apply(
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::apply(
|
void NotifySettings::apply(
|
||||||
not_null<Data::ForumTopic*> topic,
|
not_null<ForumTopic*> topic,
|
||||||
const MTPPeerNotifySettings &settings) {
|
const MTPPeerNotifySettings &settings) {
|
||||||
if (topic->notify().change(settings)) {
|
if (topic->notify().change(settings)) {
|
||||||
updateLocal(topic);
|
updateLocal(topic);
|
||||||
|
@ -171,8 +200,8 @@ void NotifySettings::apply(
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::update(
|
void NotifySettings::update(
|
||||||
not_null<Data::Thread*> thread,
|
not_null<Thread*> thread,
|
||||||
Data::MuteValue muteForSeconds,
|
MuteValue muteForSeconds,
|
||||||
std::optional<bool> silentPosts,
|
std::optional<bool> silentPosts,
|
||||||
std::optional<NotifySound> sound,
|
std::optional<NotifySound> sound,
|
||||||
std::optional<bool> storiesMuted) {
|
std::optional<bool> storiesMuted) {
|
||||||
|
@ -181,34 +210,29 @@ void NotifySettings::update(
|
||||||
silentPosts,
|
silentPosts,
|
||||||
sound,
|
sound,
|
||||||
storiesMuted)) {
|
storiesMuted)) {
|
||||||
|
if (const auto history = thread->asHistory()) {
|
||||||
|
updateException(history->peer);
|
||||||
|
}
|
||||||
updateLocal(thread);
|
updateLocal(thread);
|
||||||
thread->session().api().updateNotifySettingsDelayed(thread);
|
thread->session().api().updateNotifySettingsDelayed(thread);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::resetToDefault(not_null<Data::Thread*> thread) {
|
void NotifySettings::resetToDefault(not_null<Thread*> thread) {
|
||||||
const auto empty = MTP_peerNotifySettings(
|
// Duplicated in clearExceptions(type) and resetToDefault(peer).
|
||||||
MTP_flags(0),
|
if (thread->notify().resetToDefault()) {
|
||||||
MTPBool(),
|
if (const auto history = thread->asHistory()) {
|
||||||
MTPBool(),
|
updateException(history->peer);
|
||||||
MTPint(),
|
}
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPBool(),
|
|
||||||
MTPBool(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound());
|
|
||||||
if (thread->notify().change(empty)) {
|
|
||||||
updateLocal(thread);
|
updateLocal(thread);
|
||||||
thread->session().api().updateNotifySettingsDelayed(thread);
|
thread->session().api().updateNotifySettingsDelayed(thread);
|
||||||
|
Core::App().notifications().checkDelayed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::update(
|
void NotifySettings::update(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
Data::MuteValue muteForSeconds,
|
MuteValue muteForSeconds,
|
||||||
std::optional<bool> silentPosts,
|
std::optional<bool> silentPosts,
|
||||||
std::optional<NotifySound> sound,
|
std::optional<NotifySound> sound,
|
||||||
std::optional<bool> storiesMuted) {
|
std::optional<bool> storiesMuted) {
|
||||||
|
@ -217,33 +241,24 @@ void NotifySettings::update(
|
||||||
silentPosts,
|
silentPosts,
|
||||||
sound,
|
sound,
|
||||||
storiesMuted)) {
|
storiesMuted)) {
|
||||||
|
updateException(peer);
|
||||||
updateLocal(peer);
|
updateLocal(peer);
|
||||||
peer->session().api().updateNotifySettingsDelayed(peer);
|
peer->session().api().updateNotifySettingsDelayed(peer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::resetToDefault(not_null<PeerData*> peer) {
|
void NotifySettings::resetToDefault(not_null<PeerData*> peer) {
|
||||||
const auto empty = MTP_peerNotifySettings(
|
// Duplicated in clearExceptions(type) and resetToDefault(thread).
|
||||||
MTP_flags(0),
|
if (peer->notify().resetToDefault()) {
|
||||||
MTPBool(),
|
updateException(peer);
|
||||||
MTPBool(),
|
|
||||||
MTPint(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPBool(),
|
|
||||||
MTPBool(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound(),
|
|
||||||
MTPNotificationSound());
|
|
||||||
if (peer->notify().change(empty)) {
|
|
||||||
updateLocal(peer);
|
updateLocal(peer);
|
||||||
peer->session().api().updateNotifySettingsDelayed(peer);
|
peer->session().api().updateNotifySettingsDelayed(peer);
|
||||||
|
Core::App().notifications().checkDelayed();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::forumParentMuteUpdated(not_null<Data::Forum*> forum) {
|
void NotifySettings::forumParentMuteUpdated(not_null<Forum*> forum) {
|
||||||
forum->enumerateTopics([&](not_null<Data::ForumTopic*> topic) {
|
forum->enumerateTopics([&](not_null<ForumTopic*> topic) {
|
||||||
if (!topic->notify().settingsUnknown()) {
|
if (!topic->notify().settingsUnknown()) {
|
||||||
updateLocal(topic);
|
updateLocal(topic);
|
||||||
}
|
}
|
||||||
|
@ -266,11 +281,7 @@ auto NotifySettings::defaultValue(DefaultNotify type) const
|
||||||
|
|
||||||
const PeerNotifySettings &NotifySettings::defaultSettings(
|
const PeerNotifySettings &NotifySettings::defaultSettings(
|
||||||
not_null<const PeerData*> peer) const {
|
not_null<const PeerData*> peer) const {
|
||||||
return defaultSettings(peer->isUser()
|
return defaultSettings(DefaultNotifyType(peer));
|
||||||
? DefaultNotify::User
|
|
||||||
: (peer->isChat() || peer->isMegagroup())
|
|
||||||
? DefaultNotify::Group
|
|
||||||
: DefaultNotify::Broadcast);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const PeerNotifySettings &NotifySettings::defaultSettings(
|
const PeerNotifySettings &NotifySettings::defaultSettings(
|
||||||
|
@ -280,7 +291,7 @@ const PeerNotifySettings &NotifySettings::defaultSettings(
|
||||||
|
|
||||||
void NotifySettings::defaultUpdate(
|
void NotifySettings::defaultUpdate(
|
||||||
DefaultNotify type,
|
DefaultNotify type,
|
||||||
Data::MuteValue muteForSeconds,
|
MuteValue muteForSeconds,
|
||||||
std::optional<bool> silentPosts,
|
std::optional<bool> silentPosts,
|
||||||
std::optional<NotifySound> sound,
|
std::optional<NotifySound> sound,
|
||||||
std::optional<bool> storiesMuted) {
|
std::optional<bool> storiesMuted) {
|
||||||
|
@ -291,7 +302,7 @@ void NotifySettings::defaultUpdate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::updateLocal(not_null<Data::Thread*> thread) {
|
void NotifySettings::updateLocal(not_null<Thread*> thread) {
|
||||||
const auto topic = thread->asTopic();
|
const auto topic = thread->asTopic();
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
return updateLocal(thread->peer());
|
return updateLocal(thread->peer());
|
||||||
|
@ -351,7 +362,7 @@ void NotifySettings::cacheSound(not_null<DocumentData*> document) {
|
||||||
const auto view = document->createMediaView();
|
const auto view = document->createMediaView();
|
||||||
_ringtones.views.emplace(document->id, view);
|
_ringtones.views.emplace(document->id, view);
|
||||||
document->forceToCache(true);
|
document->forceToCache(true);
|
||||||
document->save(Data::FileOriginRingtones(), QString());
|
document->save(FileOriginRingtones(), QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) {
|
void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) {
|
||||||
|
@ -459,7 +470,7 @@ void NotifySettings::unmuteByFinished() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NotifySettings::isMuted(
|
bool NotifySettings::isMuted(
|
||||||
not_null<const Data::Thread*> thread,
|
not_null<const Thread*> thread,
|
||||||
crl::time *changesIn) const {
|
crl::time *changesIn) const {
|
||||||
const auto topic = thread->asTopic();
|
const auto topic = thread->asTopic();
|
||||||
const auto until = topic ? topic->notify().muteUntil() : std::nullopt;
|
const auto until = topic ? topic->notify().muteUntil() : std::nullopt;
|
||||||
|
@ -468,27 +479,24 @@ bool NotifySettings::isMuted(
|
||||||
: isMuted(thread->peer(), changesIn);
|
: isMuted(thread->peer(), changesIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NotifySettings::isMuted(not_null<const Data::Thread*> thread) const {
|
bool NotifySettings::isMuted(not_null<const Thread*> thread) const {
|
||||||
return isMuted(thread, nullptr);
|
return isMuted(thread, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
NotifySound NotifySettings::sound(
|
NotifySound NotifySettings::sound(not_null<const Thread*> thread) const {
|
||||||
not_null<const Data::Thread*> thread) const {
|
|
||||||
const auto topic = thread->asTopic();
|
const auto topic = thread->asTopic();
|
||||||
const auto sound = topic ? topic->notify().sound() : std::nullopt;
|
const auto sound = topic ? topic->notify().sound() : std::nullopt;
|
||||||
return sound ? *sound : this->sound(thread->peer());
|
return sound ? *sound : this->sound(thread->peer());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NotifySettings::muteUnknown(
|
bool NotifySettings::muteUnknown(not_null<const Thread*> thread) const {
|
||||||
not_null<const Data::Thread*> thread) const {
|
|
||||||
const auto topic = thread->asTopic();
|
const auto topic = thread->asTopic();
|
||||||
return (topic && topic->notify().settingsUnknown())
|
return (topic && topic->notify().settingsUnknown())
|
||||||
|| ((!topic || !topic->notify().muteUntil().has_value())
|
|| ((!topic || !topic->notify().muteUntil().has_value())
|
||||||
&& muteUnknown(thread->peer()));
|
&& muteUnknown(thread->peer()));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NotifySettings::soundUnknown(
|
bool NotifySettings::soundUnknown(not_null<const Thread*> thread) const {
|
||||||
not_null<const Data::Thread*> thread) const {
|
|
||||||
const auto topic = thread->asTopic();
|
const auto topic = thread->asTopic();
|
||||||
return (topic && topic->notify().settingsUnknown())
|
return (topic && topic->notify().settingsUnknown())
|
||||||
|| ((!topic || !topic->notify().sound().has_value())
|
|| ((!topic || !topic->notify().sound().has_value())
|
||||||
|
@ -543,8 +551,7 @@ bool NotifySettings::silentPostsUnknown(
|
||||||
&& defaultSettings(peer).settingsUnknown());
|
&& defaultSettings(peer).settingsUnknown());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NotifySettings::soundUnknown(
|
bool NotifySettings::soundUnknown(not_null<const PeerData*> peer) const {
|
||||||
not_null<const PeerData*> peer) const {
|
|
||||||
return peer->notify().settingsUnknown()
|
return peer->notify().settingsUnknown()
|
||||||
|| (!peer->notify().sound().has_value()
|
|| (!peer->notify().sound().has_value()
|
||||||
&& defaultSettings(peer).settingsUnknown());
|
&& defaultSettings(peer).settingsUnknown());
|
||||||
|
@ -556,8 +563,7 @@ bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
|
||||||
|| soundUnknown(peer);
|
|| soundUnknown(peer);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NotifySettings::settingsUnknown(
|
bool NotifySettings::settingsUnknown(not_null<const Thread*> thread) const {
|
||||||
not_null<const Data::Thread*> thread) const {
|
|
||||||
const auto topic = thread->asTopic();
|
const auto topic = thread->asTopic();
|
||||||
return muteUnknown(thread)
|
return muteUnknown(thread)
|
||||||
|| soundUnknown(thread)
|
|| soundUnknown(thread)
|
||||||
|
@ -577,4 +583,85 @@ rpl::producer<> NotifySettings::defaultUpdates(
|
||||||
: DefaultNotify::Broadcast);
|
: DefaultNotify::Broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NotifySettings::loadExceptions() {
|
||||||
|
for (auto i = 0; i != kDefaultNotifyTypes; ++i) {
|
||||||
|
if (_exceptionsRequestId[i]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto type = static_cast<DefaultNotify>(i);
|
||||||
|
const auto api = &_owner->session().api();
|
||||||
|
const auto requestId = api->request(MTPaccount_GetNotifyExceptions(
|
||||||
|
MTP_flags(MTPaccount_GetNotifyExceptions::Flag::f_peer),
|
||||||
|
DefaultNotifyToMTP(type)
|
||||||
|
)).done([=](const MTPUpdates &result) {
|
||||||
|
api->applyUpdates(result);
|
||||||
|
}).send();
|
||||||
|
_exceptionsRequestId[i] = requestId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifySettings::updateException(not_null<PeerData*> peer) {
|
||||||
|
const auto type = DefaultNotifyType(peer);
|
||||||
|
const auto index = static_cast<int>(type);
|
||||||
|
const auto exception = peer->notify().muteUntil().has_value();
|
||||||
|
if (!exception) {
|
||||||
|
if (_exceptions[index].remove(peer)) {
|
||||||
|
exceptionsUpdated(type);
|
||||||
|
}
|
||||||
|
} else if (SkipAddException(peer)) {
|
||||||
|
return;
|
||||||
|
} else if (_exceptions[index].emplace(peer).second) {
|
||||||
|
exceptionsUpdated(type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifySettings::exceptionsUpdated(DefaultNotify type) {
|
||||||
|
if (!ranges::contains(_exceptionsUpdatesScheduled, true)) {
|
||||||
|
crl::on_main(&_owner->session(), [=] {
|
||||||
|
const auto scheduled = base::take(_exceptionsUpdatesScheduled);
|
||||||
|
for (auto i = 0; i != kDefaultNotifyTypes; ++i) {
|
||||||
|
if (scheduled[i]) {
|
||||||
|
_exceptionsUpdates.fire(static_cast<DefaultNotify>(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_exceptionsUpdatesScheduled[static_cast<int>(type)] = true;
|
||||||
|
_exceptionsUpdatesRealtime.fire_copy(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<DefaultNotify> NotifySettings::exceptionsUpdates() const {
|
||||||
|
return _exceptionsUpdates.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto NotifySettings::exceptionsUpdatesRealtime() const
|
||||||
|
-> rpl::producer<DefaultNotify> {
|
||||||
|
return _exceptionsUpdatesRealtime.events();
|
||||||
|
}
|
||||||
|
|
||||||
|
const base::flat_set<not_null<PeerData*>> &NotifySettings::exceptions(
|
||||||
|
DefaultNotify type) const {
|
||||||
|
const auto index = static_cast<int>(type);
|
||||||
|
Assert(index >= 0 && index < kDefaultNotifyTypes);
|
||||||
|
|
||||||
|
return _exceptions[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void NotifySettings::clearExceptions(DefaultNotify type) {
|
||||||
|
const auto index = static_cast<int>(type);
|
||||||
|
const auto list = base::take(_exceptions[index]);
|
||||||
|
if (list.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const auto &peer : list) {
|
||||||
|
// Duplicated in resetToDefault(peer / thread).
|
||||||
|
if (peer->notify().resetToDefault()) {
|
||||||
|
updateLocal(peer);
|
||||||
|
peer->session().api().updateNotifySettingsDelayed(peer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Core::App().notifications().checkDelayed();
|
||||||
|
exceptionsUpdated(type);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -26,13 +26,17 @@ enum class DefaultNotify {
|
||||||
Group,
|
Group,
|
||||||
Broadcast,
|
Broadcast,
|
||||||
};
|
};
|
||||||
|
[[nodiscard]] DefaultNotify DefaultNotifyType(
|
||||||
|
not_null<const PeerData*> peer);
|
||||||
|
|
||||||
|
[[nodiscard]] MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type);
|
||||||
|
|
||||||
class NotifySettings final {
|
class NotifySettings final {
|
||||||
public:
|
public:
|
||||||
NotifySettings(not_null<Session*> owner);
|
NotifySettings(not_null<Session*> owner);
|
||||||
|
|
||||||
void request(not_null<PeerData*> peer);
|
void request(not_null<PeerData*> peer);
|
||||||
void request(not_null<Data::Thread*> thread);
|
void request(not_null<Thread*> thread);
|
||||||
|
|
||||||
void apply(
|
void apply(
|
||||||
const MTPNotifyPeer ¬ifyPeer,
|
const MTPNotifyPeer ¬ifyPeer,
|
||||||
|
@ -50,25 +54,25 @@ public:
|
||||||
MsgId topicRootId,
|
MsgId topicRootId,
|
||||||
const MTPPeerNotifySettings &settings);
|
const MTPPeerNotifySettings &settings);
|
||||||
void apply(
|
void apply(
|
||||||
not_null<Data::ForumTopic*> topic,
|
not_null<ForumTopic*> topic,
|
||||||
const MTPPeerNotifySettings &settings);
|
const MTPPeerNotifySettings &settings);
|
||||||
|
|
||||||
void update(
|
void update(
|
||||||
not_null<Data::Thread*> thread,
|
not_null<Thread*> thread,
|
||||||
Data::MuteValue muteForSeconds,
|
MuteValue muteForSeconds,
|
||||||
std::optional<bool> silentPosts = std::nullopt,
|
std::optional<bool> silentPosts = std::nullopt,
|
||||||
std::optional<NotifySound> sound = std::nullopt,
|
std::optional<NotifySound> sound = std::nullopt,
|
||||||
std::optional<bool> storiesMuted = std::nullopt);
|
std::optional<bool> storiesMuted = std::nullopt);
|
||||||
void resetToDefault(not_null<Data::Thread*> thread);
|
void resetToDefault(not_null<Thread*> thread);
|
||||||
void update(
|
void update(
|
||||||
not_null<PeerData*> peer,
|
not_null<PeerData*> peer,
|
||||||
Data::MuteValue muteForSeconds,
|
MuteValue muteForSeconds,
|
||||||
std::optional<bool> silentPosts = std::nullopt,
|
std::optional<bool> silentPosts = std::nullopt,
|
||||||
std::optional<NotifySound> sound = std::nullopt,
|
std::optional<NotifySound> sound = std::nullopt,
|
||||||
std::optional<bool> storiesMuted = std::nullopt);
|
std::optional<bool> storiesMuted = std::nullopt);
|
||||||
void resetToDefault(not_null<PeerData*> peer);
|
void resetToDefault(not_null<PeerData*> peer);
|
||||||
|
|
||||||
void forumParentMuteUpdated(not_null<Data::Forum*> forum);
|
void forumParentMuteUpdated(not_null<Forum*> forum);
|
||||||
|
|
||||||
void cacheSound(DocumentId id);
|
void cacheSound(DocumentId id);
|
||||||
void cacheSound(not_null<DocumentData*> document);
|
void cacheSound(not_null<DocumentData*> document);
|
||||||
|
@ -84,18 +88,15 @@ public:
|
||||||
|
|
||||||
void defaultUpdate(
|
void defaultUpdate(
|
||||||
DefaultNotify type,
|
DefaultNotify type,
|
||||||
Data::MuteValue muteForSeconds,
|
MuteValue muteForSeconds,
|
||||||
std::optional<bool> silentPosts = std::nullopt,
|
std::optional<bool> silentPosts = std::nullopt,
|
||||||
std::optional<NotifySound> sound = std::nullopt,
|
std::optional<NotifySound> sound = std::nullopt,
|
||||||
std::optional<bool> storiesMuted = std::nullopt);
|
std::optional<bool> storiesMuted = std::nullopt);
|
||||||
|
|
||||||
[[nodiscard]] bool isMuted(not_null<const Data::Thread*> thread) const;
|
[[nodiscard]] bool isMuted(not_null<const Thread*> thread) const;
|
||||||
[[nodiscard]] NotifySound sound(
|
[[nodiscard]] NotifySound sound(not_null<const Thread*> thread) const;
|
||||||
not_null<const Data::Thread*> thread) const;
|
[[nodiscard]] bool muteUnknown(not_null<const Thread*> thread) const;
|
||||||
[[nodiscard]] bool muteUnknown(
|
[[nodiscard]] bool soundUnknown(not_null<const Thread*> thread) const;
|
||||||
not_null<const Data::Thread*> thread) const;
|
|
||||||
[[nodiscard]] bool soundUnknown(
|
|
||||||
not_null<const Data::Thread*> thread) const;
|
|
||||||
|
|
||||||
[[nodiscard]] bool isMuted(not_null<const PeerData*> peer) const;
|
[[nodiscard]] bool isMuted(not_null<const PeerData*> peer) const;
|
||||||
[[nodiscard]] bool silentPosts(not_null<const PeerData*> peer) const;
|
[[nodiscard]] bool silentPosts(not_null<const PeerData*> peer) const;
|
||||||
|
@ -105,7 +106,17 @@ public:
|
||||||
not_null<const PeerData*> peer) const;
|
not_null<const PeerData*> peer) const;
|
||||||
[[nodiscard]] bool soundUnknown(not_null<const PeerData*> peer) const;
|
[[nodiscard]] bool soundUnknown(not_null<const PeerData*> peer) const;
|
||||||
|
|
||||||
|
void loadExceptions();
|
||||||
|
[[nodiscard]] rpl::producer<DefaultNotify> exceptionsUpdates() const;
|
||||||
|
[[nodiscard]] auto exceptionsUpdatesRealtime() const
|
||||||
|
-> rpl::producer<DefaultNotify>;
|
||||||
|
[[nodiscard]] const base::flat_set<not_null<PeerData*>> &exceptions(
|
||||||
|
DefaultNotify type) const;
|
||||||
|
void clearExceptions(DefaultNotify type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr auto kDefaultNotifyTypes = 3;
|
||||||
|
|
||||||
struct DefaultValue {
|
struct DefaultValue {
|
||||||
PeerNotifySettings settings;
|
PeerNotifySettings settings;
|
||||||
rpl::event_stream<> updates;
|
rpl::event_stream<> updates;
|
||||||
|
@ -114,7 +125,7 @@ private:
|
||||||
void cacheSound(const std::optional<NotifySound> &sound);
|
void cacheSound(const std::optional<NotifySound> &sound);
|
||||||
|
|
||||||
[[nodiscard]] bool isMuted(
|
[[nodiscard]] bool isMuted(
|
||||||
not_null<const Data::Thread*> thread,
|
not_null<const Thread*> thread,
|
||||||
crl::time *changesIn) const;
|
crl::time *changesIn) const;
|
||||||
[[nodiscard]] bool isMuted(
|
[[nodiscard]] bool isMuted(
|
||||||
not_null<const PeerData*> peer,
|
not_null<const PeerData*> peer,
|
||||||
|
@ -126,21 +137,22 @@ private:
|
||||||
not_null<const PeerData*> peer) const;
|
not_null<const PeerData*> peer) const;
|
||||||
[[nodiscard]] bool settingsUnknown(not_null<const PeerData*> peer) const;
|
[[nodiscard]] bool settingsUnknown(not_null<const PeerData*> peer) const;
|
||||||
[[nodiscard]] bool settingsUnknown(
|
[[nodiscard]] bool settingsUnknown(
|
||||||
not_null<const Data::Thread*> thread) const;
|
not_null<const Thread*> thread) const;
|
||||||
|
|
||||||
void unmuteByFinished();
|
void unmuteByFinished();
|
||||||
void unmuteByFinishedDelayed(crl::time delay);
|
void unmuteByFinishedDelayed(crl::time delay);
|
||||||
void updateLocal(not_null<Data::Thread*> thread);
|
void updateLocal(not_null<Thread*> thread);
|
||||||
void updateLocal(not_null<PeerData*> peer);
|
void updateLocal(not_null<PeerData*> peer);
|
||||||
void updateLocal(DefaultNotify type);
|
void updateLocal(DefaultNotify type);
|
||||||
|
|
||||||
|
void updateException(not_null<PeerData*> peer);
|
||||||
|
void exceptionsUpdated(DefaultNotify type);
|
||||||
|
|
||||||
const not_null<Session*> _owner;
|
const not_null<Session*> _owner;
|
||||||
|
|
||||||
DefaultValue _defaultValues[3];
|
DefaultValue _defaultValues[3];
|
||||||
std::unordered_set<not_null<const PeerData*>> _mutedPeers;
|
std::unordered_set<not_null<const PeerData*>> _mutedPeers;
|
||||||
std::unordered_map<
|
std::unordered_map<not_null<ForumTopic*>, rpl::lifetime> _mutedTopics;
|
||||||
not_null<Data::ForumTopic*>,
|
|
||||||
rpl::lifetime> _mutedTopics;
|
|
||||||
base::Timer _unmuteByFinishedTimer;
|
base::Timer _unmuteByFinishedTimer;
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
|
@ -151,6 +163,14 @@ private:
|
||||||
rpl::lifetime pendingLifetime;
|
rpl::lifetime pendingLifetime;
|
||||||
} _ringtones;
|
} _ringtones;
|
||||||
|
|
||||||
|
rpl::event_stream<DefaultNotify> _exceptionsUpdates;
|
||||||
|
rpl::event_stream<DefaultNotify> _exceptionsUpdatesRealtime;
|
||||||
|
std::array<
|
||||||
|
base::flat_set<not_null<PeerData*>>,
|
||||||
|
kDefaultNotifyTypes> _exceptions;
|
||||||
|
std::array<mtpRequestId, kDefaultNotifyTypes> _exceptionsRequestId = {};
|
||||||
|
std::array<bool, kDefaultNotifyTypes> _exceptionsUpdatesScheduled = {};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Data
|
} // namespace Data
|
||||||
|
|
|
@ -256,6 +256,15 @@ bool PeerNotifySettings::change(
|
||||||
SerializeSound(std::nullopt))); // stories_sound
|
SerializeSound(std::nullopt))); // stories_sound
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PeerNotifySettings::resetToDefault() {
|
||||||
|
if (_known && !_value) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_known = true;
|
||||||
|
_value = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<TimeId> PeerNotifySettings::muteUntil() const {
|
std::optional<TimeId> PeerNotifySettings::muteUntil() const {
|
||||||
return _value
|
return _value
|
||||||
? _value->muteUntil()
|
? _value->muteUntil()
|
||||||
|
|
|
@ -46,6 +46,7 @@ public:
|
||||||
std::optional<bool> silentPosts,
|
std::optional<bool> silentPosts,
|
||||||
std::optional<NotifySound> sound,
|
std::optional<NotifySound> sound,
|
||||||
std::optional<bool> storiesMuted);
|
std::optional<bool> storiesMuted);
|
||||||
|
bool resetToDefault();
|
||||||
|
|
||||||
bool settingsUnknown() const;
|
bool settingsUnknown() const;
|
||||||
std::optional<TimeId> muteUntil() const;
|
std::optional<TimeId> muteUntil() const;
|
||||||
|
|
|
@ -80,7 +80,7 @@ QPointer<Ui::RpWidget> Blocked::createPinnedToTop(not_null<QWidget*> parent) {
|
||||||
content,
|
content,
|
||||||
tr::lng_blocked_list_add(),
|
tr::lng_blocked_list_add(),
|
||||||
st::settingsButtonActive,
|
st::settingsButtonActive,
|
||||||
{ &st::menuIconBlockSettings, IconType::Round, &st::transparent }
|
{ &st::menuIconBlockSettings }
|
||||||
)->addClickHandler([=] {
|
)->addClickHandler([=] {
|
||||||
BlockedBoxController::BlockNewPeer(_controller);
|
BlockedBoxController::BlockNewPeer(_controller);
|
||||||
});
|
});
|
||||||
|
|
|
@ -172,10 +172,30 @@ void AddTypeButton(
|
||||||
showOther(NotificationsTypeId(type));
|
showOther(NotificationsTypeId(type));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const auto session = &controller->session();
|
||||||
|
const auto settings = &session->data().notifySettings();
|
||||||
const auto &st = st::settingsNotificationType;
|
const auto &st = st::settingsNotificationType;
|
||||||
|
auto status = rpl::combine(
|
||||||
|
NotificationsEnabledForTypeValue(session, type),
|
||||||
|
rpl::single(
|
||||||
|
type
|
||||||
|
) | rpl::then(settings->exceptionsUpdates(
|
||||||
|
) | rpl::filter(rpl::mappers::_1 == type))
|
||||||
|
) | rpl::map([=](bool enabled, const auto &) {
|
||||||
|
const auto count = int(settings->exceptions(type).size());
|
||||||
|
return !count
|
||||||
|
? tr::lng_notification_click_to_change()
|
||||||
|
: (enabled
|
||||||
|
? tr::lng_notification_on
|
||||||
|
: tr::lng_notification_off)(
|
||||||
|
lt_exceptions,
|
||||||
|
tr::lng_notification_exceptions(
|
||||||
|
lt_count,
|
||||||
|
rpl::single(float64(count))));
|
||||||
|
}) | rpl::flatten_latest();
|
||||||
const auto details = Ui::CreateChild<Ui::FlatLabel>(
|
const auto details = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
button.get(),
|
button.get(),
|
||||||
tr::lng_notification_click_to_change(),
|
std::move(status),
|
||||||
st::settingsNotificationTypeDetails);
|
st::settingsNotificationTypeDetails);
|
||||||
details->show();
|
details->show();
|
||||||
details->moveToLeft(
|
details->moveToLeft(
|
||||||
|
@ -183,12 +203,11 @@ void AddTypeButton(
|
||||||
st.padding.top() + st.height - details->height());
|
st.padding.top() + st.height - details->height());
|
||||||
details->setAttribute(Qt::WA_TransparentForMouseEvents);
|
details->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
|
||||||
const auto session = &controller->session();
|
|
||||||
const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
|
const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
|
||||||
container.get(),
|
container.get(),
|
||||||
nullptr,
|
nullptr,
|
||||||
st);
|
st);
|
||||||
const auto checkView = toggleButton->lifetime().make_state<Ui::ToggleView>(
|
const auto checkView = button->lifetime().make_state<Ui::ToggleView>(
|
||||||
st.toggle,
|
st.toggle,
|
||||||
NotificationsEnabledForType(session, type),
|
NotificationsEnabledForType(session, type),
|
||||||
[=] { toggleButton->update(); });
|
[=] { toggleButton->update(); });
|
||||||
|
@ -971,6 +990,8 @@ void SetupNotificationsContent(
|
||||||
previewWrap->toggle(settings.desktopNotify(), anim::type::instant);
|
previewWrap->toggle(settings.desktopNotify(), anim::type::instant);
|
||||||
previewDivider->toggle(!settings.desktopNotify(), anim::type::instant);
|
previewDivider->toggle(!settings.desktopNotify(), anim::type::instant);
|
||||||
|
|
||||||
|
controller->session().data().notifySettings().loadExceptions();
|
||||||
|
|
||||||
AddSkip(container, st::notifyPreviewBottomSkip);
|
AddSkip(container, st::notifyPreviewBottomSkip);
|
||||||
AddSubsectionTitle(container, tr::lng_settings_notify_title());
|
AddSubsectionTitle(container, tr::lng_settings_notify_title());
|
||||||
const auto addType = [&](Data::DefaultNotify type) {
|
const auto addType = [&](Data::DefaultNotify type) {
|
||||||
|
|
|
@ -11,14 +11,23 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "base/unixtime.h"
|
#include "base/unixtime.h"
|
||||||
#include "boxes/ringtones_box.h"
|
#include "boxes/ringtones_box.h"
|
||||||
|
#include "boxes/peer_list_box.h"
|
||||||
|
#include "boxes/peer_list_controllers.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_peer.h"
|
||||||
#include "data/data_session.h"
|
#include "data/data_session.h"
|
||||||
|
#include "history/history.h"
|
||||||
#include "lang/lang_keys.h"
|
#include "lang/lang_keys.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
|
#include "menu/menu_mute.h"
|
||||||
|
#include "ui/boxes/confirm_box.h"
|
||||||
#include "ui/widgets/buttons.h"
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/popup_menu.h"
|
||||||
#include "ui/wrap/slide_wrap.h"
|
#include "ui/wrap/slide_wrap.h"
|
||||||
#include "ui/wrap/vertical_layout.h"
|
#include "ui/wrap/vertical_layout.h"
|
||||||
#include "window/window_session_controller.h"
|
#include "window/window_session_controller.h"
|
||||||
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_menu_icons.h"
|
#include "styles/style_menu_icons.h"
|
||||||
#include "styles/style_settings.h"
|
#include "styles/style_settings.h"
|
||||||
|
|
||||||
|
@ -27,6 +36,321 @@ namespace {
|
||||||
|
|
||||||
using Notify = Data::DefaultNotify;
|
using Notify = Data::DefaultNotify;
|
||||||
|
|
||||||
|
class AddExceptionBoxController final
|
||||||
|
: public ChatsListBoxController
|
||||||
|
, public base::has_weak_ptr {
|
||||||
|
public:
|
||||||
|
AddExceptionBoxController(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
Notify type,
|
||||||
|
Fn<void(not_null<PeerData*>)> done);
|
||||||
|
|
||||||
|
Main::Session &session() const override;
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void prepareViewHook() override;
|
||||||
|
std::unique_ptr<Row> createRow(not_null<History*> history) override;
|
||||||
|
|
||||||
|
const not_null<Main::Session*> _session;
|
||||||
|
const Notify _type;
|
||||||
|
const Fn<void(not_null<PeerData*>)> _done;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
PeerData *_lastClickedPeer = nullptr;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
class ExceptionsController final : public PeerListController {
|
||||||
|
public:
|
||||||
|
ExceptionsController(
|
||||||
|
not_null<Window::SessionController*> window,
|
||||||
|
Notify type);
|
||||||
|
|
||||||
|
Main::Session &session() const override;
|
||||||
|
void prepare() override;
|
||||||
|
void rowClicked(not_null<PeerListRow*> row) override;
|
||||||
|
base::unique_qptr<Ui::PopupMenu> rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) override;
|
||||||
|
void rowRightActionClicked(not_null<PeerListRow*> row) override;
|
||||||
|
void loadMoreRows() override;
|
||||||
|
|
||||||
|
void bringToTop(not_null<PeerData*> peer);
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<int> countValue() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void refreshRows();
|
||||||
|
bool appendRow(not_null<PeerData*> peer);
|
||||||
|
std::unique_ptr<PeerListRow> createRow(not_null<PeerData*> peer) const;
|
||||||
|
void refreshStatus(not_null<PeerListRow*> row) const;
|
||||||
|
|
||||||
|
void sort();
|
||||||
|
|
||||||
|
const not_null<Window::SessionController*> _window;
|
||||||
|
const Notify _type;
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> _menu;
|
||||||
|
|
||||||
|
base::flat_map<not_null<PeerData*>, int> _topOrdered;
|
||||||
|
int _topOrder = 0;
|
||||||
|
|
||||||
|
rpl::variable<int> _count;
|
||||||
|
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
AddExceptionBoxController::AddExceptionBoxController(
|
||||||
|
not_null<Main::Session*> session,
|
||||||
|
Notify type,
|
||||||
|
Fn<void(not_null<PeerData*>)> done)
|
||||||
|
: ChatsListBoxController(session)
|
||||||
|
, _session(session)
|
||||||
|
, _type(type)
|
||||||
|
, _done(std::move(done)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Main::Session &AddExceptionBoxController::session() const {
|
||||||
|
return *_session;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddExceptionBoxController::prepareViewHook() {
|
||||||
|
delegate()->peerListSetTitle(tr::lng_notification_exceptions_add());
|
||||||
|
|
||||||
|
_session->changes().peerUpdates(
|
||||||
|
Data::PeerUpdate::Flag::Notifications
|
||||||
|
) | rpl::filter([=](const Data::PeerUpdate &update) {
|
||||||
|
return update.peer == _lastClickedPeer;
|
||||||
|
}) | rpl::start_with_next([=] {
|
||||||
|
if (const auto onstack = _done) {
|
||||||
|
onstack(_lastClickedPeer);
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddExceptionBoxController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
|
delegate()->peerListShowRowMenu(row, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> AddExceptionBoxController::rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) {
|
||||||
|
const auto peer = row->peer();
|
||||||
|
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
parent,
|
||||||
|
st::popupMenuWithIcons);
|
||||||
|
|
||||||
|
MuteMenu::FillMuteMenu(
|
||||||
|
result.get(),
|
||||||
|
peer->owner().history(peer),
|
||||||
|
delegate()->peerListUiShow());
|
||||||
|
|
||||||
|
// First clear _menu value, so that we don't check row positions yet.
|
||||||
|
base::take(_menu);
|
||||||
|
|
||||||
|
// Here unique_qptr is used like a shared pointer, where
|
||||||
|
// not the last destroyed pointer destroys the object, but the first.
|
||||||
|
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
|
||||||
|
_menu->setDestroyedCallback(crl::guard(this, [=] {
|
||||||
|
_lastClickedPeer = nullptr;
|
||||||
|
}));
|
||||||
|
_lastClickedPeer = peer;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto AddExceptionBoxController::createRow(not_null<History*> history)
|
||||||
|
-> std::unique_ptr<AddExceptionBoxController::Row> {
|
||||||
|
if (Data::DefaultNotifyType(history->peer) != _type
|
||||||
|
|| history->peer->isSelf()
|
||||||
|
|| history->peer->isRepliesChat()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return std::make_unique<Row>(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExceptionsController::ExceptionsController(
|
||||||
|
not_null<Window::SessionController*> window,
|
||||||
|
Notify type)
|
||||||
|
: _window(window)
|
||||||
|
, _type(type) {
|
||||||
|
}
|
||||||
|
|
||||||
|
Main::Session &ExceptionsController::session() const {
|
||||||
|
return _window->session();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::prepare() {
|
||||||
|
refreshRows();
|
||||||
|
|
||||||
|
session().data().notifySettings().exceptionsUpdates(
|
||||||
|
) | rpl::filter(rpl::mappers::_1 == _type) | rpl::start_with_next([=] {
|
||||||
|
refreshRows();
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
session().changes().peerUpdates(
|
||||||
|
Data::PeerUpdate::Flag::Notifications
|
||||||
|
) | rpl::start_with_next([=](const Data::PeerUpdate &update) {
|
||||||
|
const auto peer = update.peer;
|
||||||
|
if (const auto row = delegate()->peerListFindRow(peer->id.value)) {
|
||||||
|
if (peer->notify().muteUntil().has_value()) {
|
||||||
|
refreshStatus(row);
|
||||||
|
} else {
|
||||||
|
delegate()->peerListRemoveRow(row);
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
_count = delegate()->peerListFullRowsCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::loadMoreRows() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::bringToTop(not_null<PeerData*> peer) {
|
||||||
|
_topOrdered[peer] = ++_topOrder;
|
||||||
|
if (delegate()->peerListFindRow(peer->id.value)) {
|
||||||
|
sort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> ExceptionsController::countValue() const {
|
||||||
|
return _count.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::rowClicked(not_null<PeerListRow*> row) {
|
||||||
|
delegate()->peerListShowRowMenu(row, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::rowRightActionClicked(
|
||||||
|
not_null<PeerListRow*> row) {
|
||||||
|
session().data().notifySettings().resetToDefault(row->peer());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::refreshRows() {
|
||||||
|
auto seen = base::flat_set<not_null<PeerData*>>();
|
||||||
|
const auto &list = session().data().notifySettings().exceptions(_type);
|
||||||
|
auto removed = false, added = false;
|
||||||
|
auto already = delegate()->peerListFullRowsCount();
|
||||||
|
seen.reserve(std::min(int(list.size()), already));
|
||||||
|
for (auto i = 0; i != already;) {
|
||||||
|
const auto row = delegate()->peerListRowAt(i);
|
||||||
|
if (list.contains(row->peer())) {
|
||||||
|
seen.emplace(row->peer());
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
delegate()->peerListRemoveRow(row);
|
||||||
|
--already;
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &peer : list) {
|
||||||
|
if (!seen.contains(peer)) {
|
||||||
|
appendRow(peer);
|
||||||
|
added = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (added || removed) {
|
||||||
|
if (added) {
|
||||||
|
sort();
|
||||||
|
}
|
||||||
|
delegate()->peerListRefreshRows();
|
||||||
|
_count = delegate()->peerListFullRowsCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base::unique_qptr<Ui::PopupMenu> ExceptionsController::rowContextMenu(
|
||||||
|
QWidget *parent,
|
||||||
|
not_null<PeerListRow*> row) {
|
||||||
|
const auto peer = row->peer();
|
||||||
|
auto result = base::make_unique_q<Ui::PopupMenu>(
|
||||||
|
parent,
|
||||||
|
st::popupMenuWithIcons);
|
||||||
|
|
||||||
|
result->addAction(
|
||||||
|
(peer->isUser()
|
||||||
|
? tr::lng_context_view_profile
|
||||||
|
: peer->isBroadcast()
|
||||||
|
? tr::lng_context_view_channel
|
||||||
|
: tr::lng_context_view_group)(tr::now),
|
||||||
|
crl::guard(_window, [window = _window.get(), peer] {
|
||||||
|
window->showPeerInfo(peer);
|
||||||
|
}),
|
||||||
|
(peer->isUser() ? &st::menuIconProfile : &st::menuIconInfo));
|
||||||
|
result->addSeparator();
|
||||||
|
|
||||||
|
MuteMenu::FillMuteMenu(
|
||||||
|
result.get(),
|
||||||
|
peer->owner().history(peer),
|
||||||
|
_window->uiShow());
|
||||||
|
|
||||||
|
// First clear _menu value, so that we don't check row positions yet.
|
||||||
|
base::take(_menu);
|
||||||
|
|
||||||
|
// Here unique_qptr is used like a shared pointer, where
|
||||||
|
// not the last destroyed pointer destroys the object, but the first.
|
||||||
|
_menu = base::unique_qptr<Ui::PopupMenu>(result.get());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ExceptionsController::appendRow(not_null<PeerData*> peer) {
|
||||||
|
delegate()->peerListAppendRow(createRow(peer));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<PeerListRow> ExceptionsController::createRow(
|
||||||
|
not_null<PeerData*> peer) const {
|
||||||
|
auto row = std::make_unique<PeerListRowWithLink>(peer);
|
||||||
|
row->setActionLink(tr::lng_notification_exceptions_remove(tr::now));
|
||||||
|
refreshStatus(row.get());
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::refreshStatus(not_null<PeerListRow*> row) const {
|
||||||
|
const auto peer = row->peer();
|
||||||
|
const auto status = peer->owner().notifySettings().isMuted(peer)
|
||||||
|
? tr::lng_notification_exceptions_muted(tr::now)
|
||||||
|
: tr::lng_notification_exceptions_unmuted(tr::now);
|
||||||
|
row->setCustomStatus(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExceptionsController::sort() {
|
||||||
|
auto keys = base::flat_map<PeerListRowId, QString>();
|
||||||
|
keys.reserve(delegate()->peerListFullRowsCount());
|
||||||
|
const auto length = QString::number(_topOrder).size();
|
||||||
|
const auto key = [&](const PeerListRow &row) {
|
||||||
|
const auto id = row.id();
|
||||||
|
const auto i = keys.find(id);
|
||||||
|
if (i != end(keys)) {
|
||||||
|
return i->second;
|
||||||
|
}
|
||||||
|
const auto peer = row.peer();
|
||||||
|
const auto top = _topOrdered.find(peer);
|
||||||
|
if (top != end(_topOrdered)) {
|
||||||
|
const auto order = _topOrder - top->second;
|
||||||
|
return keys.emplace(
|
||||||
|
id,
|
||||||
|
u"0%1"_q.arg(order, length, 10, QChar('0'))).first->second;
|
||||||
|
}
|
||||||
|
const auto history = peer->owner().history(peer);
|
||||||
|
return keys.emplace(
|
||||||
|
id,
|
||||||
|
'1' + history->chatListNameSortKey()).first->second;
|
||||||
|
};
|
||||||
|
const auto predicate = [&](const PeerListRow &a, const PeerListRow &b) {
|
||||||
|
return (key(a).compare(key(b)) < 0);
|
||||||
|
};
|
||||||
|
delegate()->peerListSortRows(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
template <Notify kType>
|
template <Notify kType>
|
||||||
[[nodiscard]] Type Id() {
|
[[nodiscard]] Type Id() {
|
||||||
return &NotificationsTypeMetaImplementation<kType>::Meta;
|
return &NotificationsTypeMetaImplementation<kType>::Meta;
|
||||||
|
@ -103,7 +427,7 @@ void SetupChecks(
|
||||||
: ExtractRingtoneName(session->data().document(now.id));
|
: ExtractRingtoneName(session->data().document(now.id));
|
||||||
};
|
};
|
||||||
settings->defaultUpdates(
|
settings->defaultUpdates(
|
||||||
Data::DefaultNotify::User
|
Notify::User
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
toneLabel->fire(label());
|
toneLabel->fire(label());
|
||||||
}, toneInner->lifetime());
|
}, toneInner->lifetime());
|
||||||
|
@ -147,9 +471,74 @@ void SetupChecks(
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetupExceptions(
|
void SetupExceptions(
|
||||||
not_null<Ui::VerticalLayout*> container,
|
not_null<Ui::VerticalLayout*> container,
|
||||||
not_null<Window::SessionController*> controller,
|
not_null<Window::SessionController*> window,
|
||||||
Notify type) {
|
Notify type) {
|
||||||
|
const auto add = AddButton(
|
||||||
|
container,
|
||||||
|
tr::lng_notification_exceptions_add(),
|
||||||
|
st::settingsButtonActive,
|
||||||
|
{ &st::menuIconInviteSettings });
|
||||||
|
|
||||||
|
auto controller = std::make_unique<ExceptionsController>(window, type);
|
||||||
|
controller->setStyleOverrides(&st::settingsBlockedList);
|
||||||
|
const auto content = container->add(
|
||||||
|
object_ptr<PeerListContent>(container, controller.get()));
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
std::unique_ptr<ExceptionsController> controller;
|
||||||
|
std::unique_ptr<PeerListContentDelegateSimple> delegate;
|
||||||
|
};
|
||||||
|
const auto state = content->lifetime().make_state<State>();
|
||||||
|
state->controller = std::move(controller);
|
||||||
|
state->delegate = std::make_unique<PeerListContentDelegateSimple>();
|
||||||
|
|
||||||
|
state->delegate->setContent(content);
|
||||||
|
state->controller->setDelegate(state->delegate.get());
|
||||||
|
|
||||||
|
add->setClickedCallback([=] {
|
||||||
|
const auto box = std::make_shared<QPointer<Ui::BoxContent>>();
|
||||||
|
const auto done = [=](not_null<PeerData*> peer) {
|
||||||
|
state->controller->bringToTop(peer);
|
||||||
|
if (*box) {
|
||||||
|
(*box)->closeBox();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
auto controller = std::make_unique<AddExceptionBoxController>(
|
||||||
|
&window->session(),
|
||||||
|
type,
|
||||||
|
crl::guard(content, done));
|
||||||
|
auto initBox = [=](not_null<PeerListBox*> box) {
|
||||||
|
box->addButton(tr::lng_cancel(), [box] { box->closeBox(); });
|
||||||
|
};
|
||||||
|
*box = window->show(
|
||||||
|
Box<PeerListBox>(std::move(controller), std::move(initBox)));
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto wrap = container->add(
|
||||||
|
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
|
||||||
|
container,
|
||||||
|
CreateButton(
|
||||||
|
container,
|
||||||
|
tr::lng_notification_exceptions_clear(),
|
||||||
|
st::settingsAttentionButtonWithIcon,
|
||||||
|
{ &st::menuIconDeleteAttention })));
|
||||||
|
wrap->entity()->setClickedCallback([=] {
|
||||||
|
const auto clear = [=](Fn<void()> close) {
|
||||||
|
window->session().data().notifySettings().clearExceptions(type);
|
||||||
|
close();
|
||||||
|
};
|
||||||
|
window->show(Ui::MakeConfirmBox({
|
||||||
|
.text = tr::lng_notification_exceptions_clear_sure(),
|
||||||
|
.confirmed = clear,
|
||||||
|
.confirmText = tr::lng_notification_exceptions_clear_button(),
|
||||||
|
.confirmStyle = &st::attentionBoxButton,
|
||||||
|
.title = tr::lng_notification_exceptions_clear(),
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
wrap->toggleOn(
|
||||||
|
state->controller->countValue() | rpl::map(rpl::mappers::_1 > 1),
|
||||||
|
anim::type::instant);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
@ -211,7 +600,7 @@ bool NotificationsEnabledForType(
|
||||||
|
|
||||||
rpl::producer<bool> NotificationsEnabledForTypeValue(
|
rpl::producer<bool> NotificationsEnabledForTypeValue(
|
||||||
not_null<Main::Session*> session,
|
not_null<Main::Session*> session,
|
||||||
Data::DefaultNotify type) {
|
Notify type) {
|
||||||
const auto settings = &session->data().notifySettings();
|
const auto settings = &session->data().notifySettings();
|
||||||
return rpl::single(
|
return rpl::single(
|
||||||
rpl::empty
|
rpl::empty
|
||||||
|
|
|
@ -173,6 +173,7 @@ menuIconReportAttention: icon {{ "menu/report", menuIconAttentionColor }};
|
||||||
menuIconRestoreAttention: icon {{ "menu/restore", menuIconAttentionColor }};
|
menuIconRestoreAttention: icon {{ "menu/restore", menuIconAttentionColor }};
|
||||||
|
|
||||||
menuIconBlockSettings: icon {{ "menu/block", windowBgActive }};
|
menuIconBlockSettings: icon {{ "menu/block", windowBgActive }};
|
||||||
|
menuIconInviteSettings: icon {{ "menu/invite", windowBgActive }};
|
||||||
|
|
||||||
playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
|
playerSpeedSlow: icon {{ "player/speed/audiospeed_menu_0.5", menuIconColor }};
|
||||||
playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};
|
playerSpeedSlowActive: icon {{ "player/speed/audiospeed_menu_0.5", mediaPlayerActiveFg }};
|
||||||
|
|
Loading…
Reference in New Issue