init
Some checks failed
Docker. / Ubuntu (push) Has been cancelled
User-agent updater. / User-agent (push) Failing after 15s
Lock Threads / lock (push) Failing after 10s
Waiting for answer. / waiting-for-answer (push) Failing after 22s
Needs user action. / needs-user-action (push) Failing after 8s
Can't reproduce. / cant-reproduce (push) Failing after 8s
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
allhaileris
2026-02-16 15:50:16 +03:00
commit afb81b8278
13816 changed files with 3689732 additions and 0 deletions

View File

@@ -0,0 +1,674 @@
/*
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 "data/notify/data_notify_settings.h"
#include "apiwrap.h"
#include "api/api_ringtones.h"
#include "base/unixtime.h"
#include "core/application.h"
#include "data/data_changes.h"
#include "data/data_channel.h"
#include "data/data_chat.h"
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_peer.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
#include "history/history.h"
#include "main/main_session.h"
#include "window/notifications_manager.h"
namespace Data {
namespace {
constexpr auto kMaxNotifyCheckDelay = 24 * 3600 * crl::time(1000);
[[nodiscard]] bool MutedFromUntil(TimeId until, crl::time *changesIn) {
const auto now = base::unixtime::now();
const auto result = (until > now) ? (until - now) : 0;
if (changesIn) {
*changesIn = (result > 0)
? std::min(result * crl::time(1000), kMaxNotifyCheckDelay)
: kMaxNotifyCheckDelay;
}
return (result > 0);
}
[[nodiscard]] bool SkipAddException(not_null<PeerData*> peer) {
if (const auto user = peer->asUser()) {
return user->isInaccessible() || user->isSelf();
} 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
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)
: _owner(owner)
, _unmuteByFinishedTimer([=] { unmuteByFinished(); }) {
}
void NotifySettings::request(not_null<PeerData*> peer) {
if (peer->notify().settingsUnknown()) {
peer->session().api().requestNotifySettings(
MTP_inputNotifyPeer(peer->input()));
}
if (defaultSettings(peer).settingsUnknown()) {
peer->session().api().requestNotifySettings(peer->isUser()
? MTP_inputNotifyUsers()
: (peer->isChat() || peer->isMegagroup())
? MTP_inputNotifyChats()
: MTP_inputNotifyBroadcasts());
}
}
void NotifySettings::request(not_null<Thread*> thread) {
if (const auto topic = thread->asTopic()) {
if (topic->notify().settingsUnknown()) {
topic->session().api().requestNotifySettings(
MTP_inputNotifyForumTopic(
topic->peer()->input(),
MTP_int(topic->rootId())));
}
}
request(thread->peer());
}
void NotifySettings::apply(
const MTPNotifyPeer &notifyPeer,
const MTPPeerNotifySettings &settings) {
notifyPeer.match([&](const MTPDnotifyUsers &) {
apply(DefaultNotify::User, settings);
}, [&](const MTPDnotifyChats &) {
apply(DefaultNotify::Group, settings);
}, [&](const MTPDnotifyBroadcasts &) {
apply(DefaultNotify::Broadcast, settings);
}, [&](const MTPDnotifyPeer &data) {
apply(peerFromMTP(data.vpeer()), settings);
}, [&](const MTPDnotifyForumTopic &data) {
apply(peerFromMTP(data.vpeer()), data.vtop_msg_id().v, settings);
});
}
void NotifySettings::apply(
const MTPInputNotifyPeer &notifyPeer,
const MTPPeerNotifySettings &settings) {
const auto peerFromInput = [&](const MTPInputPeer &peer) {
return peer.match([&](const MTPDinputPeerSelf &) {
return _owner->session().userPeerId();
}, [](const MTPDinputPeerUser &data) {
return peerFromUser(data.vuser_id());
}, [](const MTPDinputPeerChat &data) {
return peerFromChat(data.vchat_id());
}, [](const MTPDinputPeerChannel &data) {
return peerFromChannel(data.vchannel_id());
}, [](const MTPDinputPeerUserFromMessage &data) -> PeerId {
Unexpected("From message peer in NotifySettings::apply.");
}, [](const MTPDinputPeerChannelFromMessage &data) -> PeerId {
Unexpected("From message peer in NotifySettings::apply.");
}, [](const MTPDinputPeerEmpty &) -> PeerId {
Unexpected("Empty peer in NotifySettings::apply.");
});
};
notifyPeer.match([&](const MTPDinputNotifyUsers &) {
apply(DefaultNotify::User, settings);
}, [&](const MTPDinputNotifyChats &) {
apply(DefaultNotify::Group, settings);
}, [&](const MTPDinputNotifyBroadcasts &) {
apply(DefaultNotify::Broadcast, settings);
}, [&](const MTPDinputNotifyPeer &data) {
apply(peerFromInput(data.vpeer()), settings);
}, [&](const MTPDinputNotifyForumTopic &data) {
apply(peerFromInput(data.vpeer()), data.vtop_msg_id().v, settings);
});
}
void NotifySettings::apply(
DefaultNotify type,
const MTPPeerNotifySettings &settings) {
if (defaultValue(type).settings.change(settings)) {
updateLocal(type);
Core::App().notifications().checkDelayed();
}
}
void NotifySettings::apply(
PeerId peerId,
const MTPPeerNotifySettings &settings) {
if (const auto peer = _owner->peerLoaded(peerId)) {
apply(peer, settings);
}
}
void NotifySettings::apply(
not_null<PeerData*> peer,
const MTPPeerNotifySettings &settings) {
if (peer->notify().change(settings)) {
updateException(peer);
updateLocal(peer);
Core::App().notifications().checkDelayed();
}
}
void NotifySettings::apply(
PeerId peerId,
MsgId topicRootId,
const MTPPeerNotifySettings &settings) {
if (const auto peer = _owner->peerLoaded(peerId)) {
if (const auto topic = peer->forumTopicFor(topicRootId)) {
apply(topic, settings);
}
}
}
void NotifySettings::apply(
not_null<ForumTopic*> topic,
const MTPPeerNotifySettings &settings) {
if (topic->notify().change(settings)) {
updateLocal(topic);
Core::App().notifications().checkDelayed();
}
}
void NotifySettings::update(
not_null<Thread*> thread,
MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) {
if (thread->notify().change(
muteForSeconds,
silentPosts,
sound,
storiesMuted)) {
if (const auto history = thread->asHistory()) {
updateException(history->peer);
}
updateLocal(thread);
thread->session().api().updateNotifySettingsDelayed(thread);
}
}
void NotifySettings::resetToDefault(not_null<Thread*> thread) {
// Duplicated in clearExceptions(type) and resetToDefault(peer).
if (thread->notify().resetToDefault()) {
if (const auto history = thread->asHistory()) {
updateException(history->peer);
}
updateLocal(thread);
thread->session().api().updateNotifySettingsDelayed(thread);
Core::App().notifications().checkDelayed();
}
}
void NotifySettings::update(
not_null<PeerData*> peer,
MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) {
if (peer->notify().change(
muteForSeconds,
silentPosts,
sound,
storiesMuted)) {
updateException(peer);
updateLocal(peer);
peer->session().api().updateNotifySettingsDelayed(peer);
}
}
void NotifySettings::resetToDefault(not_null<PeerData*> peer) {
// Duplicated in clearExceptions(type) and resetToDefault(thread).
if (peer->notify().resetToDefault()) {
updateException(peer);
updateLocal(peer);
peer->session().api().updateNotifySettingsDelayed(peer);
Core::App().notifications().checkDelayed();
}
}
void NotifySettings::forumParentMuteUpdated(not_null<Forum*> forum) {
forum->enumerateTopics([&](not_null<ForumTopic*> topic) {
if (!topic->notify().settingsUnknown()) {
updateLocal(topic);
}
});
}
auto NotifySettings::defaultValue(DefaultNotify type)
-> DefaultValue & {
const auto index = static_cast<int>(type);
Assert(index >= 0 && index < base::array_size(_defaultValues));
return _defaultValues[index];
}
auto NotifySettings::defaultValue(DefaultNotify type) const
-> const DefaultValue & {
const auto index = static_cast<int>(type);
Assert(index >= 0 && index < base::array_size(_defaultValues));
return _defaultValues[index];
}
const PeerNotifySettings &NotifySettings::defaultSettings(
not_null<const PeerData*> peer) const {
return defaultSettings(DefaultNotifyType(peer));
}
const PeerNotifySettings &NotifySettings::defaultSettings(
DefaultNotify type) const {
return defaultValue(type).settings;
}
bool NotifySettings::isMuted(DefaultNotify type) const {
if (const auto until = defaultSettings(type).muteUntil()) {
return MutedFromUntil(*until, nullptr);
}
return true;
}
void NotifySettings::defaultUpdate(
DefaultNotify type,
MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) {
auto &settings = defaultValue(type).settings;
if (settings.change(muteForSeconds, silentPosts, sound, storiesMuted)) {
updateLocal(type);
_owner->session().api().updateNotifySettingsDelayed(type);
}
}
void NotifySettings::updateLocal(not_null<Thread*> thread) {
const auto topic = thread->asTopic();
if (!topic) {
return updateLocal(thread->peer());
}
auto changesIn = crl::time(0);
const auto muted = isMuted(topic, &changesIn);
topic->setMuted(muted);
if (muted) {
auto &lifetime = _mutedTopics.emplace(
topic,
rpl::lifetime()).first->second;
topic->destroyed() | rpl::on_next([=] {
_mutedTopics.erase(topic);
}, lifetime);
unmuteByFinishedDelayed(changesIn);
Core::App().notifications().clearIncomingFromTopic(topic);
} else {
_mutedTopics.erase(topic);
}
cacheSound(topic->notify().sound());
}
void NotifySettings::updateLocal(not_null<PeerData*> peer) {
const auto history = _owner->historyLoaded(peer->id);
auto changesIn = crl::time(0);
const auto muted = isMuted(peer, &changesIn);
const auto changeInHistory = history && (history->muted() != muted);
if (changeInHistory) {
history->setMuted(muted);
// Notification already sent.
} else {
peer->session().changes().peerUpdated(
peer,
PeerUpdate::Flag::Notifications);
}
if (muted) {
_mutedPeers.emplace(peer);
unmuteByFinishedDelayed(changesIn);
if (history) {
Core::App().notifications().clearIncomingFromHistory(history);
}
} else {
_mutedPeers.erase(peer);
}
cacheSound(peer->notify().sound());
}
void NotifySettings::cacheSound(DocumentId id) {
cacheSound(_owner->document(id));
}
void NotifySettings::cacheSound(not_null<DocumentData*> document) {
if (document->isNull()) {
return;
}
const auto view = document->createMediaView();
_ringtones.views.emplace(document->id, view);
document->forceToCache(true);
document->save(FileOriginRingtones(), QString());
}
void NotifySettings::cacheSound(const std::optional<NotifySound> &sound) {
if (!sound || !sound->id) {
return;
} else if (const auto doc = _owner->document(sound->id); !doc->isNull()) {
cacheSound(doc);
return;
}
_ringtones.pendingIds.push_back(sound->id);
if (_ringtones.pendingLifetime) {
return;
}
// Not requested yet.
_owner->session().api().ringtones().listUpdates(
) | rpl::on_next([=] {
for (const auto id : base::take(_ringtones.pendingIds)) {
cacheSound(id);
}
_ringtones.pendingLifetime.destroy();
}, _ringtones.pendingLifetime);
_owner->session().api().ringtones().requestList();
}
void NotifySettings::updateLocal(DefaultNotify type) {
defaultValue(type).updates.fire({});
const auto goodForUpdate = [&](
not_null<const PeerData*> peer,
const PeerNotifySettings &settings) {
auto &peers = peer->notify();
return !peers.settingsUnknown()
&& ((!peers.muteUntil() && settings.muteUntil())
|| (!peers.silentPosts() && settings.silentPosts())
|| (!peers.sound() && settings.sound()));
};
const auto callback = [&](not_null<PeerData*> peer) {
if (goodForUpdate(peer, defaultSettings(type))) {
updateLocal(peer);
}
};
switch (type) {
case DefaultNotify::User: _owner->enumerateUsers(callback); break;
case DefaultNotify::Group: _owner->enumerateGroups(callback); break;
case DefaultNotify::Broadcast:
_owner->enumerateBroadcasts(callback);
break;
}
cacheSound(defaultValue(type).settings.sound());
}
std::shared_ptr<DocumentMedia> NotifySettings::lookupRingtone(
DocumentId id) const {
if (!id) {
return nullptr;
}
const auto it = _ringtones.views.find(id);
return (it == end(_ringtones.views)) ? nullptr : it->second;
}
void NotifySettings::unmuteByFinishedDelayed(crl::time delay) {
accumulate_min(delay, kMaxNotifyCheckDelay);
if (!_unmuteByFinishedTimer.isActive()
|| _unmuteByFinishedTimer.remainingTime() > delay) {
_unmuteByFinishedTimer.callOnce(delay);
}
}
void NotifySettings::unmuteByFinished() {
auto changesInMin = crl::time(0);
for (auto i = begin(_mutedPeers); i != end(_mutedPeers);) {
const auto history = _owner->historyLoaded((*i)->id);
auto changesIn = crl::time(0);
const auto muted = isMuted(*i, &changesIn);
if (history) {
history->setMuted(muted);
}
if (muted) {
if (!changesInMin || changesInMin > changesIn) {
changesInMin = changesIn;
}
++i;
} else {
i = _mutedPeers.erase(i);
}
}
for (auto i = begin(_mutedTopics); i != end(_mutedTopics);) {
auto changesIn = crl::time(0);
const auto topic = i->first;
const auto muted = isMuted(topic, &changesIn);
topic->setMuted(muted);
if (muted) {
if (!changesInMin || changesInMin > changesIn) {
changesInMin = changesIn;
}
++i;
} else {
i = _mutedTopics.erase(i);
}
}
if (changesInMin) {
unmuteByFinishedDelayed(changesInMin);
}
}
bool NotifySettings::isMuted(
not_null<const Thread*> thread,
crl::time *changesIn) const {
const auto topic = thread->asTopic();
const auto until = topic ? topic->notify().muteUntil() : std::nullopt;
return until
? MutedFromUntil(*until, changesIn)
: isMuted(thread->peer(), changesIn);
}
bool NotifySettings::isMuted(not_null<const Thread*> thread) const {
return isMuted(thread, nullptr);
}
NotifySound NotifySettings::sound(not_null<const Thread*> thread) const {
const auto topic = thread->asTopic();
const auto sound = topic ? topic->notify().sound() : std::nullopt;
return sound ? *sound : this->sound(thread->peer());
}
bool NotifySettings::muteUnknown(not_null<const Thread*> thread) const {
const auto topic = thread->asTopic();
return (topic && topic->notify().settingsUnknown())
|| ((!topic || !topic->notify().muteUntil().has_value())
&& muteUnknown(thread->peer()));
}
bool NotifySettings::soundUnknown(not_null<const Thread*> thread) const {
const auto topic = thread->asTopic();
return (topic && topic->notify().settingsUnknown())
|| ((!topic || !topic->notify().sound().has_value())
&& soundUnknown(topic->peer()));
}
bool NotifySettings::isMuted(
not_null<const PeerData*> peer,
crl::time *changesIn) const {
if (const auto until = peer->notify().muteUntil()) {
return MutedFromUntil(*until, changesIn);
} else if (const auto until = defaultSettings(peer).muteUntil()) {
return MutedFromUntil(*until, changesIn);
}
return true;
}
bool NotifySettings::isMuted(not_null<const PeerData*> peer) const {
return isMuted(peer, nullptr);
}
bool NotifySettings::silentPosts(not_null<const PeerData*> peer) const {
if (const auto silent = peer->notify().silentPosts()) {
return *silent;
} else if (const auto silent = defaultSettings(peer).silentPosts()) {
return *silent;
}
return false;
}
NotifySound NotifySettings::sound(not_null<const PeerData*> peer) const {
// Explicitly ignore a notify sound for Saved Messages
// to follow the global notify sound.
if (const auto sound = peer->notify().sound(); !peer->isSelf() && sound) {
return *sound;
} else if (const auto sound = defaultSettings(peer).sound()) {
return *sound;
}
return {};
}
bool NotifySettings::muteUnknown(not_null<const PeerData*> peer) const {
return peer->notify().settingsUnknown()
|| (!peer->notify().muteUntil().has_value()
&& defaultSettings(peer).settingsUnknown());
}
bool NotifySettings::silentPostsUnknown(
not_null<const PeerData*> peer) const {
return peer->notify().settingsUnknown()
|| (!peer->notify().silentPosts().has_value()
&& defaultSettings(peer).settingsUnknown());
}
bool NotifySettings::soundUnknown(not_null<const PeerData*> peer) const {
return peer->notify().settingsUnknown()
|| (!peer->notify().sound().has_value()
&& defaultSettings(peer).settingsUnknown());
}
bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
return muteUnknown(peer)
|| silentPostsUnknown(peer)
|| soundUnknown(peer);
}
bool NotifySettings::settingsUnknown(not_null<const Thread*> thread) const {
const auto topic = thread->asTopic();
return muteUnknown(thread)
|| soundUnknown(thread)
|| (!topic && silentPostsUnknown(thread->peer()));
}
rpl::producer<> NotifySettings::defaultUpdates(DefaultNotify type) const {
return defaultValue(type).updates.events();
}
rpl::producer<> NotifySettings::defaultUpdates(
not_null<const PeerData*> peer) const {
return defaultUpdates(peer->isUser()
? DefaultNotify::User
: (peer->isChat() || peer->isMegagroup())
? DefaultNotify::Group
: 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

View File

@@ -0,0 +1,172 @@
/*
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 "data/notify/data_peer_notify_settings.h"
#include "base/timer.h"
class PeerData;
namespace Data {
class DocumentMedia;
class Session;
class Thread;
class Forum;
class ForumTopic;
[[nodiscard]] DefaultNotify DefaultNotifyType(
not_null<const PeerData*> peer);
[[nodiscard]] MTPInputNotifyPeer DefaultNotifyToMTP(DefaultNotify type);
class NotifySettings final {
public:
NotifySettings(not_null<Session*> owner);
void request(not_null<PeerData*> peer);
void request(not_null<Thread*> thread);
void apply(
const MTPNotifyPeer &notifyPeer,
const MTPPeerNotifySettings &settings);
void apply(
const MTPInputNotifyPeer &notifyPeer,
const MTPPeerNotifySettings &settings);
void apply(DefaultNotify type, const MTPPeerNotifySettings &settings);
void apply(PeerId peerId, const MTPPeerNotifySettings &settings);
void apply(
not_null<PeerData*> peer,
const MTPPeerNotifySettings &settings);
void apply(
PeerId peerId,
MsgId topicRootId,
const MTPPeerNotifySettings &settings);
void apply(
not_null<ForumTopic*> topic,
const MTPPeerNotifySettings &settings);
void update(
not_null<Thread*> thread,
MuteValue muteForSeconds,
std::optional<bool> silentPosts = std::nullopt,
std::optional<NotifySound> sound = std::nullopt,
std::optional<bool> storiesMuted = std::nullopt);
void resetToDefault(not_null<Thread*> thread);
void update(
not_null<PeerData*> peer,
MuteValue muteForSeconds,
std::optional<bool> silentPosts = std::nullopt,
std::optional<NotifySound> sound = std::nullopt,
std::optional<bool> storiesMuted = std::nullopt);
void resetToDefault(not_null<PeerData*> peer);
void forumParentMuteUpdated(not_null<Forum*> forum);
void cacheSound(DocumentId id);
void cacheSound(not_null<DocumentData*> document);
[[nodiscard]] std::shared_ptr<DocumentMedia> lookupRingtone(
DocumentId id) const;
[[nodiscard]] rpl::producer<> defaultUpdates(DefaultNotify type) const;
[[nodiscard]] rpl::producer<> defaultUpdates(
not_null<const PeerData*> peer) const;
[[nodiscard]] const PeerNotifySettings &defaultSettings(
DefaultNotify type) const;
[[nodiscard]] bool isMuted(DefaultNotify type) const;
void defaultUpdate(
DefaultNotify type,
MuteValue muteForSeconds,
std::optional<bool> silentPosts = std::nullopt,
std::optional<NotifySound> sound = std::nullopt,
std::optional<bool> storiesMuted = std::nullopt);
[[nodiscard]] bool isMuted(not_null<const Thread*> thread) const;
[[nodiscard]] NotifySound sound(not_null<const Thread*> thread) const;
[[nodiscard]] bool muteUnknown(not_null<const Thread*> thread) const;
[[nodiscard]] bool soundUnknown(not_null<const Thread*> thread) const;
[[nodiscard]] bool isMuted(not_null<const PeerData*> peer) const;
[[nodiscard]] bool silentPosts(not_null<const PeerData*> peer) const;
[[nodiscard]] NotifySound sound(not_null<const PeerData*> peer) const;
[[nodiscard]] bool muteUnknown(not_null<const PeerData*> peer) const;
[[nodiscard]] bool silentPostsUnknown(
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:
static constexpr auto kDefaultNotifyTypes = 3;
struct DefaultValue {
PeerNotifySettings settings;
rpl::event_stream<> updates;
};
void cacheSound(const std::optional<NotifySound> &sound);
[[nodiscard]] bool isMuted(
not_null<const Thread*> thread,
crl::time *changesIn) const;
[[nodiscard]] bool isMuted(
not_null<const PeerData*> peer,
crl::time *changesIn) const;
[[nodiscard]] DefaultValue &defaultValue(DefaultNotify type);
[[nodiscard]] const DefaultValue &defaultValue(DefaultNotify type) const;
[[nodiscard]] const PeerNotifySettings &defaultSettings(
not_null<const PeerData*> peer) const;
[[nodiscard]] bool settingsUnknown(not_null<const PeerData*> peer) const;
[[nodiscard]] bool settingsUnknown(
not_null<const Thread*> thread) const;
void unmuteByFinished();
void unmuteByFinishedDelayed(crl::time delay);
void updateLocal(not_null<Thread*> thread);
void updateLocal(not_null<PeerData*> peer);
void updateLocal(DefaultNotify type);
void updateException(not_null<PeerData*> peer);
void exceptionsUpdated(DefaultNotify type);
const not_null<Session*> _owner;
DefaultValue _defaultValues[3];
std::unordered_set<not_null<const PeerData*>> _mutedPeers;
std::unordered_map<not_null<ForumTopic*>, rpl::lifetime> _mutedTopics;
base::Timer _unmuteByFinishedTimer;
struct {
base::flat_map<
DocumentId,
std::shared_ptr<DocumentMedia>> views;
std::vector<DocumentId> pendingIds;
rpl::lifetime pendingLifetime;
} _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

View File

@@ -0,0 +1,298 @@
/*
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 "data/notify/data_peer_notify_settings.h"
#include "base/unixtime.h"
namespace Data {
namespace {
[[nodiscard]] MTPinputPeerNotifySettings DefaultSettings() {
return MTP_inputPeerNotifySettings(
MTP_flags(0),
MTPBool(),
MTPBool(),
MTPint(),
MTPNotificationSound(),
MTPBool(),
MTPBool(),
MTPNotificationSound());
}
[[nodiscard]] NotifySound ParseSound(const MTPNotificationSound &sound) {
return sound.match([&](const MTPDnotificationSoundDefault &data) {
return NotifySound();
}, [&](const MTPDnotificationSoundNone &data) {
return NotifySound{ .none = true };
}, [&](const MTPDnotificationSoundLocal &data) {
return NotifySound{
.title = qs(data.vtitle()),
.data = qs(data.vdata()),
};
}, [&](const MTPDnotificationSoundRingtone &data) {
return NotifySound{ .id = data.vid().v };
});
}
[[nodiscard]] MTPNotificationSound SerializeSound(
const std::optional<NotifySound> &sound) {
return !sound
? MTPNotificationSound()
: sound->none
? MTP_notificationSoundNone()
: sound->id
? MTP_notificationSoundRingtone(MTP_long(sound->id))
: !sound->title.isEmpty()
? MTP_notificationSoundLocal(
MTP_string(sound->title),
MTP_string(sound->data))
: MTP_notificationSoundDefault();
}
} // namespace
int MuteValue::until() const {
constexpr auto kMax = std::numeric_limits<int>::max();
return forever
? kMax
: (period > 0)
? int(std::min(int64(base::unixtime::now()) + period, int64(kMax)))
: unmute
? 0
: -1;
}
class NotifyPeerSettingsValue {
public:
NotifyPeerSettingsValue(const MTPDpeerNotifySettings &data);
bool change(const MTPDpeerNotifySettings &data);
bool change(
MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound,
std::optional<bool> storiesMuted);
std::optional<TimeId> muteUntil() const;
std::optional<bool> silentPosts() const;
std::optional<NotifySound> sound() const;
MTPinputPeerNotifySettings serialize() const;
private:
bool change(
std::optional<int> mute,
std::optional<NotifySound> sound,
std::optional<bool> showPreviews,
std::optional<bool> silentPosts,
std::optional<bool> storiesMuted);
std::optional<TimeId> _mute;
std::optional<NotifySound> _sound;
std::optional<bool> _silent;
std::optional<bool> _showPreviews;
std::optional<bool> _storiesMuted;
};
NotifyPeerSettingsValue::NotifyPeerSettingsValue(
const MTPDpeerNotifySettings &data) {
change(data);
}
bool NotifyPeerSettingsValue::change(const MTPDpeerNotifySettings &data) {
const auto mute = data.vmute_until();
const auto sound = data.vother_sound();
const auto showPreviews = data.vshow_previews();
const auto silent = data.vsilent();
const auto storiesMuted = data.vstories_muted();
return change(
mute ? std::make_optional(mute->v) : std::nullopt,
sound ? std::make_optional(ParseSound(*sound)) : std::nullopt,
(showPreviews
? std::make_optional(mtpIsTrue(*showPreviews))
: std::nullopt),
silent ? std::make_optional(mtpIsTrue(*silent)) : std::nullopt,
(storiesMuted
? std::make_optional(mtpIsTrue(*storiesMuted))
: std::nullopt));
}
bool NotifyPeerSettingsValue::change(
MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) {
const auto newMute = muteForSeconds
? base::make_optional(muteForSeconds.until())
: _mute;
const auto newSilentPosts = silentPosts
? base::make_optional(*silentPosts)
: _silent;
const auto newSound = sound
? base::make_optional(*sound)
: _sound;
const auto newStoriesMuted = storiesMuted
? base::make_optional(*storiesMuted)
: _storiesMuted;
return change(
newMute,
newSound,
_showPreviews,
newSilentPosts,
newStoriesMuted);
}
bool NotifyPeerSettingsValue::change(
std::optional<int> mute,
std::optional<NotifySound> sound,
std::optional<bool> showPreviews,
std::optional<bool> silentPosts,
std::optional<bool> storiesMuted) {
if (_mute == mute
&& _sound == sound
&& _showPreviews == showPreviews
&& _silent == silentPosts
&& _storiesMuted == storiesMuted) {
return false;
}
_mute = mute;
_sound = sound;
_showPreviews = showPreviews;
_silent = silentPosts;
_storiesMuted = storiesMuted;
return true;
}
std::optional<TimeId> NotifyPeerSettingsValue::muteUntil() const {
return _mute;
}
std::optional<bool> NotifyPeerSettingsValue::silentPosts() const {
return _silent;
}
std::optional<NotifySound> NotifyPeerSettingsValue::sound() const {
return _sound;
}
MTPinputPeerNotifySettings NotifyPeerSettingsValue::serialize() const {
using Flag = MTPDinputPeerNotifySettings::Flag;
const auto flag = [](auto &&optional, Flag flag) {
return optional.has_value() ? flag : Flag(0);
};
return MTP_inputPeerNotifySettings(
MTP_flags(flag(_mute, Flag::f_mute_until)
| flag(_sound, Flag::f_sound)
| flag(_silent, Flag::f_silent)
| flag(_showPreviews, Flag::f_show_previews)
| flag(_storiesMuted, Flag::f_stories_muted)),
MTP_bool(_showPreviews.value_or(true)),
MTP_bool(_silent.value_or(false)),
MTP_int(_mute.value_or(false)),
SerializeSound(_sound),
MTP_bool(_storiesMuted.value_or(false)),
MTP_bool(false), // stories_hide_sender
SerializeSound(std::nullopt)); // stories_sound
}
PeerNotifySettings::PeerNotifySettings() = default;
bool PeerNotifySettings::change(const MTPPeerNotifySettings &settings) {
auto &data = settings.data();
const auto empty = !data.vflags().v;
if (empty) {
if (!_known || _value) {
_known = true;
_value = nullptr;
return true;
}
return false;
}
if (_value) {
return _value->change(data);
}
_known = true;
_value = std::make_unique<NotifyPeerSettingsValue>(data);
return true;
}
bool PeerNotifySettings::change(
MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound,
std::optional<bool> storiesMuted) {
if (!muteForSeconds && !silentPosts && !sound && !storiesMuted) {
return false;
} else if (_value) {
return _value->change(
muteForSeconds,
silentPosts,
sound,
storiesMuted);
}
using Flag = MTPDpeerNotifySettings::Flag;
const auto flags = (muteForSeconds ? Flag::f_mute_until : Flag(0))
| (silentPosts ? Flag::f_silent : Flag(0))
| (sound ? Flag::f_other_sound : Flag(0))
| (storiesMuted ? Flag::f_stories_muted : Flag(0));
return change(MTP_peerNotifySettings(
MTP_flags(flags),
MTPBool(),
silentPosts ? MTP_bool(*silentPosts) : MTPBool(),
MTP_int(muteForSeconds.until()),
MTPNotificationSound(),
MTPNotificationSound(),
SerializeSound(sound),
storiesMuted ? MTP_bool(*storiesMuted) : MTPBool(),
MTPBool(), // stories_hide_sender
MTPNotificationSound(),
MTPNotificationSound(),
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 {
return _value
? _value->muteUntil()
: std::nullopt;
}
bool PeerNotifySettings::settingsUnknown() const {
return !_known;
}
std::optional<bool> PeerNotifySettings::silentPosts() const {
return _value
? _value->silentPosts()
: std::nullopt;
}
std::optional<NotifySound> PeerNotifySettings::sound() const {
return _value
? _value->sound()
: std::nullopt;
}
MTPinputPeerNotifySettings PeerNotifySettings::serialize() const {
return _value
? _value->serialize()
: DefaultSettings();
}
PeerNotifySettings::~PeerNotifySettings() = default;
} // namespace Data

View File

@@ -0,0 +1,71 @@
/*
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
namespace Data {
class NotifyPeerSettingsValue;
enum class DefaultNotify : uint8_t {
User,
Group,
Broadcast,
};
struct NotifySound {
QString title;
QString data;
DocumentId id = 0;
bool none = false;
};
struct MuteValue {
bool unmute = false;
bool forever = false;
int period = 0;
[[nodiscard]] explicit operator bool() const {
return unmute || forever || period;
}
[[nodiscard]] int until() const;
};
inline bool operator==(const NotifySound &a, const NotifySound &b) {
return (a.id == b.id)
&& (a.none == b.none)
&& (a.title == b.title)
&& (a.data == b.data);
}
class PeerNotifySettings {
public:
PeerNotifySettings();
bool change(const MTPPeerNotifySettings &settings);
bool change(
MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound,
std::optional<bool> storiesMuted);
bool resetToDefault();
bool settingsUnknown() const;
std::optional<TimeId> muteUntil() const;
std::optional<bool> silentPosts() const;
std::optional<NotifySound> sound() const;
MTPinputPeerNotifySettings serialize() const;
~PeerNotifySettings();
private:
bool _known = false;
std::unique_ptr<NotifyPeerSettingsValue> _value;
};
} // namespace Data

View File

@@ -0,0 +1,117 @@
/*
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 "data/notify/data_peer_notify_volume.h"
#include "data/data_peer.h"
#include "data/data_thread.h"
#include "data/notify/data_peer_notify_settings.h"
#include "main/main_session.h"
#include "main/main_session_settings.h"
#include "core/application.h"
#include "window/notifications_manager.h"
#include "settings/settings_common.h"
#include "ui/vertical_list.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h"
#include "styles/style_settings.h"
namespace Data {
Data::VolumeController DefaultRingtonesVolumeController(
not_null<Main::Session*> session,
Data::DefaultNotify defaultNotify) {
return Data::VolumeController{
.volume = [=]() -> ushort {
const auto volume = session->settings().ringtoneVolume(
defaultNotify);
return volume ? volume : 100;
},
.saveVolume = [=](ushort volume) {
session->settings().setRingtoneVolume(defaultNotify, volume);
session->saveSettingsDelayed();
}};
}
Data::VolumeController ThreadRingtonesVolumeController(
not_null<Data::Thread*> thread) {
return Data::VolumeController{
.volume = [=]() -> ushort {
const auto volume = thread->session().settings().ringtoneVolume(
thread->peer()->id,
thread->topicRootId(),
thread->monoforumPeerId());
return volume ? volume : 100;
},
.saveVolume = [=](ushort volume) {
thread->session().settings().setRingtoneVolume(
thread->peer()->id,
thread->topicRootId(),
thread->monoforumPeerId(),
volume);
thread->session().saveSettingsDelayed();
}};
}
} // namespace Data
namespace Ui {
void AddRingtonesVolumeSlider(
not_null<Ui::VerticalLayout*> container,
rpl::producer<bool> toggleOn,
rpl::producer<QString> subtitle,
Data::VolumeController volumeController) {
Expects(volumeController.volume && volumeController.saveVolume);
const auto volumeWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
volumeWrap->toggleOn(
rpl::combine(
Core::App().notifications().volumeSupportedValue(),
std::move(toggleOn)
) | rpl::map(
rpl::mappers::_1 && rpl::mappers::_2
) | rpl::distinct_until_changed(),
anim::type::normal);
volumeWrap->finishAnimating();
Ui::AddSubsectionTitle(volumeWrap->entity(), std::move(subtitle));
auto sliderWithLabel = Settings::MakeSliderWithLabel(
volumeWrap->entity(),
st::settingsScale,
st::settingsScaleLabel,
st::normalFont->spacew * 2,
st::settingsScaleLabel.style.font->width("100%"),
true);
const auto slider = sliderWithLabel.slider;
const auto label = sliderWithLabel.label;
volumeWrap->entity()->add(
std::move(sliderWithLabel.widget),
st::settingsBigScalePadding);
const auto updateLabel = [=](int volume) {
label->setText(QString::number(volume) + '%');
};
slider->setPseudoDiscrete(
100,
[=](int index) { return index + 1; },
int(volumeController.volume()),
updateLabel,
[saveVolume = volumeController.saveVolume](int volume) {
saveVolume(volume);
});
updateLabel(volumeController.volume());
}
} // namespace Ui

View File

@@ -0,0 +1,45 @@
/*
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
namespace Main {
class Session;
} // namespace Main
namespace Ui {
class VerticalLayout;
} // namespace Ui
namespace Data {
enum class DefaultNotify : uint8_t;
class Thread;
struct VolumeController {
Fn<ushort()> volume = nullptr;
Fn<void(ushort)> saveVolume = nullptr;
};
[[nodiscard]] VolumeController DefaultRingtonesVolumeController(
not_null<Main::Session*> session,
Data::DefaultNotify defaultNotify);
[[nodiscard]] VolumeController ThreadRingtonesVolumeController(
not_null<Data::Thread*> thread);
} // namespace Data
namespace Ui {
void AddRingtonesVolumeSlider(
not_null<Ui::VerticalLayout*> container,
rpl::producer<bool> toggleOn,
rpl::producer<QString> subtitle,
Data::VolumeController volumeController);
} // namespace Ui