diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 222644dc33..f9a0dcaa5c 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -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 session) , _dialogsLoadState(std::make_unique()) , _fileLoader(std::make_unique(kFileLoaderQueueStopTimeout)) , _topPromotionTimer([=] { refreshTopPromotion(); }) -, _updateNotifySettingsTimer([=] { sendNotifySettingsUpdates(); }) +, _updateNotifyTimer([=] { sendNotifySettingsUpdates(); }) , _authorizations(std::make_unique(this)) , _attachedStickers(std::make_unique(this)) , _blockedPeers(std::make_unique(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 channel) { _channelAmInRequests.remove(channel); }).send(); - _channelAmInRequests.insert(channel, requestId); + _channelAmInRequests.emplace(channel, requestId); } } @@ -1690,44 +1689,48 @@ void ApiWrap::leaveChannel(not_null 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 peer) { - _updateNotifySettingsPeers.emplace(peer); - _updateNotifySettingsTimer.callOnce(kNotifySettingSaveTimeout); +void ApiWrap::updateNotifySettingsDelayed( + not_null 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 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) { @@ -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 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 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 { diff --git a/Telegram/SourceFiles/apiwrap.h b/Telegram/SourceFiles/apiwrap.h index 2585b30562..0ee848577f 100644 --- a/Telegram/SourceFiles/apiwrap.h +++ b/Telegram/SourceFiles/apiwrap.h @@ -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 channel, not_null from); - void requestWebPageDelayed(WebPageData *page); - void clearWebPageRequest(WebPageData *page); + void requestWebPageDelayed(not_null page); + void clearWebPageRequest(not_null page); void clearWebPageRequests(); void scheduleStickerSetRequest(uint64 setId, uint64 access); @@ -244,8 +245,10 @@ public: void leaveChannel(not_null channel); void requestNotifySettings(const MTPInputNotifyPeer &peer); + void updateNotifySettingsDelayed( + not_null topic); void updateNotifySettingsDelayed(not_null peer); - void updateDefaultNotifySettingsDelayed(Data::DefaultNotify type); + void updateNotifySettingsDelayed(Data::DefaultNotify type); void saveDraftToCloudDelayed(not_null history); static int OnlineTillFromStatus( @@ -523,7 +526,7 @@ private: not_null channel); void migrateFail(not_null peer, const QString &error); - not_null _session; + const not_null _session; base::flat_map _modifyRequests; @@ -541,13 +544,29 @@ private: not_null, std::pair>> _historyArchivedRequests; - QMap _webPagesPending; + base::flat_map, mtpRequestId> _webPagesPending; base::Timer _webPagesTimer; - QMap > _stickerSetRequests; + struct StickerSetRequest { + uint64 accessHash = 0; + mtpRequestId id = 0; + }; + base::flat_map _stickerSetRequests; + + base::flat_map< + not_null, + mtpRequestId> _channelAmInRequests; + + struct NotifySettingsKey { + PeerId peerId = 0; + MsgId topicRootId = 0; + + friend inline constexpr auto operator<=>( + NotifySettingsKey, + NotifySettingsKey) = default; + }; + base::flat_map _notifySettingRequests; - QMap _channelAmInRequests; - base::flat_map _notifySettingRequests; base::flat_map, mtpRequestId> _draftsSaveRequestIds; base::Timer _draftsSaveTimer; @@ -610,9 +629,11 @@ private: TimeId _topPromotionNextRequestTime = TimeId(0); base::Timer _topPromotionTimer; - base::flat_set> _updateNotifySettingsPeers; - base::flat_set _updateNotifySettingsDefaults; - base::Timer _updateNotifySettingsTimer; + base::flat_set> _updateNotifyTopics; + base::flat_set> _updateNotifyPeers; + base::flat_set _updateNotifyDefaults; + base::Timer _updateNotifyTimer; + rpl::lifetime _updateNotifyQueueLifetime; std::map< Data::FileOrigin, diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index 5f53340579..51c7085fc1 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -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; friend inline constexpr auto is_flag_type(Flag) { return true; } diff --git a/Telegram/SourceFiles/data/data_chat_filters.cpp b/Telegram/SourceFiles/data/data_chat_filters.cpp index d77b47c02e..6a433db9a7 100644 --- a/Telegram/SourceFiles/data/data_chat_filters.cpp +++ b/Telegram/SourceFiles/data/data_chat_filters.cpp @@ -207,7 +207,7 @@ bool ChatFilter::contains(not_null history) const { return false || ((_flags & flag) && (!(_flags & Flag::NoMuted) - || !history->mute() + || !history->muted() || (history->unreadMentions().has() && history->folderKnown() && !history->folder())) diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 069505c485..b8469436b1 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -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, MsgId rootId) , _forum(history->peer->forum()) , _list(_forum->topicsList()) , _replies(std::make_shared(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 { diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 61c0bdc165..dd3ae7760e 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.h +++ b/Telegram/SourceFiles/data/data_forum_topic.h @@ -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 item); void applyItemRemoved(MsgId id); + [[nodiscard]] Data::PeerNotifySettings ¬ify() { + return _notify; + } + [[nodiscard]] const Data::PeerNotifySettings ¬ify() 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 _replies; MsgId _rootId = 0; + Data::PeerNotifySettings _notify; + QString _title; DocumentId _iconId = 0; base::flat_set _titleWords; @@ -151,7 +164,7 @@ private: std::optional _lastServerMessage; std::optional _chatListMessage; base::flat_set _requestedGroups; - base::flags _flags; + base::flags _flags; // Initializer accesses _notify, be careful. rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/data_peer.h b/Telegram/SourceFiles/data/data_peer.h index f6af6bfc2b..329ec2c3f2 100644 --- a/Telegram/SourceFiles/data/data_peer.h +++ b/Telegram/SourceFiles/data/data_peer.h @@ -202,29 +202,11 @@ public: not_null item) const; [[nodiscard]] Data::ForumTopic *forumTopicFor(MsgId rootId) const; - [[nodiscard]] std::optional notifyMuteUntil() const { - return _notify.muteUntil(); + [[nodiscard]] Data::PeerNotifySettings ¬ify() { + return _notify; } - bool notifyChange(const MTPPeerNotifySettings &settings) { - return _notify.change(settings); - } - bool notifyChange( - Data::MuteValue muteForSeconds, - std::optional silentPosts, - std::optional sound) { - return _notify.change(muteForSeconds, silentPosts, sound); - } - [[nodiscard]] bool notifySettingsUnknown() const { - return _notify.settingsUnknown(); - } - [[nodiscard]] std::optional notifySilentPosts() const { - return _notify.silentPosts(); - } - [[nodiscard]] std::optional notifySound() const { - return _notify.sound(); - } - [[nodiscard]] MTPinputPeerNotifySettings notifySerialize() const { - return _notify.serialize(); + [[nodiscard]] const Data::PeerNotifySettings ¬ify() const { + return _notify; } [[nodiscard]] bool canWrite() const; diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index ec51889d1b..d6fcce1e73 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -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); } } diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp index d32550340a..ea4982c771 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.cpp +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.cpp @@ -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 owner) @@ -38,7 +49,7 @@ NotifySettings::NotifySettings(not_null owner) } void NotifySettings::request(not_null 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 peer) { } } +void NotifySettings::request(not_null 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 ¬ifyPeer, 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 topic, + Data::MuteValue muteForSeconds, + std::optional sound) { + if (topic->notify().change(muteForSeconds, std::nullopt, sound)) { + updateLocal(topic); + topic->session().api().updateNotifySettingsDelayed(topic); + } +} + +void NotifySettings::resetToDefault(not_null 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 peer, Data::MuteValue muteForSeconds, std::optional silentPosts, std::optional 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 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 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 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 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 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 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 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 topic) const { + return isMuted(topic, nullptr); +} + +NotifySound NotifySettings::sound( + not_null topic) const { + const auto sound = topic->notify().sound(); + return sound ? *sound : this->sound(topic->channel()); +} + +bool NotifySettings::muteUnknown( + not_null topic) const { + return topic->notify().settingsUnknown() + || (!topic->notify().muteUntil().has_value() + && muteUnknown(topic->channel())); +} + +bool NotifySettings::soundUnknown( + not_null topic) const { + return topic->notify().settingsUnknown() + || (!topic->notify().sound().has_value() + && soundUnknown(topic->channel())); +} + bool NotifySettings::isMuted( not_null 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 peer) const { } bool NotifySettings::silentPosts(not_null 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 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 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 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 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 peer) const { @@ -354,6 +443,11 @@ bool NotifySettings::settingsUnknown(not_null peer) const { || soundUnknown(peer); } +bool NotifySettings::settingsUnknown( + not_null topic) const { + return muteUnknown(topic) || soundUnknown(topic); +} + rpl::producer<> NotifySettings::defaultUpdates(DefaultNotify type) const { return defaultValue(type).updates.events(); } diff --git a/Telegram/SourceFiles/data/notify/data_notify_settings.h b/Telegram/SourceFiles/data/notify/data_notify_settings.h index a1a3dfd173..b07dd2f1e3 100644 --- a/Telegram/SourceFiles/data/notify/data_notify_settings.h +++ b/Telegram/SourceFiles/data/notify/data_notify_settings.h @@ -17,6 +17,7 @@ namespace Data { class DocumentMedia; class Session; +class ForumTopic; enum class DefaultNotify { User, @@ -29,9 +30,15 @@ public: NotifySettings(not_null owner); void request(not_null peer); + void request(not_null topic); void apply( const MTPNotifyPeer ¬ifyPeer, const MTPPeerNotifySettings &settings); + void update( + not_null topic, + Data::MuteValue muteForSeconds, + std::optional sound = std::nullopt); + void resetToDefault(not_null topic); void update( not_null peer, Data::MuteValue muteForSeconds, @@ -57,6 +64,15 @@ public: std::optional silentPosts = std::nullopt, std::optional sound = std::nullopt); + [[nodiscard]] bool isMuted( + not_null topic) const; + [[nodiscard]] NotifySound sound( + not_null topic) const; + [[nodiscard]] bool muteUnknown( + not_null topic) const; + [[nodiscard]] bool soundUnknown( + not_null topic) const; + [[nodiscard]] bool isMuted(not_null peer) const; [[nodiscard]] bool silentPosts(not_null peer) const; [[nodiscard]] NotifySound sound(not_null peer) const; @@ -73,6 +89,9 @@ private: void cacheSound(const std::optional &sound); + [[nodiscard]] bool isMuted( + not_null peer, + crl::time *changesIn) const; [[nodiscard]] bool isMuted( not_null peer, crl::time *changesIn) const; @@ -82,9 +101,12 @@ private: [[nodiscard]] const PeerNotifySettings &defaultSettings( not_null peer) const; [[nodiscard]] bool settingsUnknown(not_null peer) const; + [[nodiscard]] bool settingsUnknown( + not_null topic) const; void unmuteByFinished(); void unmuteByFinishedDelayed(crl::time delay); + void updateLocal(not_null topic); void updateLocal(not_null peer); void updateLocal(DefaultNotify type); @@ -92,6 +114,9 @@ private: DefaultValue _defaultValues[3]; std::unordered_set> _mutedPeers; + std::unordered_map< + not_null, + rpl::lifetime> _mutedTopics; base::Timer _unmuteByFinishedTimer; struct { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index b0ece2b777..13edacfb74 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -74,7 +74,7 @@ History::History(not_null 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; diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index f164a421dc..3fb2617fb3 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -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; friend inline constexpr auto is_flag_type(Flag) { @@ -588,7 +589,6 @@ private: const std::unique_ptr _delegateMixin; Flags _flags = 0; - bool _mute = false; int _width = 0; int _height = 0; Element *_unreadBarView = nullptr; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 5f47c6f89c..153c86dc97 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -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, diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp index 8653814152..b82b6fa215 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.cpp +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.cpp @@ -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 item) { _readRequestTimer.callOnce(0); } } + if (_topic) { + Core::App().notifications().clearIncomingFromTopic(_topic); + } } void RepliesWidget::listMarkReadTill(not_null item) { diff --git a/Telegram/SourceFiles/history/view/history_view_replies_section.h b/Telegram/SourceFiles/history/view/history_view_replies_section.h index b6a20bff3b..9bf999af52 100644 --- a/Telegram/SourceFiles/history/view/history_view_replies_section.h +++ b/Telegram/SourceFiles/history/view/history_view_replies_section.h @@ -320,7 +320,7 @@ private: }; -class RepliesMemento : public Window::SectionMemento { +class RepliesMemento final : public Window::SectionMemento { public: RepliesMemento( not_null history, diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index b31065ab80..3ef5d97294 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -180,6 +180,23 @@ rpl::producer LocationValue( }); } +rpl::producer NotificationsEnabledValue( + not_null 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 NotificationsEnabledValue(not_null peer) { return rpl::merge( peer->session().changes().peerFlagsValue( diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 9f73c40531..6a011a64ed 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -62,6 +62,8 @@ rpl::producer> MigratedOrMeValue( not_null channel); [[nodiscard]] rpl::producer NotificationsEnabledValue( not_null peer); +[[nodiscard]] rpl::producer NotificationsEnabledValue( + not_null topic); [[nodiscard]] rpl::producer IsContactValue(not_null user); [[nodiscard]] rpl::producer InviteToChatButton( not_null user); diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp index 1eb4d8b804..91a63daa14 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.cpp @@ -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 item); + void clearFromTopic(not_null topic); void clearFromHistory(not_null history); void clearFromSession(not_null session); void clearNotification(NotificationId id); - bool inhibited() { + [[nodiscard]] bool inhibited() const { return _inhibited; } @@ -808,7 +810,7 @@ private: const not_null _manager; base::flat_map< - FullPeer, + ContextId, base::flat_map> _notifications; Window::Notifications::CachedUserpics _cachedUserpics; @@ -892,17 +894,22 @@ Manager::Private::Private(not_null manager, Type type) void Manager::Private::showNotification( not_null peer, + MsgId topicRootId, std::shared_ptr &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( _manager, notificationId); @@ -951,9 +958,10 @@ void Manager::Private::clearAll() { } void Manager::Private::clearFromItem(not_null 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 item) { taken->close(); } -void Manager::Private::clearFromHistory(not_null history) { - const auto key = FullPeer{ - .sessionId = history->session().uniqueId(), - .peerId = history->peer->id +void Manager::Private::clearFromTopic(not_null 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) { } } +void Manager::Private::clearFromHistory(not_null 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 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 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 item) { _private->clearFromItem(item); } +void Manager::doClearFromTopic(not_null topic) { + _private->clearFromTopic(topic); +} + void Manager::doClearFromHistory(not_null history) { _private->clearFromHistory(history); } diff --git a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h index 4dcc870dfb..2212fa93b5 100644 --- a/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h +++ b/Telegram/SourceFiles/platform/linux/notifications_manager_linux.h @@ -29,6 +29,7 @@ protected: DisplayOptions options) override; void doClearAllFast() override; void doClearFromItem(not_null item) override; + void doClearFromTopic(not_null topic) override; void doClearFromHistory(not_null history) override; void doClearFromSession(not_null session) override; bool doSkipAudio() const override; diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h index 50bb841001..718adc1e01 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.h @@ -21,6 +21,7 @@ public: protected: void doShowNativeNotification( not_null peer, + MsgId topicRootId, std::shared_ptr &userpicView, MsgId msgId, const QString &title, @@ -29,6 +30,7 @@ protected: DisplayOptions options) override; void doClearAllFast() override; void doClearFromItem(not_null item) override; + void doClearFromTopic(not_null topic) override; void doClearFromHistory(not_null history) override; void doClearFromSession(not_null session) override; QString accountNameSeparator() override; diff --git a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm index 920f190a30..ebff047e3b 100644 --- a/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm +++ b/Telegram/SourceFiles/platform/mac/notifications_manager_mac.mm @@ -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 peer, + MsgId topicRootId, std::shared_ptr &userpicView, MsgId msgId, const QString &title, @@ -188,6 +196,7 @@ public: DisplayOptions options); void clearAll(); void clearFromItem(not_null item); + void clearFromTopic(not_null topic); void clearFromHistory(not_null history); void clearFromSession(not_null 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 peer, + MsgId topicRootId, std::shared_ptr &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(); - auto clearFromPeers = base::flat_set(); + auto clearFromTopics = base::flat_set(); + auto clearFromHistories = base::flat_set(); auto clearFromSessions = base::flat_set(); { std::unique_lock 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 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 topic) { + putClearTask(ClearFromTopic{ ContextId{ + .sessionId = topic->session().uniqueId(), + .peerId = topic->history()->peer->id, + .topicRootId = topic->rootId(), + } }); +} + void Manager::Private::clearFromHistory(not_null 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 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 peer, + MsgId topicRootId, std::shared_ptr &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 item) { _private->clearFromItem(item); } +void Manager::doClearFromTopic(not_null topic) { + _private->clearFromTopic(topic); +} + void Manager::doClearFromHistory(not_null history) { _private->clearFromHistory(history); } diff --git a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm index b514b0013f..0e026ee696 100644 --- a/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm +++ b/Telegram/SourceFiles/platform/mac/touchbar/items/mac_pinned_chats_item.mm @@ -100,7 +100,7 @@ QImage UnreadBadge(not_null 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(); diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp index 97a7d2437d..a1f273da1f 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.cpp @@ -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 peer, + MsgId topicRootId, std::shared_ptr &userpicView, MsgId msgId, const QString &title, @@ -419,6 +421,7 @@ public: DisplayOptions options); void clearAll(); void clearFromItem(not_null item); + void clearFromTopic(not_null topic); void clearFromHistory(not_null history); void clearFromSession(not_null session); void beforeNotificationActivated(NotificationId id); @@ -434,6 +437,7 @@ public: private: bool showNotificationInTryCatch( not_null peer, + MsgId topicRootId, std::shared_ptr &userpicView, MsgId msgId, const QString &title, @@ -450,7 +454,7 @@ private: ToastNotifier _notifier = nullptr; base::flat_map< - FullPeer, + ContextId, base::flat_map> _notifications; rpl::lifetime _lifetime; @@ -496,9 +500,10 @@ void Manager::Private::clearFromItem(not_null 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 item) { tryHide(taken); } +void Manager::Private::clearFromTopic(not_null 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) { 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 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 peer, + MsgId topicRootId, std::shared_ptr &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 peer, + MsgId topicRootId, std::shared_ptr &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 peer, + MsgId topicRootId, std::shared_ptr &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 item) { _private->clearFromItem(item); } +void Manager::doClearFromTopic(not_null topic) { + _private->clearFromTopic(topic); +} + void Manager::doClearFromHistory(not_null history) { _private->clearFromHistory(history); } diff --git a/Telegram/SourceFiles/platform/win/notifications_manager_win.h b/Telegram/SourceFiles/platform/win/notifications_manager_win.h index 5ae34cc0f3..0eaaa2e833 100644 --- a/Telegram/SourceFiles/platform/win/notifications_manager_win.h +++ b/Telegram/SourceFiles/platform/win/notifications_manager_win.h @@ -29,6 +29,7 @@ public: protected: void doShowNativeNotification( not_null peer, + MsgId topicRootId, std::shared_ptr &userpicView, MsgId msgId, const QString &title, @@ -37,6 +38,7 @@ protected: DisplayOptions options) override; void doClearAllFast() override; void doClearFromItem(not_null item) override; + void doClearFromTopic(not_null topic) override; void doClearFromHistory(not_null history) override; void doClearFromSession(not_null session) override; void onBeforeNotificationActivated(NotificationId id) override; diff --git a/Telegram/SourceFiles/storage/storage_shared_media.cpp b/Telegram/SourceFiles/storage/storage_shared_media.cpp index e6ced75914..60ea325d43 100644 --- a/Telegram/SourceFiles/storage/storage_shared_media.cpp +++ b/Telegram/SourceFiles/storage/storage_shared_media.cpp @@ -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(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(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(); diff --git a/Telegram/SourceFiles/window/notifications_manager.cpp b/Telegram/SourceFiles/window/notifications_manager.cpp index 84aaaf22d5..a6a84c643d 100644 --- a/Telegram/SourceFiles/window/notifications_manager.cpp +++ b/Telegram/SourceFiles/window/notifications_manager.cpp @@ -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 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) { if (_manager) { _manager->clearFromHistory(history); @@ -381,6 +399,15 @@ void System::clearIncomingFromHistory(not_null history) { _whenAlerts.remove(history); } +void System::clearIncomingFromTopic(not_null topic) { + if (_manager) { + _manager->clearFromTopic(topic); + } + // #TODO forum notifications + //topic->clearIncomingNotifications(); + //_whenAlerts.remove(topic); +} + void System::clearFromItem(not_null 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, 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( + 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, diff --git a/Telegram/SourceFiles/window/notifications_manager.h b/Telegram/SourceFiles/window/notifications_manager.h index ae3259474f..ec71281f05 100644 --- a/Telegram/SourceFiles/window/notifications_manager.h +++ b/Telegram/SourceFiles/window/notifications_manager.h @@ -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 topic); void clearFromHistory(not_null history); + void clearIncomingFromTopic(not_null topic); void clearIncomingFromHistory(not_null history); void clearFromSession(not_null session); void clearFromItem(not_null 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 item; @@ -246,6 +247,9 @@ public: void clearFromItem(not_null item) { doClearFromItem(item); } + void clearFromTopic(not_null topic) { + doClearFromTopic(topic); + } void clearFromHistory(not_null history) { doClearFromHistory(history); } @@ -294,7 +298,7 @@ public: virtual ~Manager() = default; protected: - not_null system() const { + [[nodiscard]] not_null system() const { return _system; } @@ -303,6 +307,7 @@ protected: virtual void doClearAll() = 0; virtual void doClearAllFast() = 0; virtual void doClearFromItem(not_null item) = 0; + virtual void doClearFromTopic(not_null topic) = 0; virtual void doClearFromHistory(not_null history) = 0; virtual void doClearFromSession(not_null session) = 0; virtual bool doSkipAudio() const = 0; @@ -349,6 +354,7 @@ protected: virtual void doShowNativeNotification( not_null peer, + MsgId topicRootId, std::shared_ptr &userpicView, MsgId msgId, const QString &title, @@ -369,6 +375,7 @@ public: protected: void doShowNativeNotification( not_null peer, + MsgId topicRootId, std::shared_ptr &userpicView, MsgId msgId, const QString &title, @@ -380,6 +387,8 @@ protected: } void doClearFromItem(not_null item) override { } + void doClearFromTopic(not_null topic) override { + } void doClearFromHistory(not_null history) override { } void doClearFromSession(not_null session) override { diff --git a/Telegram/SourceFiles/window/notifications_manager_default.cpp b/Telegram/SourceFiles/window/notifications_manager_default.cpp index 4e04d4ec0a..0d526f8f8b 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.cpp +++ b/Telegram/SourceFiles/window/notifications_manager_default.cpp @@ -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( 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 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 ¬ification : _notifications) { + if (notification->unlinkHistory(history, topicRootId)) { + _positionsOutdated = true; + } + } + showNextFromQueue(); +} + void Manager::doClearFromHistory(not_null 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, not_null history, + MsgId topicRootId, not_null 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; diff --git a/Telegram/SourceFiles/window/notifications_manager_default.h b/Telegram/SourceFiles/window/notifications_manager_default.h index 28a9a9bfe6..6637695a8d 100644 --- a/Telegram/SourceFiles/window/notifications_manager_default.h +++ b/Telegram/SourceFiles/window/notifications_manager_default.h @@ -72,6 +72,7 @@ private: void doShowNotification(NotificationFields &&fields) override; void doClearAll() override; void doClearAllFast() override; + void doClearFromTopic(not_null topic) override; void doClearFromHistory(not_null history) override; void doClearFromSession(not_null session) override; void doClearFromItem(not_null item) override; @@ -112,6 +113,7 @@ private: QueuedNotification(NotificationFields &&fields); not_null history; + MsgId topicRootId = 0; not_null peer; Data::ReactionId reaction; QString author; @@ -205,6 +207,7 @@ public: Notification( not_null manager, not_null history, + MsgId topicRootId, not_null 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 session); bool checkLastInput( bool hasReplyingNotifications, @@ -282,6 +285,7 @@ private: crl::time _started; History *_history = nullptr; + MsgId _topicRootId = 0; std::shared_ptr _userpicView; QString _author; Data::ReactionId _reaction; diff --git a/Telegram/SourceFiles/window/window_peer_menu.cpp b/Telegram/SourceFiles/window/window_peer_menu.cpp index 0ab888f341..70637fd6df 100644 --- a/Telegram/SourceFiles/window/window_peer_menu.cpp +++ b/Telegram/SourceFiles/window/window_peer_menu.cpp @@ -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 }); diff --git a/Telegram/lib_base b/Telegram/lib_base index 8d9d8e86b9..2a94813b6a 160000 --- a/Telegram/lib_base +++ b/Telegram/lib_base @@ -1 +1 @@ -Subproject commit 8d9d8e86b9abea58ed5a218e60d9f79b47085c63 +Subproject commit 2a94813b6aa1f8645fd9de88a78981356259fab7