Support per-topic notification settings.

This commit is contained in:
John Preston 2022-10-13 00:23:14 +04:00
parent 24843e3acd
commit 92a4b27e65
31 changed files with 713 additions and 293 deletions

View File

@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_photo.h"
#include "data/data_web_page.h"
#include "data/data_folder.h"
#include "data/data_forum_topic.h"
#include "data/data_media_types.h"
#include "data/data_sparse_ids.h"
#include "data/data_search_controller.h"
@ -150,7 +151,7 @@ ApiWrap::ApiWrap(not_null<Main::Session*> session)
, _dialogsLoadState(std::make_unique<DialogsLoadState>())
, _fileLoader(std::make_unique<TaskQueue>(kFileLoaderQueueStopTimeout))
, _topPromotionTimer([=] { refreshTopPromotion(); })
, _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); })
, _updateNotifyTimer([=] { sendNotifySettingsUpdates(); })
, _authorizations(std::make_unique<Api::Authorizations>(this))
, _attachedStickers(std::make_unique<Api::AttachedStickers>(this))
, _blockedPeers(std::make_unique<Api::BlockedPeers>(this))
@ -1323,27 +1324,25 @@ void ApiWrap::deleteAllFromParticipantSend(
void ApiWrap::scheduleStickerSetRequest(uint64 setId, uint64 access) {
if (!_stickerSetRequests.contains(setId)) {
_stickerSetRequests.insert(setId, qMakePair(access, 0));
_stickerSetRequests.emplace(setId, StickerSetRequest{ access });
}
}
void ApiWrap::requestStickerSets() {
for (auto i = _stickerSetRequests.begin(), j = i, e = _stickerSetRequests.end(); i != e; i = j) {
++j;
if (i.value().second) continue;
auto waitMs = (j == e) ? 0 : kSmallDelayMs;
const auto id = MTP_inputStickerSetID(
MTP_long(i.key()),
MTP_long(i.value().first));
i.value().second = request(MTPmessages_GetStickerSet(
id,
for (auto &[id, info] : _stickerSetRequests) {
if (info.id) {
continue;
}
info.id = request(MTPmessages_GetStickerSet(
MTP_inputStickerSetID(
MTP_long(id),
MTP_long(info.accessHash)),
MTP_int(0) // hash
)).done([=, setId = i.key()](const MTPmessages_StickerSet &result) {
)).done([=, setId = id](const MTPmessages_StickerSet &result) {
gotStickerSet(setId, result);
}).fail([=, setId = i.key()] {
}).fail([=, setId = id] {
_stickerSetRequests.remove(setId);
}).afterDelay(waitMs).send();
}).afterDelay(kSmallDelayMs).send();
}
}
@ -1671,7 +1670,7 @@ void ApiWrap::joinChannel(not_null<ChannelData*> channel) {
_channelAmInRequests.remove(channel);
}).send();
_channelAmInRequests.insert(channel, requestId);
_channelAmInRequests.emplace(channel, requestId);
}
}
@ -1690,44 +1689,48 @@ void ApiWrap::leaveChannel(not_null<ChannelData*> channel) {
_channelAmInRequests.remove(channel);
}).send();
_channelAmInRequests.insert(channel, requestId);
_channelAmInRequests.emplace(channel, requestId);
}
}
void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
const auto key = [&] {
switch (peer.type()) {
case mtpc_inputNotifyUsers: return peerFromUser(0);
case mtpc_inputNotifyChats: return peerFromChat(0);
case mtpc_inputNotifyBroadcasts: return peerFromChannel(0);
case mtpc_inputNotifyPeer: {
const auto &inner = peer.c_inputNotifyPeer().vpeer();
switch (inner.type()) {
case mtpc_inputPeerSelf:
return _session->userPeerId();
case mtpc_inputPeerEmpty:
return PeerId(0);
case mtpc_inputPeerChannel:
return peerFromChannel(
inner.c_inputPeerChannel().vchannel_id());
case mtpc_inputPeerChat:
return peerFromChat(inner.c_inputPeerChat().vchat_id());
case mtpc_inputPeerUser:
return peerFromUser(inner.c_inputPeerUser().vuser_id());
}
const auto peerFromInput = [&](const MTPInputPeer &inputPeer) {
return inputPeer.match([&](const MTPDinputPeerSelf &) {
return _session->userPeerId();
}, [](const MTPDinputPeerEmpty &) {
return PeerId(0);
}, [](const MTPDinputPeerChannel &data) {
return peerFromChannel(data.vchannel_id());
}, [](const MTPDinputPeerChat &data) {
return peerFromChat(data.vchat_id());
}, [](const MTPDinputPeerUser &data) {
return peerFromUser(data.vuser_id());
}, [](const auto &) -> PeerId {
Unexpected("Type in ApiRequest::requestNotifySettings peer.");
} break;
}
Unexpected("Type in ApiRequest::requestNotifySettings.");
}();
if (_notifySettingRequests.find(key) != end(_notifySettingRequests)) {
});
};
const auto key = peer.match([](const MTPDinputNotifyUsers &) {
return NotifySettingsKey{ peerFromUser(1) };
}, [](const MTPDinputNotifyChats &) {
return NotifySettingsKey{ peerFromChat(1) };
}, [](const MTPDinputNotifyBroadcasts &) {
return NotifySettingsKey{ peerFromChannel(1) };
}, [&](const MTPDinputNotifyPeer &data) {
return NotifySettingsKey{ peerFromInput(data.vpeer()) };
}, [&](const MTPDinputNotifyForumTopic &data) {
return NotifySettingsKey{
peerFromInput(data.vpeer()),
data.vtop_msg_id().v,
};
});
if (_notifySettingRequests.contains(key)) {
return;
}
const auto requestId = request(MTPaccount_GetNotifySettings(
peer
)).done([=](const MTPPeerNotifySettings &result) {
applyNotifySettings(peer, result);
_notifySettingRequests.erase(key);
_notifySettingRequests.remove(key);
}).fail([=] {
applyNotifySettings(
peer,
@ -1741,34 +1744,50 @@ void ApiWrap::requestNotifySettings(const MTPInputNotifyPeer &peer) {
MTPNotificationSound()));
_notifySettingRequests.erase(key);
}).send();
_notifySettingRequests.emplace(key, requestId);
}
void ApiWrap::updateNotifySettingsDelayed(not_null<const PeerData*> peer) {
_updateNotifySettingsPeers.emplace(peer);
_updateNotifySettingsTimer.callOnce(kNotifySettingSaveTimeout);
void ApiWrap::updateNotifySettingsDelayed(
not_null<const Data::ForumTopic*> topic) {
if (_updateNotifyTopics.emplace(topic).second) {
topic->destroyed(
) | rpl::start_with_next([=] {
_updateNotifyTopics.remove(topic);
}, _updateNotifyQueueLifetime);
_updateNotifyTimer.callOnce(kNotifySettingSaveTimeout);
}
}
void ApiWrap::updateDefaultNotifySettingsDelayed(Data::DefaultNotify type) {
_updateNotifySettingsDefaults.emplace(type);
_updateNotifySettingsTimer.callOnce(kNotifySettingSaveTimeout);
void ApiWrap::updateNotifySettingsDelayed(not_null<const PeerData*> peer) {
if (_updateNotifyPeers.emplace(peer).second) {
_updateNotifyTimer.callOnce(kNotifySettingSaveTimeout);
}
}
void ApiWrap::updateNotifySettingsDelayed(Data::DefaultNotify type) {
if (_updateNotifyDefaults.emplace(type).second) {
_updateNotifyTimer.callOnce(kNotifySettingSaveTimeout);
}
}
void ApiWrap::sendNotifySettingsUpdates() {
while (!_updateNotifySettingsPeers.empty()) {
const auto peer = *_updateNotifySettingsPeers.begin();
_updateNotifySettingsPeers.erase(_updateNotifySettingsPeers.begin());
_updateNotifyQueueLifetime.destroy();
for (const auto topic : base::take(_updateNotifyTopics)) {
request(MTPaccount_UpdateNotifySettings(
MTP_inputNotifyForumTopic(
topic->channel()->input,
MTP_int(topic->rootId())),
topic->notify().serialize()
)).afterDelay(kSmallDelayMs).send();
}
for (const auto peer : base::take(_updateNotifyPeers)) {
request(MTPaccount_UpdateNotifySettings(
MTP_inputNotifyPeer(peer->input),
peer->notifySerialize()
)).afterDelay(_updateNotifySettingsPeers.empty() ? 0 : 10).send();
peer->notify().serialize()
)).afterDelay(kSmallDelayMs).send();
}
const auto &settings = session().data().notifySettings();
while (!_updateNotifySettingsDefaults.empty()) {
const auto type = *_updateNotifySettingsDefaults.begin();
_updateNotifySettingsDefaults.erase(
_updateNotifySettingsDefaults.begin());
for (const auto type : base::take(_updateNotifyDefaults)) {
const auto input = [&] {
switch (type) {
case Data::DefaultNotify::User: return MTP_inputNotifyUsers();
@ -1781,8 +1800,9 @@ void ApiWrap::sendNotifySettingsUpdates() {
request(MTPaccount_UpdateNotifySettings(
input,
settings.defaultSettings(type).serialize()
)).afterDelay(_updateNotifySettingsDefaults.empty() ? 0 : 10).send();
)).afterDelay(kSmallDelayMs).send();
}
session().mtp().sendAnything();
}
void ApiWrap::saveDraftToCloudDelayed(not_null<History*> history) {
@ -2147,7 +2167,9 @@ void ApiWrap::applyNotifySettings(
Core::App().notifications().checkDelayed();
}
void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result) {
void ApiWrap::gotStickerSet(
uint64 setId,
const MTPmessages_StickerSet &result) {
_stickerSetRequests.remove(setId);
result.match([&](const MTPDmessages_stickerSet &data) {
_session->data().stickers().feedSetFull(data);
@ -2156,18 +2178,20 @@ void ApiWrap::gotStickerSet(uint64 setId, const MTPmessages_StickerSet &result)
});
}
void ApiWrap::requestWebPageDelayed(WebPageData *page) {
if (page->pendingTill <= 0) return;
_webPagesPending.insert(page, 0);
void ApiWrap::requestWebPageDelayed(not_null<WebPageData*> page) {
if (page->pendingTill <= 0) {
return;
}
_webPagesPending.emplace(page, 0);
auto left = (page->pendingTill - base::unixtime::now()) * 1000;
if (!_webPagesTimer.isActive() || left <= _webPagesTimer.remainingTime()) {
_webPagesTimer.callOnce((left < 0 ? 0 : left) + 1);
}
}
void ApiWrap::clearWebPageRequest(WebPageData *page) {
void ApiWrap::clearWebPageRequest(not_null<WebPageData*> page) {
_webPagesPending.remove(page);
if (_webPagesPending.isEmpty() && _webPagesTimer.isActive()) {
if (_webPagesPending.empty() && _webPagesTimer.isActive()) {
_webPagesTimer.cancel();
}
}
@ -2185,11 +2209,12 @@ void ApiWrap::resolveWebPages() {
ids.reserve(_webPagesPending.size());
int32 t = base::unixtime::now(), m = INT_MAX;
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend(); ++i) {
if (i.value() > 0) continue;
if (i.key()->pendingTill <= t) {
const auto item = _session->data().findWebPageItem(i.key());
if (item) {
for (auto &[page, requestId] : _webPagesPending) {
if (requestId > 0) {
continue;
}
if (page->pendingTill <= t) {
if (const auto item = _session->data().findWebPageItem(page)) {
if (const auto channel = item->history()->peer->asChannel()) {
auto channelMap = idsByChannel.find(channel);
if (channelMap == idsByChannel.cend()) {
@ -2204,14 +2229,14 @@ void ApiWrap::resolveWebPages() {
channelMap->second.second.push_back(
MTP_inputMessageID(MTP_int(item->id)));
}
i.value() = -channelMap->second.first - 2;
requestId = -channelMap->second.first - 2;
} else {
ids.push_back(MTP_inputMessageID(MTP_int(item->id)));
i.value() = -1;
requestId = -1;
}
}
} else {
m = qMin(m, i.key()->pendingTill - t);
m = std::min(m, page->pendingTill - t);
}
}
@ -2237,9 +2262,10 @@ void ApiWrap::resolveWebPages() {
}).afterDelay(kSmallDelayMs).send();
}
if (requestId || !reqsByIndex.isEmpty()) {
for (auto &pendingRequestId : _webPagesPending) {
if (pendingRequestId > 0) continue;
if (pendingRequestId < 0) {
for (auto &[page, pendingRequestId] : _webPagesPending) {
if (pendingRequestId > 0) {
continue;
} else if (pendingRequestId < 0) {
if (pendingRequestId == -1) {
pendingRequestId = requestId;
} else {
@ -2248,9 +2274,8 @@ void ApiWrap::resolveWebPages() {
}
}
}
if (m < INT_MAX) {
_webPagesTimer.callOnce(m * 1000);
_webPagesTimer.callOnce(std::min(m, 86400) * crl::time(1000));
}
}
@ -2441,10 +2466,10 @@ void ApiWrap::refreshFileReference(
void ApiWrap::gotWebPages(ChannelData *channel, const MTPmessages_Messages &result, mtpRequestId req) {
WebPageData::ApplyChanges(_session, channel, result);
for (auto i = _webPagesPending.begin(); i != _webPagesPending.cend();) {
if (i.value() == req) {
if (i.key()->pendingTill > 0) {
i.key()->pendingTill = -1;
_session->data().notifyWebPageUpdateDelayed(i.key());
if (i->second == req) {
if (i->first->pendingTill > 0) {
i->first->pendingTill = -1;
_session->data().notifyWebPageUpdateDelayed(i->first);
}
i = _webPagesPending.erase(i);
} else {

View File

@ -32,6 +32,7 @@ class WallPaper;
struct ResolvedForwardDraft;
enum class DefaultNotify;
enum class StickersType : uchar;
class ForumTopic;
} // namespace Data
namespace InlineBots {
@ -219,8 +220,8 @@ public:
not_null<ChannelData*> channel,
not_null<PeerData*> from);
void requestWebPageDelayed(WebPageData *page);
void clearWebPageRequest(WebPageData *page);
void requestWebPageDelayed(not_null<WebPageData*> page);
void clearWebPageRequest(not_null<WebPageData*> page);
void clearWebPageRequests();
void scheduleStickerSetRequest(uint64 setId, uint64 access);
@ -244,8 +245,10 @@ public:
void leaveChannel(not_null<ChannelData*> channel);
void requestNotifySettings(const MTPInputNotifyPeer &peer);
void updateNotifySettingsDelayed(
not_null<const Data::ForumTopic*> topic);
void updateNotifySettingsDelayed(not_null<const PeerData*> peer);
void updateDefaultNotifySettingsDelayed(Data::DefaultNotify type);
void updateNotifySettingsDelayed(Data::DefaultNotify type);
void saveDraftToCloudDelayed(not_null<History*> history);
static int OnlineTillFromStatus(
@ -523,7 +526,7 @@ private:
not_null<ChannelData*> channel);
void migrateFail(not_null<PeerData*> peer, const QString &error);
not_null<Main::Session*> _session;
const not_null<Main::Session*> _session;
base::flat_map<QString, int> _modifyRequests;
@ -541,13 +544,29 @@ private:
not_null<History*>,
std::pair<mtpRequestId,Fn<void()>>> _historyArchivedRequests;
QMap<WebPageData*, mtpRequestId> _webPagesPending;
base::flat_map<not_null<WebPageData*>, mtpRequestId> _webPagesPending;
base::Timer _webPagesTimer;
QMap<uint64, QPair<uint64, mtpRequestId> > _stickerSetRequests;
struct StickerSetRequest {
uint64 accessHash = 0;
mtpRequestId id = 0;
};
base::flat_map<uint64, StickerSetRequest> _stickerSetRequests;
base::flat_map<
not_null<ChannelData*>,
mtpRequestId> _channelAmInRequests;
struct NotifySettingsKey {
PeerId peerId = 0;
MsgId topicRootId = 0;
friend inline constexpr auto operator<=>(
NotifySettingsKey,
NotifySettingsKey) = default;
};
base::flat_map<NotifySettingsKey, mtpRequestId> _notifySettingRequests;
QMap<ChannelData*, mtpRequestId> _channelAmInRequests;
base::flat_map<PeerId, mtpRequestId> _notifySettingRequests;
base::flat_map<not_null<History*>, mtpRequestId> _draftsSaveRequestIds;
base::Timer _draftsSaveTimer;
@ -610,9 +629,11 @@ private:
TimeId _topPromotionNextRequestTime = TimeId(0);
base::Timer _topPromotionTimer;
base::flat_set<not_null<const PeerData*>> _updateNotifySettingsPeers;
base::flat_set<Data::DefaultNotify> _updateNotifySettingsDefaults;
base::Timer _updateNotifySettingsTimer;
base::flat_set<not_null<const Data::ForumTopic*>> _updateNotifyTopics;
base::flat_set<not_null<const PeerData*>> _updateNotifyPeers;
base::flat_set<Data::DefaultNotify> _updateNotifyDefaults;
base::Timer _updateNotifyTimer;
rpl::lifetime _updateNotifyQueueLifetime;
std::map<
Data::FileOrigin,

View File

@ -148,8 +148,9 @@ struct TopicUpdate {
UnreadView = (1U << 1),
UnreadMentions = (1U << 2),
UnreadReactions = (1U << 3),
Notifications = (1U << 4),
LastUsedBit = (1U << 3),
LastUsedBit = (1U << 4),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View File

@ -207,7 +207,7 @@ bool ChatFilter::contains(not_null<History*> history) const {
return false
|| ((_flags & flag)
&& (!(_flags & Flag::NoMuted)
|| !history->mute()
|| !history->muted()
|| (history->unreadMentions().has()
&& history->folderKnown()
&& !history->folder()))

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum.h"
#include "data/data_histories.h"
#include "data/data_replies_list.h"
#include "data/notify/data_notify_settings.h"
#include "data/data_session.h"
#include "data/stickers/data_custom_emoji.h"
#include "dialogs/dialogs_main_list.h"
@ -141,7 +142,8 @@ ForumTopic::ForumTopic(not_null<History*> history, MsgId rootId)
, _forum(history->peer->forum())
, _list(_forum->topicsList())
, _replies(std::make_shared<RepliesList>(history, rootId))
, _rootId(rootId) {
, _rootId(rootId)
, _flags(owner().notifySettings().isMuted(this) ? Flag::Muted : Flag(0)) {
_replies->unreadCountValue(
) | rpl::combine_previous(
) | rpl::filter([=] {
@ -487,6 +489,32 @@ int ForumTopic::unreadCountForBadge() const {
return (!result && unreadMark()) ? 1 : result;
}
bool ForumTopic::muted() const {
return (_flags & Flag::Muted);
}
bool ForumTopic::changeMuted(bool muted) {
if (this->muted() == muted) {
return false;
}
const auto refresher = gsl::finally([&] {
if (inChatList()) {
updateChatListEntry();
}
session().changes().topicUpdated(
this,
Data::TopicUpdate::Flag::Notifications);
});
const auto notify = (unreadCountForBadge() > 0);
const auto notifier = unreadStateChangeNotifier(notify);
if (muted) {
_flags |= Flag::Muted;
} else {
_flags &= ~Flag::Muted;
}
return true;
}
bool ForumTopic::unreadCountKnown() const {
return _replies->unreadCountKnown();
}
@ -531,7 +559,7 @@ Dialogs::UnreadState ForumTopic::unreadStateFor(
bool known) const {
auto result = Dialogs::UnreadState();
const auto mark = !count && unreadMark();
const auto muted = history()->mute();
const auto muted = this->muted();
result.messages = count;
result.messagesMuted = muted ? count : 0;
result.chats = count ? 1 : 0;
@ -547,7 +575,7 @@ bool ForumTopic::chatListUnreadMark() const {
}
bool ForumTopic::chatListMutedBadge() const {
return history()->mute();
return muted();
}
HistoryItem *ForumTopic::chatListMessage() const {

View File

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_entry.h"
#include "dialogs/ui/dialogs_message_view.h"
#include "data/notify/data_peer_notify_settings.h"
#include "base/flags.h"
class ChannelData;
@ -95,6 +96,13 @@ public:
void applyItemAdded(not_null<HistoryItem*> item);
void applyItemRemoved(MsgId id);
[[nodiscard]] Data::PeerNotifySettings &notify() {
return _notify;
}
[[nodiscard]] const Data::PeerNotifySettings &notify() const {
return _notify;
}
void loadUserpic() override;
void paintUserpic(
Painter &p,
@ -105,6 +113,8 @@ public:
[[nodiscard]] bool unreadCountKnown() const;
[[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
[[nodiscard]] bool muted() const;
bool changeMuted(bool muted);
void setUnreadMark(bool unread);
[[nodiscard]] bool unreadMark() const;
@ -114,7 +124,8 @@ public:
private:
enum class Flag : uchar {
UnreadMark = 0x01,
UnreadMark = (1 << 0),
Muted = (1 << 1),
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
@ -137,6 +148,8 @@ private:
std::shared_ptr<RepliesList> _replies;
MsgId _rootId = 0;
Data::PeerNotifySettings _notify;
QString _title;
DocumentId _iconId = 0;
base::flat_set<QString> _titleWords;
@ -151,7 +164,7 @@ private:
std::optional<HistoryItem*> _lastServerMessage;
std::optional<HistoryItem*> _chatListMessage;
base::flat_set<FullMsgId> _requestedGroups;
base::flags<Flag> _flags;
base::flags<Flag> _flags; // Initializer accesses _notify, be careful.
rpl::lifetime _lifetime;

View File

@ -202,29 +202,11 @@ public:
not_null<const HistoryItem*> item) const;
[[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const;
[[nodiscard]] std::optional<TimeId> notifyMuteUntil() const {
return _notify.muteUntil();
[[nodiscard]] Data::PeerNotifySettings &notify() {
return _notify;
}
bool notifyChange(const MTPPeerNotifySettings &settings) {
return _notify.change(settings);
}
bool notifyChange(
Data::MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<Data::NotifySound> sound) {
return _notify.change(muteForSeconds, silentPosts, sound);
}
[[nodiscard]] bool notifySettingsUnknown() const {
return _notify.settingsUnknown();
}
[[nodiscard]] std::optional<bool> notifySilentPosts() const {
return _notify.silentPosts();
}
[[nodiscard]] std::optional<Data::NotifySound> notifySound() const {
return _notify.sound();
}
[[nodiscard]] MTPinputPeerNotifySettings notifySerialize() const {
return _notify.serialize();
[[nodiscard]] const Data::PeerNotifySettings &notify() const {
return _notify;
}
[[nodiscard]] bool canWrite() const;

View File

@ -3892,7 +3892,9 @@ void Session::removeChatListEntry(Dialogs::Key key) {
_contactsNoChatsList.addByName(key);
}
}
if (const auto history = key.history()) {
if (const auto topic = key.topic()) {
Core::App().notifications().clearFromTopic(topic);
} else if (const auto history = key.history()) {
Core::App().notifications().clearFromHistory(history);
}
}

View File

@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_document.h"
#include "data/data_file_origin.h"
#include "data/data_peer.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h"
#include "data/data_user.h"
#include "main/main_session.h"
@ -25,11 +26,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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);
}
} // namespace
NotifySettings::NotifySettings(not_null<Session*> owner)
@ -38,7 +49,7 @@ NotifySettings::NotifySettings(not_null<Session*> owner)
}
void NotifySettings::request(not_null<PeerData*> peer) {
if (peer->notifySettingsUnknown()) {
if (peer->notify().settingsUnknown()) {
peer->session().api().requestNotifySettings(
MTP_inputNotifyPeer(peer->input));
}
@ -51,6 +62,16 @@ void NotifySettings::request(not_null<PeerData*> peer) {
}
}
void NotifySettings::request(not_null<Data::ForumTopic*> topic) {
if (topic->notify().settingsUnknown()) {
topic->session().api().requestNotifySettings(
MTP_inputNotifyForumTopic(
topic->channel()->input,
MTP_int(topic->rootId())));
}
request(topic->channel());
}
void NotifySettings::apply(
const MTPNotifyPeer &notifyPeer,
const MTPPeerNotifySettings &settings) {
@ -66,7 +87,7 @@ void NotifySettings::apply(
case mtpc_notifyPeer: {
const auto &data = notifyPeer.c_notifyPeer();
if (const auto peer = _owner->peerLoaded(peerFromMTP(data.vpeer()))) {
if (peer->notifyChange(settings)) {
if (peer->notify().change(settings)) {
updateLocal(peer);
}
}
@ -74,12 +95,37 @@ void NotifySettings::apply(
}
}
void NotifySettings::update(
not_null<Data::ForumTopic*> topic,
Data::MuteValue muteForSeconds,
std::optional<NotifySound> sound) {
if (topic->notify().change(muteForSeconds, std::nullopt, sound)) {
updateLocal(topic);
topic->session().api().updateNotifySettingsDelayed(topic);
}
}
void NotifySettings::resetToDefault(not_null<Data::ForumTopic*> topic) {
const auto empty = MTP_peerNotifySettings(
MTP_flags(0),
MTPBool(),
MTPBool(),
MTPint(),
MTPNotificationSound(),
MTPNotificationSound(),
MTPNotificationSound());
if (topic->notify().change(empty)) {
updateLocal(topic);
topic->session().api().updateNotifySettingsDelayed(topic);
}
}
void NotifySettings::update(
not_null<PeerData*> peer,
Data::MuteValue muteForSeconds,
std::optional<bool> silentPosts,
std::optional<NotifySound> sound) {
if (peer->notifyChange(muteForSeconds, silentPosts, sound)) {
if (peer->notify().change(muteForSeconds, silentPosts, sound)) {
updateLocal(peer);
peer->session().api().updateNotifySettingsDelayed(peer);
}
@ -94,7 +140,7 @@ void NotifySettings::resetToDefault(not_null<PeerData*> peer) {
MTPNotificationSound(),
MTPNotificationSound(),
MTPNotificationSound());
if (peer->notifyChange(empty)) {
if (peer->notify().change(empty)) {
updateLocal(peer);
peer->session().api().updateNotifySettingsDelayed(peer);
}
@ -136,15 +182,34 @@ void NotifySettings::defaultUpdate(
auto &settings = defaultValue(type).settings;
if (settings.change(muteForSeconds, silentPosts, sound)) {
updateLocal(type);
_owner->session().api().updateDefaultNotifySettingsDelayed(type);
_owner->session().api().updateNotifySettingsDelayed(type);
}
}
void NotifySettings::updateLocal(not_null<Data::ForumTopic*> topic) {
auto changesIn = crl::time(0);
const auto muted = isMuted(topic, &changesIn);
topic->changeMuted(muted);
if (muted) {
auto &lifetime = _mutedTopics.emplace(
topic,
rpl::lifetime()).first->second;
topic->destroyed() | rpl::start_with_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);
if (history && history->changeMute(muted)) {
if (history && history->changeMuted(muted)) {
// Notification already sent.
} else {
peer->session().changes().peerUpdated(
@ -161,7 +226,7 @@ void NotifySettings::updateLocal(not_null<PeerData*> peer) {
} else {
_mutedPeers.erase(peer);
}
cacheSound(peer->notifySound());
cacheSound(peer->notify().sound());
}
void NotifySettings::cacheSound(DocumentId id) {
@ -206,10 +271,11 @@ void NotifySettings::updateLocal(DefaultNotify type) {
const auto goodForUpdate = [&](
not_null<const PeerData*> peer,
const PeerNotifySettings &settings) {
return !peer->notifySettingsUnknown()
&& ((!peer->notifyMuteUntil() && settings.muteUntil())
|| (!peer->notifySilentPosts() && settings.silentPosts())
|| (!peer->notifySound() && settings.sound()));
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) {
@ -252,7 +318,7 @@ void NotifySettings::unmuteByFinished() {
const auto muted = isMuted(*i, &changesIn);
if (muted) {
if (history) {
history->changeMute(true);
history->changeMuted(true);
}
if (!changesInMin || changesInMin > changesIn) {
changesInMin = changesIn;
@ -260,35 +326,71 @@ void NotifySettings::unmuteByFinished() {
++i;
} else {
if (history) {
history->changeMute(false);
history->changeMuted(false);
}
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);
if (muted) {
topic->changeMuted(true);
if (!changesInMin || changesInMin > changesIn) {
changesInMin = changesIn;
}
++i;
} else {
topic->changeMuted(false);
i = _mutedTopics.erase(i);
}
}
if (changesInMin) {
unmuteByFinishedDelayed(changesInMin);
}
}
bool NotifySettings::isMuted(
not_null<const Data::ForumTopic*> topic,
crl::time *changesIn) const {
const auto until = topic->notify().muteUntil();
return until
? MutedFromUntil(*until, changesIn)
: isMuted(topic->channel(), changesIn);
}
bool NotifySettings::isMuted(not_null<const Data::ForumTopic*> topic) const {
return isMuted(topic, nullptr);
}
NotifySound NotifySettings::sound(
not_null<const Data::ForumTopic*> topic) const {
const auto sound = topic->notify().sound();
return sound ? *sound : this->sound(topic->channel());
}
bool NotifySettings::muteUnknown(
not_null<const Data::ForumTopic*> topic) const {
return topic->notify().settingsUnknown()
|| (!topic->notify().muteUntil().has_value()
&& muteUnknown(topic->channel()));
}
bool NotifySettings::soundUnknown(
not_null<const Data::ForumTopic*> topic) const {
return topic->notify().settingsUnknown()
|| (!topic->notify().sound().has_value()
&& soundUnknown(topic->channel()));
}
bool NotifySettings::isMuted(
not_null<const PeerData*> peer,
crl::time *changesIn) const {
const auto resultFromUntil = [&](TimeId until) {
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);
};
if (const auto until = peer->notifyMuteUntil()) {
return resultFromUntil(*until);
}
const auto &settings = defaultSettings(peer);
if (const auto until = settings.muteUntil()) {
return resultFromUntil(*until);
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;
}
@ -298,54 +400,41 @@ bool NotifySettings::isMuted(not_null<const PeerData*> peer) const {
}
bool NotifySettings::silentPosts(not_null<const PeerData*> peer) const {
if (const auto silent = peer->notifySilentPosts()) {
if (const auto silent = peer->notify().silentPosts()) {
return *silent;
}
const auto &settings = defaultSettings(peer);
if (const auto silent = settings.silentPosts()) {
} else if (const auto silent = defaultSettings(peer).silentPosts()) {
return *silent;
}
return false;
}
NotifySound NotifySettings::sound(not_null<const PeerData*> peer) const {
if (const auto sound = peer->notifySound()) {
if (const auto sound = peer->notify().sound()) {
return *sound;
}
const auto &settings = defaultSettings(peer);
if (const auto sound = settings.sound()) {
} else if (const auto sound = defaultSettings(peer).sound()) {
return *sound;
}
return {};
}
bool NotifySettings::muteUnknown(not_null<const PeerData*> peer) const {
if (peer->notifySettingsUnknown()) {
return true;
} else if (const auto nonDefault = peer->notifyMuteUntil()) {
return false;
}
return defaultSettings(peer).settingsUnknown();
return peer->notify().settingsUnknown()
|| (!peer->notify().muteUntil().has_value()
&& defaultSettings(peer).settingsUnknown());
}
bool NotifySettings::silentPostsUnknown(
not_null<const PeerData*> peer) const {
if (peer->notifySettingsUnknown()) {
return true;
} else if (const auto nonDefault = peer->notifySilentPosts()) {
return false;
}
return defaultSettings(peer).settingsUnknown();
return peer->notify().settingsUnknown()
|| (!peer->notify().silentPosts().has_value()
&& defaultSettings(peer).settingsUnknown());
}
bool NotifySettings::soundUnknown(
not_null<const PeerData*> peer) const {
if (peer->notifySettingsUnknown()) {
return true;
} else if (const auto nonDefault = peer->notifySound()) {
return false;
}
return defaultSettings(peer).settingsUnknown();
return peer->notify().settingsUnknown()
|| (!peer->notify().sound().has_value()
&& defaultSettings(peer).settingsUnknown());
}
bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
@ -354,6 +443,11 @@ bool NotifySettings::settingsUnknown(not_null<const PeerData*> peer) const {
|| soundUnknown(peer);
}
bool NotifySettings::settingsUnknown(
not_null<const Data::ForumTopic*> topic) const {
return muteUnknown(topic) || soundUnknown(topic);
}
rpl::producer<> NotifySettings::defaultUpdates(DefaultNotify type) const {
return defaultValue(type).updates.events();
}

View File

@ -17,6 +17,7 @@ namespace Data {
class DocumentMedia;
class Session;
class ForumTopic;
enum class DefaultNotify {
User,
@ -29,9 +30,15 @@ public:
NotifySettings(not_null<Session*> owner);
void request(not_null<PeerData*> peer);
void request(not_null<Data::ForumTopic*> topic);
void apply(
const MTPNotifyPeer &notifyPeer,
const MTPPeerNotifySettings &settings);
void update(
not_null<Data::ForumTopic*> topic,
Data::MuteValue muteForSeconds,
std::optional<NotifySound> sound = std::nullopt);
void resetToDefault(not_null<Data::ForumTopic*> topic);
void update(
not_null<PeerData*> peer,
Data::MuteValue muteForSeconds,
@ -57,6 +64,15 @@ public:
std::optional<bool> silentPosts = std::nullopt,
std::optional<NotifySound> sound = std::nullopt);
[[nodiscard]] bool isMuted(
not_null<const Data::ForumTopic*> topic) const;
[[nodiscard]] NotifySound sound(
not_null<const Data::ForumTopic*> topic) const;
[[nodiscard]] bool muteUnknown(
not_null<const Data::ForumTopic*> topic) const;
[[nodiscard]] bool soundUnknown(
not_null<const Data::ForumTopic*> topic) 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;
@ -73,6 +89,9 @@ private:
void cacheSound(const std::optional<NotifySound> &sound);
[[nodiscard]] bool isMuted(
not_null<const Data::ForumTopic*> peer,
crl::time *changesIn) const;
[[nodiscard]] bool isMuted(
not_null<const PeerData*> peer,
crl::time *changesIn) const;
@ -82,9 +101,12 @@ private:
[[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 Data::ForumTopic*> topic) const;
void unmuteByFinished();
void unmuteByFinishedDelayed(crl::time delay);
void updateLocal(not_null<Data::ForumTopic*> topic);
void updateLocal(not_null<PeerData*> peer);
void updateLocal(DefaultNotify type);
@ -92,6 +114,9 @@ private:
DefaultValue _defaultValues[3];
std::unordered_set<not_null<const PeerData*>> _mutedPeers;
std::unordered_map<
not_null<Data::ForumTopic*>,
rpl::lifetime> _mutedTopics;
base::Timer _unmuteByFinishedTimer;
struct {

View File

@ -74,7 +74,7 @@ History::History(not_null<Data::Session*> owner, PeerId peerId)
, peer(owner->peer(peerId))
, cloudDraftTextCache(st::dialogsTextWidthMin)
, _delegateMixin(HistoryInner::DelegateMixin())
, _mute(owner->notifySettings().isMuted(peer))
, _flags(owner->notifySettings().isMuted(peer) ? Flag::Muted : Flag(0))
, _chatListNameSortKey(owner->nameSortKey(peer->name()))
, _sendActionPainter(this) {
if (const auto user = peer->asUser()) {
@ -1768,12 +1768,12 @@ void History::setFakeUnreadWhileOpened(bool enabled) {
return _fakeUnreadWhileOpened;
}
bool History::mute() const {
return _mute;
bool History::muted() const {
return (_flags & Flag::Muted);
}
bool History::changeMute(bool newMute) {
if (_mute == newMute) {
bool History::changeMuted(bool muted) {
if (this->muted() == muted) {
return false;
}
const auto refresher = gsl::finally([&] {
@ -1787,7 +1787,11 @@ bool History::changeMute(bool newMute) {
});
const auto notify = (unreadCountForBadge() > 0);
const auto notifier = unreadStateChangeNotifier(notify);
_mute = newMute;
if (muted) {
_flags |= Flag::Muted;
} else {
_flags &= ~Flag::Muted;
}
return true;
}
@ -2072,14 +2076,14 @@ bool History::chatListUnreadMark() const {
}
bool History::chatListMutedBadge() const {
return mute();
return muted();
}
Dialogs::UnreadState History::chatListUnreadState() const {
auto result = Dialogs::UnreadState();
const auto count = _unreadCount.value_or(0);
const auto mark = !count && _unreadMark;
const auto muted = mute();
const auto muted = this->muted();
result.messages = count;
result.messagesMuted = muted ? count : 0;
result.chats = count ? 1 : 0;

View File

@ -262,8 +262,8 @@ public:
void setFakeUnreadWhileOpened(bool enabled);
[[nodiscard]] bool fakeUnreadWhileOpened() const;
[[nodiscard]] int unreadCountForBadge() const; // unreadCount || unreadMark ? 1 : 0.
[[nodiscard]] bool mute() const;
bool changeMute(bool newMute);
[[nodiscard]] bool muted() const;
bool changeMuted(bool muted);
void addUnreadBar();
void destroyUnreadBar();
[[nodiscard]] Element *unreadBar() const;
@ -472,6 +472,7 @@ private:
enum class Flag : uchar {
HasPendingResizedItems = (1 << 0),
Muted = (1 << 1),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) {
@ -588,7 +589,6 @@ private:
const std::unique_ptr<HistoryMainElementDelegateMixin> _delegateMixin;
Flags _flags = 0;
bool _mute = false;
int _width = 0;
int _height = 0;
Element *_unreadBarView = nullptr;

View File

@ -2448,7 +2448,7 @@ void HistoryWidget::updateNotifyControls() {
return;
}
_muteUnmute->setText((_history->mute()
_muteUnmute->setText((_history->muted()
? tr::lng_channel_unmute(tr::now)
: tr::lng_channel_mute(tr::now)).toUpper());
if (!session().data().notifySettings().silentPostsUnknown(_peer)) {
@ -3797,7 +3797,7 @@ void HistoryWidget::joinChannel() {
}
void HistoryWidget::toggleMuteUnmute() {
const auto wasMuted = !!_history->mute();
const auto wasMuted = _history->muted();
const auto muteForSeconds = Data::MuteValue{
.unmute = wasMuted,
.forever = !wasMuted,

View File

@ -53,6 +53,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/call_delayed.h"
#include "base/qt/qt_key_modifiers.h"
#include "core/file_utilities.h"
#include "core/application.h"
#include "main/main_session.h"
#include "data/data_session.h"
#include "data/data_user.h"
@ -69,6 +70,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "inline_bots/inline_bot_result.h"
#include "lang/lang_keys.h"
#include "facades.h"
#include "window/notifications_manager.h"
#include "styles/style_chat.h"
#include "styles/style_window.h"
#include "styles/style_info.h"
@ -1954,6 +1956,9 @@ void RepliesWidget::readTill(not_null<HistoryItem*> item) {
_readRequestTimer.callOnce(0);
}
}
if (_topic) {
Core::App().notifications().clearIncomingFromTopic(_topic);
}
}
void RepliesWidget::listMarkReadTill(not_null<HistoryItem*> item) {

View File

@ -320,7 +320,7 @@ private:
};
class RepliesMemento : public Window::SectionMemento {
class RepliesMemento final : public Window::SectionMemento {
public:
RepliesMemento(
not_null<History*> history,

View File

@ -180,6 +180,23 @@ rpl::producer<const ChannelLocation*> LocationValue(
});
}
rpl::producer<bool> NotificationsEnabledValue(
not_null<Data::ForumTopic*> topic) {
return rpl::merge(
topic->session().changes().topicFlagsValue(
topic,
Data::TopicUpdate::Flag::Notifications
) | rpl::to_empty,
topic->session().changes().peerUpdates(
topic->channel(),
UpdateFlag::Notifications
) | rpl::to_empty,
topic->owner().notifySettings().defaultUpdates(topic->channel())
) | rpl::map([=] {
return !topic->owner().notifySettings().isMuted(topic);
}) | rpl::distinct_until_changed();
}
rpl::producer<bool> NotificationsEnabledValue(not_null<PeerData*> peer) {
return rpl::merge(
peer->session().changes().peerFlagsValue(

View File

@ -62,6 +62,8 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
not_null<ChannelData*> channel);
[[nodiscard]] rpl::producer<bool> NotificationsEnabledValue(
not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<bool> NotificationsEnabledValue(
not_null<Data::ForumTopic*> topic);
[[nodiscard]] rpl::producer<bool> IsContactValue(not_null<UserData*> user);
[[nodiscard]] rpl::producer<QString> InviteToChatButton(
not_null<UserData*> user);

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/platform/linux/base_linux_dbus_utilities.h"
#include "core/application.h"
#include "core/core_settings.h"
#include "data/data_forum_topic.h"
#include "history/history.h"
#include "history/history_item.h"
#include "main/main_session.h"
@ -794,11 +795,12 @@ public:
DisplayOptions options);
void clearAll();
void clearFromItem(not_null<HistoryItem*> item);
void clearFromTopic(not_null<Data::ForumTopic*> topic);
void clearFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void clearNotification(NotificationId id);
bool inhibited() {
[[nodiscard]] bool inhibited() const {
return _inhibited;
}
@ -808,7 +810,7 @@ private:
const not_null<Manager*> _manager;
base::flat_map<
FullPeer,
ContextId,
base::flat_map<MsgId, Notification>> _notifications;
Window::Notifications::CachedUserpics _cachedUserpics;
@ -892,17 +894,22 @@ Manager::Private::Private(not_null<Manager*> manager, Type type)
void Manager::Private::showNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
const QString &subtitle,
const QString &msg,
DisplayOptions options) {
const auto key = FullPeer{
const auto key = ContextId{
.sessionId = peer->session().uniqueId(),
.peerId = peer->id
.peerId = peer->id,
.topicRootId = topicRootId,
};
const auto notificationId = NotificationId{
.contextId = key,
.msgId = msgId,
};
const auto notificationId = NotificationId{ .full = key, .msgId = msgId };
auto notification = std::make_unique<NotificationData>(
_manager,
notificationId);
@ -951,9 +958,10 @@ void Manager::Private::clearAll() {
}
void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
const auto key = FullPeer{
const auto key = ContextId{
.sessionId = item->history()->session().uniqueId(),
.peerId = item->history()->peer->id
.peerId = item->history()->peer->id,
.topicRootId = item->topicRootId(),
};
const auto i = _notifications.find(key);
if (i == _notifications.cend()) {
@ -971,10 +979,10 @@ void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
taken->close();
}
void Manager::Private::clearFromHistory(not_null<History*> history) {
const auto key = FullPeer{
.sessionId = history->session().uniqueId(),
.peerId = history->peer->id
void Manager::Private::clearFromTopic(not_null<Data::ForumTopic*> topic) {
const auto key = ContextId{
.sessionId = topic->session().uniqueId(),
.peerId = topic->history()->peer->id
};
const auto i = _notifications.find(key);
if (i != _notifications.cend()) {
@ -987,13 +995,31 @@ void Manager::Private::clearFromHistory(not_null<History*> history) {
}
}
void Manager::Private::clearFromHistory(not_null<History*> history) {
const auto sessionId = history->session().uniqueId();
const auto peerId = history->peer->id;
auto i = _notifications.lower_bound(ContextId{
.sessionId = sessionId,
.peerId = peerId,
});
while (i != _notifications.cend()
&& i->first.sessionId == sessionId
&& i->first.peerId == peerId) {
const auto temp = base::take(i->second);
i = _notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
notification->close();
}
}
}
void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
const auto sessionId = session->uniqueId();
for (auto i = _notifications.begin(); i != _notifications.end();) {
if (i->first.sessionId != sessionId) {
++i;
continue;
}
auto i = _notifications.lower_bound(ContextId{
.sessionId = sessionId,
});
while (i != _notifications.cend() && i->first.sessionId == sessionId) {
const auto temp = base::take(i->second);
i = _notifications.erase(i);
@ -1004,7 +1030,7 @@ void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
}
void Manager::Private::clearNotification(NotificationId id) {
auto i = _notifications.find(id.full);
auto i = _notifications.find(id.contextId);
if (i != _notifications.cend()) {
if (i->second.remove(id.msgId) && i->second.empty()) {
_notifications.erase(i);
@ -1059,6 +1085,10 @@ void Manager::doClearFromItem(not_null<HistoryItem*> item) {
_private->clearFromItem(item);
}
void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
_private->clearFromTopic(topic);
}
void Manager::doClearFromHistory(not_null<History*> history) {
_private->clearFromHistory(history);
}

View File

@ -29,6 +29,7 @@ protected:
DisplayOptions options) override;
void doClearAllFast() override;
void doClearFromItem(not_null<HistoryItem*> item) override;
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
bool doSkipAudio() const override;

View File

@ -21,6 +21,7 @@ public:
protected:
void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -29,6 +30,7 @@ protected:
DisplayOptions options) override;
void doClearAllFast() override;
void doClearFromItem(not_null<HistoryItem*> item) override;
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
QString accountNameSeparator() override;

View File

@ -106,16 +106,23 @@ using Manager = Platform::Notifications::Manager;
LOG(("App Error: A notification with unknown peer was received"));
return;
}
NSNumber *topicObject = [notificationUserInfo objectForKey:@"topic"];
if (!topicObject) {
LOG(("App Error: A notification with unknown topic was received"));
return;
}
const auto notificationTopicRootId = [topicObject longlongValue];
NSNumber *msgObject = [notificationUserInfo objectForKey:@"msgid"];
const auto notificationMsgId = msgObject ? [msgObject longLongValue] : 0LL;
const auto my = Window::Notifications::Manager::NotificationId{
.full = Manager::FullPeer{
.contextId = Manager::ContextId{
.sessionId = notificationSessionId,
.peerId = PeerId(notificationPeerId)
.peerId = PeerId(notificationPeerId),
.topicRootId = MsgId(notificationTopicRootId),
},
.msgId = notificationMsgId
.msgId = notificationMsgId,
};
if (notification.activationType == NSUserNotificationActivationTypeReplied) {
const auto notificationReply = QString::fromUtf8([[[notification response] string] UTF8String]);
@ -180,6 +187,7 @@ public:
void showNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -188,6 +196,7 @@ public:
DisplayOptions options);
void clearAll();
void clearFromItem(not_null<HistoryItem*> item);
void clearFromTopic(not_null<Data::ForumTopic*> topic);
void clearFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void updateDelegate();
@ -212,8 +221,11 @@ private:
struct ClearFromItem {
NotificationId id;
};
struct ClearFromTopic {
ContextId contextId;
}
struct ClearFromHistory {
FullPeer fullPeer;
ContextId partialContextId;
};
struct ClearFromSession {
uint64 sessionId = 0;
@ -224,6 +236,7 @@ private:
};
using ClearTask = std::variant<
ClearFromItem,
ClearFromTopic,
ClearFromHistory,
ClearFromSession,
ClearAll,
@ -250,6 +263,7 @@ Manager::Private::Private(Manager *manager)
void Manager::Private::showNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -274,6 +288,8 @@ void Manager::Private::showNotification(
@"session",
[NSNumber numberWithUnsignedLongLong:peer->id.value],
@"peer",
[NSNumber numberWithLongLong:topicRootId.bare],
@"topic",
[NSNumber numberWithLongLong:msgId.bare],
@"msgid",
[NSNumber numberWithUnsignedLongLong:_managerId],
@ -312,7 +328,8 @@ void Manager::Private::clearingThreadLoop() {
while (!finished) {
auto clearAll = false;
auto clearFromItems = base::flat_set<NotificationId>();
auto clearFromPeers = base::flat_set<FullPeer>();
auto clearFromTopics = base::flat_set<ContextId>();
auto clearFromHistories = base::flat_set<ContextId>();
auto clearFromSessions = base::flat_set<uint64>();
{
std::unique_lock<std::mutex> lock(_clearingMutex);
@ -327,8 +344,10 @@ void Manager::Private::clearingThreadLoop() {
clearAll = true;
}, [&](const ClearFromItem &value) {
clearFromItems.emplace(value.id);
}, [&](Const ClearFromTopic &value) {
clearFromTopics.emplace(value.contextId);
}, [&](const ClearFromHistory &value) {
clearFromPeers.emplace(value.fullPeer);
clearFromHistories.emplace(value.partialContextId);
}, [&](const ClearFromSession &value) {
clearFromSessions.emplace(value.sessionId);
});
@ -349,15 +368,26 @@ void Manager::Private::clearingThreadLoop() {
if (!notificationPeerId) {
return true;
}
NSNumber *topicObject = [notificationUserInfo objectForKey:@"topic"];
if (!topicObject) {
return true;
}
const auto notificationTopicRootId = [topicObject longLongValue];
NSNumber *msgObject = [notificationUserInfo objectForKey:@"msgid"];
const auto msgId = msgObject ? [msgObject longLongValue] : 0LL;
const auto full = FullPeer{
const auto partialContextId = ContextId{
.sessionId = notificationSessionId,
.peerId = PeerId(notificationPeerId)
.peerId = PeerId(notificationPeerId),
};
const auto id = NotificationId{ full, MsgId(msgId) };
const auto contextId = ContextId{
.sessionId = notificationSessionId,
.peerId = PeerId(notificationPeerId),
.topicRootId = MsgId(notificationTopicRootId),
};
const auto id = NotificationId{ contextId, MsgId(msgId) };
return clearFromSessions.contains(notificationSessionId)
|| clearFromPeers.contains(full)
|| clearFromHistories.contains(partialContextId)
|| clearFromTopics.contains(contextId)
|| (msgId && clearFromItems.contains(id));
};
@ -394,21 +424,30 @@ void Manager::Private::clearAll() {
}
void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
putClearTask(ClearFromItem { FullPeer{
putClearTask(ClearFromItem{ ContextId{
.sessionId = item->history()->session().uniqueId(),
.peerId = item->history()->peer->id
.peerId = item->history()->peer->id,
.topicRootId = item->topicRootId(),
}, item->id });
}
void Manager::Private::clearFromTopic(not_null<Data::ForumTopic*> topic) {
putClearTask(ClearFromTopic{ ContextId{
.sessionId = topic->session().uniqueId(),
.peerId = topic->history()->peer->id,
.topicRootId = topic->rootId(),
} });
}
void Manager::Private::clearFromHistory(not_null<History*> history) {
putClearTask(ClearFromHistory { FullPeer{
putClearTask(ClearFromHistory{ ContextId{
.sessionId = history->session().uniqueId(),
.peerId = history->peer->id
.peerId = history->peer->id,
} });
}
void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
putClearTask(ClearFromSession { session->uniqueId() });
putClearTask(ClearFromSession{ session->uniqueId() });
}
void Manager::Private::updateDelegate() {
@ -434,6 +473,7 @@ Manager::~Manager() = default;
void Manager::doShowNativeNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -442,6 +482,7 @@ void Manager::doShowNativeNotification(
DisplayOptions options) {
_private->showNotification(
peer,
topicRootId,
userpicView,
msgId,
title,
@ -458,6 +499,10 @@ void Manager::doClearFromItem(not_null<HistoryItem*> item) {
_private->clearFromItem(item);
}
void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
_private->clearFromTopic(topic);
}
void Manager::doClearFromHistory(not_null<History*> history) {
_private->clearFromHistory(history);
}

View File

@ -100,7 +100,7 @@ QImage UnreadBadge(not_null<PeerData*> peer) {
: QString::number(count);
Dialogs::Ui::UnreadBadgeStyle unreadSt;
unreadSt.sizeId = Dialogs::Ui::UnreadBadgeSize::TouchBar;
unreadSt.muted = history->mute();
unreadSt.muted = history->muted();
// Use constant values to draw badge regardless of cConfigScale().
unreadSt.size = kUnreadBadgeSize * cRetinaFactor();
unreadSt.padding = 4 * cRetinaFactor();

View File

@ -18,6 +18,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/win/windows_toast_activator.h"
#include "platform/win/windows_dlls.h"
#include "platform/win/specific_win.h"
#include "data/data_forum_topic.h"
#include "history/history.h"
#include "history/history_item.h"
#include "core/application.h"
@ -411,6 +412,7 @@ public:
bool showNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -419,6 +421,7 @@ public:
DisplayOptions options);
void clearAll();
void clearFromItem(not_null<HistoryItem*> item);
void clearFromTopic(not_null<Data::ForumTopic*> topic);
void clearFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void beforeNotificationActivated(NotificationId id);
@ -434,6 +437,7 @@ public:
private:
bool showNotificationInTryCatch(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -450,7 +454,7 @@ private:
ToastNotifier _notifier = nullptr;
base::flat_map<
FullPeer,
ContextId,
base::flat_map<MsgId, ToastNotification>> _notifications;
rpl::lifetime _lifetime;
@ -496,9 +500,10 @@ void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
return;
}
auto i = _notifications.find(FullPeer{
auto i = _notifications.find(ContextId{
.sessionId = item->history()->session().uniqueId(),
.peerId = item->history()->peer->id
.peerId = item->history()->peer->id,
.topicRootId = item->topicRootId(),
});
if (i == _notifications.cend()) {
return;
@ -515,18 +520,42 @@ void Manager::Private::clearFromItem(not_null<HistoryItem*> item) {
tryHide(taken);
}
void Manager::Private::clearFromTopic(not_null<Data::ForumTopic*> topic) {
if (!_notifier) {
return;
}
const auto i = _notifications.find(ContextId{
.sessionId = topic->session().uniqueId(),
.peerId = topic->history()->peer->id,
.topicRootId = topic->rootId(),
});
if (i != _notifications.cend()) {
const auto temp = base::take(i->second);
_notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
tryHide(notification);
}
}
}
void Manager::Private::clearFromHistory(not_null<History*> history) {
if (!_notifier) {
return;
}
auto i = _notifications.find(FullPeer{
.sessionId = history->session().uniqueId(),
.peerId = history->peer->id
const auto sessionId = history->session().uniqueId();
const auto peerId = history->peer->id;
auto i = _notifications.lower_bound(ContextId{
.sessionId = sessionId,
.peerId = peerId,
});
if (i != _notifications.cend()) {
while (i != _notifications.cend()
&& i->first.sessionId == sessionId
&& i->first.peerId == peerId) {
const auto temp = base::take(i->second);
_notifications.erase(i);
i = _notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
tryHide(notification);
@ -540,13 +569,12 @@ void Manager::Private::clearFromSession(not_null<Main::Session*> session) {
}
const auto sessionId = session->uniqueId();
for (auto i = _notifications.begin(); i != _notifications.end();) {
if (i->first.sessionId != sessionId) {
++i;
continue;
}
auto i = _notifications.lower_bound(ContextId{
.sessionId = sessionId,
});
while (i != _notifications.cend() && i->first.sessionId == sessionId) {
const auto temp = base::take(i->second);
_notifications.erase(i);
i = _notifications.erase(i);
for (const auto &[msgId, notification] : temp) {
tryHide(notification);
@ -565,7 +593,7 @@ void Manager::Private::afterNotificationActivated(
}
void Manager::Private::clearNotification(NotificationId id) {
auto i = _notifications.find(id.full);
auto i = _notifications.find(id.contextId);
if (i != _notifications.cend()) {
i->second.remove(id.msgId);
if (i->second.empty()) {
@ -589,13 +617,14 @@ void Manager::Private::handleActivation(const ToastActivation &activation) {
}
const auto action = parsed.value("action");
const auto id = NotificationId{
.full = FullPeer{
.contextId = ContextId{
.sessionId = parsed.value("session").toULongLong(),
.peerId = PeerId(parsed.value("peer").toULongLong()),
.topicRootId = MsgId(parsed.value("topic").toLongLong())
},
.msgId = MsgId(parsed.value("msg").toLongLong()),
};
if (!id.full.sessionId || !id.full.peerId || !id.msgId) {
if (!id.contextId.sessionId || !id.contextId.peerId || !id.msgId) {
DEBUG_LOG(("Toast Info: Got activation \"%1\", my %1, skipping."
).arg(activation.args
).arg(pid));
@ -610,7 +639,7 @@ void Manager::Private::handleActivation(const ToastActivation &activation) {
text.text = entry.value;
}
}
const auto i = _notifications.find(id.full);
const auto i = _notifications.find(id.contextId);
if (i == _notifications.cend() || !i->second.contains(id.msgId)) {
return;
}
@ -627,6 +656,7 @@ void Manager::Private::handleActivation(const ToastActivation &activation) {
bool Manager::Private::showNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -640,6 +670,7 @@ bool Manager::Private::showNotification(
return base::WinRT::Try([&] {
return showNotificationInTryCatch(
peer,
topicRootId,
userpicView,
msgId,
title,
@ -660,6 +691,7 @@ std::wstring Manager::Private::ensureSendButtonIcon() {
bool Manager::Private::showNotificationInTryCatch(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -669,18 +701,20 @@ bool Manager::Private::showNotificationInTryCatch(
const auto withSubtitle = !subtitle.isEmpty();
auto toastXml = XmlDocument();
const auto key = FullPeer{
const auto key = ContextId{
.sessionId = peer->session().uniqueId(),
.peerId = peer->id,
.topicRootId = topicRootId,
};
const auto notificationId = NotificationId{
.full = key,
.contextId = key,
.msgId = msgId
};
const auto idString = u"pid=%1&session=%2&peer=%3&msg=%4"_q
const auto idString = u"pid=%1&session=%2&peer=%3&topic=%4&msg=%5"_q
.arg(GetCurrentProcessId())
.arg(key.sessionId)
.arg(key.peerId.value)
.arg(topicRootId.bare)
.arg(msgId.bare);
const auto modern = Platform::IsWindows10OrGreater();
@ -855,6 +889,7 @@ Manager::~Manager() = default;
void Manager::doShowNativeNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -863,6 +898,7 @@ void Manager::doShowNativeNotification(
DisplayOptions options) {
_private->showNotification(
peer,
topicRootId,
userpicView,
msgId,
title,
@ -879,6 +915,10 @@ void Manager::doClearFromItem(not_null<HistoryItem*> item) {
_private->clearFromItem(item);
}
void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
_private->clearFromTopic(topic);
}
void Manager::doClearFromHistory(not_null<History*> history) {
_private->clearFromHistory(history);
}

View File

@ -29,6 +29,7 @@ public:
protected:
void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -37,6 +38,7 @@ protected:
DisplayOptions options) override;
void doClearAllFast() override;
void doClearFromItem(not_null<HistoryItem*> item) override;
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
void onBeforeNotificationActivated(NotificationId id) override;

View File

@ -71,7 +71,7 @@ void SharedMedia::add(SharedMediaAddSlice &&query) {
}
void SharedMedia::remove(SharedMediaRemoveOne &&query) {
auto peerIt = _lists.find({ query.peerId, MsgId(0) });
auto peerIt = _lists.lower_bound({ query.peerId, MsgId(0) });
while (peerIt != end(_lists) && peerIt->first.peerId == query.peerId) {
for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
auto type = static_cast<SharedMediaType>(index);
@ -85,7 +85,7 @@ void SharedMedia::remove(SharedMediaRemoveOne &&query) {
}
void SharedMedia::remove(SharedMediaRemoveAll &&query) {
auto peerIt = _lists.find({ query.peerId, MsgId(0) });
auto peerIt = _lists.lower_bound({ query.peerId, MsgId(0) });
while (peerIt != end(_lists) && peerIt->first.peerId == query.peerId) {
for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
auto type = static_cast<SharedMediaType>(index);
@ -99,7 +99,7 @@ void SharedMedia::remove(SharedMediaRemoveAll &&query) {
}
void SharedMedia::invalidate(SharedMediaInvalidateBottom &&query) {
auto peerIt = _lists.find({ query.peerId, MsgId(0) });
auto peerIt = _lists.lower_bound({ query.peerId, MsgId(0) });
while (peerIt != end(_lists) && peerIt->first.peerId == query.peerId) {
for (auto index = 0; index != kSharedMediaTypeCount; ++index) {
peerIt->second[index].invalidateBottom();

View File

@ -14,12 +14,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "mtproto/mtproto_config.h"
#include "history/history.h"
#include "history/history_item_components.h"
#include "history/view/history_view_replies_section.h"
#include "lang/lang_keys.h"
#include "data/notify/data_notify_settings.h"
#include "data/stickers/data_custom_emoji.h"
#include "data/data_document_media.h"
#include "data/data_session.h"
#include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_user.h"
#include "data/data_document.h"
#include "data/data_poll.h"
@ -327,6 +329,22 @@ void System::clearAll() {
_settingWaiters.clear();
}
void System::clearFromTopic(not_null<Data::ForumTopic*> topic) {
if (_manager) {
_manager->clearFromTopic(topic);
}
// #TODO forum notifications
//topic->clearNotifications();
//_whenMaps.remove(topic);
//_whenAlerts.remove(topic);
//_waiters.remove(topic);
//_settingWaiters.remove(topic);
_waitTimer.cancel();
showNext();
}
void System::clearFromHistory(not_null<History*> history) {
if (_manager) {
_manager->clearFromHistory(history);
@ -381,6 +399,15 @@ void System::clearIncomingFromHistory(not_null<History*> history) {
_whenAlerts.remove(history);
}
void System::clearIncomingFromTopic(not_null<Data::ForumTopic*> topic) {
if (_manager) {
_manager->clearFromTopic(topic);
}
// #TODO forum notifications
//topic->clearIncomingNotifications();
//_whenAlerts.remove(topic);
}
void System::clearFromItem(not_null<HistoryItem*> item) {
if (_manager) {
_manager->clearFromItem(item);
@ -922,14 +949,16 @@ void Manager::notificationActivated(
NotificationId id,
const TextWithTags &reply) {
onBeforeNotificationActivated(id);
if (const auto session = system()->findSession(id.full.sessionId)) {
if (const auto session = system()->findSession(id.contextId.sessionId)) {
if (session->windows().empty()) {
Core::App().domain().activate(&session->account());
}
if (!session->windows().empty()) {
const auto window = session->windows().front();
const auto history = session->data().history(id.full.peerId);
const auto history = session->data().history(
id.contextId.peerId);
if (!reply.text.isEmpty()) {
// #TODO forum notifications
const auto replyToId = (id.msgId > 0
&& !history->peer->isUser())
? id.msgId
@ -961,45 +990,57 @@ void Manager::notificationActivated(
void Manager::openNotificationMessage(
not_null<History*> history,
MsgId messageId) {
const auto openExactlyMessage = [&] {
const auto peer = history->peer;
if (peer->isBroadcast()) {
return false;
const auto item = history->owner().message(history->peer, messageId);
const auto openExactlyMessage = !history->peer->isBroadcast()
&& item
&& item->isRegular()
&& (item->out() || (item->mentionsMe() && !history->peer->isUser()));
const auto topic = item ? history->peer->forumTopicFor(item) : nullptr;
const auto separate = Core::App().separateWindowForPeer(history->peer);
const auto window = separate
? separate->sessionController()
: history->session().tryResolveWindow();
const auto itemId = openExactlyMessage ? messageId : ShowAtUnreadMsgId;
if (window) {
if (topic) {
window->showSection(
std::make_shared<HistoryView::RepliesMemento>(
history,
topic->rootId(),
itemId),
SectionShow::Way::Forward);
} else {
window->showPeerHistory(
history->peer->id,
SectionShow::Way::Forward,
itemId);
}
const auto item = history->owner().message(history->peer, messageId);
if (!item
|| !item->isRegular()
|| (!item->out() && (!item->mentionsMe() || peer->isUser()))) {
return false;
}
return true;
}();
if (openExactlyMessage) {
Ui::showPeerHistory(history, messageId);
} else {
Ui::showPeerHistory(history, ShowAtUnreadMsgId);
}
system()->clearFromHistory(history);
if (topic) {
system()->clearFromTopic(topic);
} else {
system()->clearFromHistory(history);
}
}
void Manager::notificationReplied(
NotificationId id,
const TextWithTags &reply) {
if (!id.full.sessionId || !id.full.peerId) {
if (!id.contextId.sessionId || !id.contextId.peerId) {
return;
}
const auto session = system()->findSession(id.full.sessionId);
const auto session = system()->findSession(id.contextId.sessionId);
if (!session) {
return;
}
const auto history = session->data().history(id.full.peerId);
const auto history = session->data().history(id.contextId.peerId);
auto message = Api::MessageToSend(Api::SendAction(history));
message.textWithTags = reply;
message.action.replyTo = (id.msgId > 0 && !history->peer->isUser())
? id.msgId
: 0;
: id.contextId.topicRootId;
message.action.clearDraft = false;
history->session().api().sendMessage(std::move(message));
@ -1053,6 +1094,7 @@ void NativeManager::doShowNotification(NotificationFields &&fields) {
auto userpicView = item->history()->peer->createUserpicView();
doShowNativeNotification(
item->history()->peer,
item->topicRootId(),
userpicView,
item->id,
scheduled ? WrapFromScheduled(fullTitle) : fullTitle,

View File

@ -18,6 +18,7 @@ enum class ItemNotificationType;
namespace Data {
class Session;
class CloudImageView;
class ForumTopic;
} // namespace Data
namespace Main {
@ -90,7 +91,9 @@ public:
void checkDelayed();
void schedule(ItemNotification notification);
void clearFromTopic(not_null<Data::ForumTopic*> topic);
void clearFromHistory(not_null<History*> history);
void clearIncomingFromTopic(not_null<Data::ForumTopic*> topic);
void clearIncomingFromHistory(not_null<History*> history);
void clearFromSession(not_null<Main::Session*> session);
void clearFromItem(not_null<HistoryItem*> item);
@ -202,24 +205,22 @@ private:
class Manager {
public:
struct FullPeer {
struct ContextId {
uint64 sessionId = 0;
PeerId peerId = 0;
MsgId topicRootId = 0;
friend inline bool operator<(const FullPeer &a, const FullPeer &b) {
return std::tie(a.sessionId, a.peerId)
< std::tie(b.sessionId, b.peerId);
}
friend inline auto operator<=>(
const ContextId&,
const ContextId&) = default;
};
struct NotificationId {
FullPeer full;
ContextId contextId;
MsgId msgId = 0;
friend inline bool operator<(
const NotificationId &a,
const NotificationId &b) {
return std::tie(a.full, a.msgId) < std::tie(b.full, b.msgId);
}
friend inline auto operator<=>(
const NotificationId&,
const NotificationId&) = default;
};
struct NotificationFields {
not_null<HistoryItem*> item;
@ -246,6 +247,9 @@ public:
void clearFromItem(not_null<HistoryItem*> item) {
doClearFromItem(item);
}
void clearFromTopic(not_null<Data::ForumTopic*> topic) {
doClearFromTopic(topic);
}
void clearFromHistory(not_null<History*> history) {
doClearFromHistory(history);
}
@ -294,7 +298,7 @@ public:
virtual ~Manager() = default;
protected:
not_null<System*> system() const {
[[nodiscard]] not_null<System*> system() const {
return _system;
}
@ -303,6 +307,7 @@ protected:
virtual void doClearAll() = 0;
virtual void doClearAllFast() = 0;
virtual void doClearFromItem(not_null<HistoryItem*> item) = 0;
virtual void doClearFromTopic(not_null<Data::ForumTopic*> topic) = 0;
virtual void doClearFromHistory(not_null<History*> history) = 0;
virtual void doClearFromSession(not_null<Main::Session*> session) = 0;
virtual bool doSkipAudio() const = 0;
@ -349,6 +354,7 @@ protected:
virtual void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -369,6 +375,7 @@ public:
protected:
void doShowNativeNotification(
not_null<PeerData*> peer,
MsgId topicRootId,
std::shared_ptr<Data::CloudImageView> &userpicView,
MsgId msgId,
const QString &title,
@ -380,6 +387,8 @@ protected:
}
void doClearFromItem(not_null<HistoryItem*> item) override {
}
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override {
}
void doClearFromHistory(not_null<History*> history) override {
}
void doClearFromSession(not_null<Main::Session*> session) override {

View File

@ -23,6 +23,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/painter.h"
#include "ui/ui_utility.h"
#include "data/data_session.h"
#include "data/data_forum_topic.h"
#include "data/stickers/data_custom_emoji.h"
#include "dialogs/ui/dialogs_layout.h"
#include "window/window_controller.h"
@ -82,6 +83,7 @@ Manager::Manager(System *system)
Manager::QueuedNotification::QueuedNotification(NotificationFields &&fields)
: history(fields.item->history())
, topicRootId(fields.item->topicRootId())
, peer(history->peer)
, reaction(fields.reactionId)
, author(!fields.reactionFrom
@ -236,6 +238,7 @@ void Manager::showNextFromQueue() {
_notifications.push_back(std::make_unique<Notification>(
this,
queued.history,
queued.topicRootId,
queued.peer,
queued.author,
queued.item,
@ -372,6 +375,24 @@ void Manager::doClearAllFast() {
base::take(_hideAll);
}
void Manager::doClearFromTopic(not_null<Data::ForumTopic*> topic) {
const auto history = topic->history();
const auto topicRootId = topic->rootId();
for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
if (i->history == history && i->topicRootId == topicRootId) {
i = _queuedNotifications.erase(i);
} else {
++i;
}
}
for (const auto &notification : _notifications) {
if (notification->unlinkHistory(history, topicRootId)) {
_positionsOutdated = true;
}
}
showNextFromQueue();
}
void Manager::doClearFromHistory(not_null<History*> history) {
for (auto i = _queuedNotifications.begin(); i != _queuedNotifications.cend();) {
if (i->history == history) {
@ -601,6 +622,7 @@ void Background::paintEvent(QPaintEvent *e) {
Notification::Notification(
not_null<Manager*> manager,
not_null<History*> history,
MsgId topicRootId,
not_null<PeerData*> peer,
const QString &author,
HistoryItem *item,
@ -614,6 +636,7 @@ Notification::Notification(
, _peer(peer)
, _started(crl::now())
, _history(history)
, _topicRootId(topicRootId)
, _userpicView(_peer->createUserpicView())
, _author(author)
, _reaction(reaction)
@ -1061,9 +1084,10 @@ Notifications::Manager::NotificationId Notification::myId() const {
if (!_history) {
return {};
}
return { .full = {
return { .contextId = {
.sessionId = _history->session().uniqueId(),
.peerId = _history->peer->id
.peerId = _history->peer->id,
.topicRootId = _topicRootId,
}, .msgId = _item ? _item->id : ShowAtUnreadMsgId };
}
@ -1071,8 +1095,10 @@ void Notification::changeHeight(int newHeight) {
manager()->changeNotificationHeight(this, newHeight);
}
bool Notification::unlinkHistory(History *history) {
const auto unlink = _history && (history == _history || !history);
bool Notification::unlinkHistory(History *history, MsgId topicRootId) {
const auto unlink = _history
&& (history == _history || !history)
&& (topicRootId == _topicRootId || !topicRootId);
if (unlink) {
hideFast();
_history = nullptr;

View File

@ -72,6 +72,7 @@ private:
void doShowNotification(NotificationFields &&fields) override;
void doClearAll() override;
void doClearAllFast() override;
void doClearFromTopic(not_null<Data::ForumTopic*> topic) override;
void doClearFromHistory(not_null<History*> history) override;
void doClearFromSession(not_null<Main::Session*> session) override;
void doClearFromItem(not_null<HistoryItem*> item) override;
@ -112,6 +113,7 @@ private:
QueuedNotification(NotificationFields &&fields);
not_null<History*> history;
MsgId topicRootId = 0;
not_null<PeerData*> peer;
Data::ReactionId reaction;
QString author;
@ -205,6 +207,7 @@ public:
Notification(
not_null<Manager*> manager,
not_null<History*> history,
MsgId topicRootId,
not_null<PeerData*> peer,
const QString &author,
HistoryItem *item,
@ -233,7 +236,7 @@ public:
// Called only by Manager.
bool unlinkItem(HistoryItem *del);
bool unlinkHistory(History *history = nullptr);
bool unlinkHistory(History *history = nullptr, MsgId topicRootId = 0);
bool unlinkSession(not_null<Main::Session*> session);
bool checkLastInput(
bool hasReplyingNotifications,
@ -282,6 +285,7 @@ private:
crl::time _started;
History *_history = nullptr;
MsgId _topicRootId = 0;
std::shared_ptr<Data::CloudImageView> _userpicView;
QString _author;
Data::ReactionId _reaction;

View File

@ -141,7 +141,7 @@ void PeerMenuAddMuteSubmenuAction(
if (isMuted) {
const auto text = tr::lng_context_unmute(tr::now)
+ '\t'
+ Ui::FormatMuteForTiny(peer->notifyMuteUntil().value_or(0)
+ Ui::FormatMuteForTiny(peer->notify().muteUntil().value_or(0)
- base::unixtime::now());
addAction(text, [=] {
peer->owner().notifySettings().update(peer, { .unmute = true });

@ -1 +1 @@
Subproject commit 8d9d8e86b9abea58ed5a218e60d9f79b47085c63
Subproject commit 2a94813b6aa1f8645fd9de88a78981356259fab7