From 2c0b5b3210d31b1e6ee9e99cbb69587bcc849384 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 7 Oct 2022 17:56:07 +0400 Subject: [PATCH] Track unread mentions / reactions in topics. --- .../SourceFiles/api/api_unread_things.cpp | 75 ++++++++++++----- Telegram/SourceFiles/api/api_unread_things.h | 25 ++++-- Telegram/SourceFiles/data/data_changes.cpp | 29 +++++++ Telegram/SourceFiles/data/data_changes.h | 43 +++++++++- Telegram/SourceFiles/data/data_forum.cpp | 15 ++-- Telegram/SourceFiles/data/data_forum.h | 5 +- .../SourceFiles/data/data_forum_topic.cpp | 30 +++++-- Telegram/SourceFiles/data/data_forum_topic.h | 8 +- Telegram/SourceFiles/data/data_histories.h | 12 +-- .../SourceFiles/dialogs/dialogs_entry.cpp | 60 ++++++++++++-- Telegram/SourceFiles/dialogs/dialogs_entry.h | 26 +++++- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 9 +- Telegram/SourceFiles/history/history.cpp | 83 +++++++------------ Telegram/SourceFiles/history/history.h | 16 +--- Telegram/SourceFiles/history/history_item.cpp | 27 ++++-- Telegram/SourceFiles/history/history_item.h | 8 +- .../SourceFiles/history/history_message.cpp | 48 ++++++++--- .../history/history_unread_things.cpp | 74 +++++++++++------ .../history/history_unread_things.h | 15 +++- .../SourceFiles/history/history_widget.cpp | 2 +- Telegram/SourceFiles/menu/menu_send.cpp | 27 ++++-- Telegram/SourceFiles/menu/menu_send.h | 3 +- 22 files changed, 448 insertions(+), 192 deletions(-) diff --git a/Telegram/SourceFiles/api/api_unread_things.cpp b/Telegram/SourceFiles/api/api_unread_things.cpp index ee85597a17..2a4ced220e 100644 --- a/Telegram/SourceFiles/api/api_unread_things.cpp +++ b/Telegram/SourceFiles/api/api_unread_things.cpp @@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_peer.h" #include "data/data_channel.h" +#include "data/data_forum_topic.h" #include "data/data_session.h" #include "main/main_session.h" #include "history/history.h" @@ -23,6 +24,18 @@ constexpr auto kPreloadIfLess = 5; constexpr auto kFirstRequestLimit = 10; constexpr auto kNextRequestLimit = 100; + +[[nodiscard]] not_null ResolveHistory( + not_null entry) { + if (const auto history = entry->asHistory()) { + return history; + } + const auto topic = entry->asTopic(); + + Ensures(topic != nullptr); + return topic->history(); +} + } // namespace UnreadThings::UnreadThings(not_null api) : _api(api) { @@ -36,10 +49,11 @@ bool UnreadThings::trackReactions(PeerData *peer) const { return trackMentions(peer) || (peer && peer->isUser()); } -void UnreadThings::preloadEnough(History *history) { - if (!history) { +void UnreadThings::preloadEnough(DialogsEntry *entry) { + if (!entry) { return; } + const auto history = ResolveHistory(entry); if (trackMentions(history->peer)) { preloadEnoughMentions(history); } @@ -63,40 +77,53 @@ void UnreadThings::mediaAndMentionsRead( } } -void UnreadThings::preloadEnoughMentions(not_null history) { - const auto fullCount = history->unreadMentions().count(); - const auto loadedCount = history->unreadMentions().loadedCount(); +void UnreadThings::preloadEnoughMentions(not_null entry) { + const auto fullCount = entry->unreadMentions().count(); + const auto loadedCount = entry->unreadMentions().loadedCount(); const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount); if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) { - requestMentions(history, loadedCount); + requestMentions(entry, loadedCount); } } -void UnreadThings::preloadEnoughReactions(not_null history) { - const auto fullCount = history->unreadReactions().count(); - const auto loadedCount = history->unreadReactions().loadedCount(); +void UnreadThings::preloadEnoughReactions(not_null entry) { + const auto fullCount = entry->unreadReactions().count(); + const auto loadedCount = entry->unreadReactions().loadedCount(); const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount); if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) { - requestReactions(history, loadedCount); + requestReactions(entry, loadedCount); } } -void UnreadThings::requestMentions(not_null history, int loaded) { - if (_mentionsRequests.contains(history)) { +void UnreadThings::cancelRequests(not_null entry) { + if (const auto requestId = _mentionsRequests.take(entry)) { + _api->request(*requestId).cancel(); + } + if (const auto requestId = _reactionsRequests.take(entry)) { + _api->request(*requestId).cancel(); + } +} + +void UnreadThings::requestMentions( + not_null entry, + int loaded) { + if (_mentionsRequests.contains(entry)) { return; } - const auto topMsgId = 0; const auto offsetId = std::max( - history->unreadMentions().maxLoaded(), + entry->unreadMentions().maxLoaded(), MsgId(1)); const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; const auto addOffset = loaded ? -(limit + 1) : -limit; const auto maxId = 0; const auto minId = 0; + const auto history = ResolveHistory(entry); + const auto topic = entry->asTopic(); + using Flag = MTPmessages_GetUnreadMentions::Flag; const auto requestId = _api->request(MTPmessages_GetUnreadMentions( - MTP_flags(0), + MTP_flags(topic ? Flag::f_top_msg_id : Flag()), history->peer->input, - MTP_int(topMsgId), + MTP_int(topic ? topic->rootId() : 0), MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), @@ -111,22 +138,26 @@ void UnreadThings::requestMentions(not_null history, int loaded) { _mentionsRequests.emplace(history, requestId); } -void UnreadThings::requestReactions(not_null history, int loaded) { - if (_reactionsRequests.contains(history)) { +void UnreadThings::requestReactions( + not_null entry, + int loaded) { + if (_reactionsRequests.contains(entry)) { return; } - const auto topMsgId = 0; const auto offsetId = loaded - ? std::max(history->unreadReactions().maxLoaded(), MsgId(1)) + ? std::max(entry->unreadReactions().maxLoaded(), MsgId(1)) : MsgId(1); const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; const auto addOffset = loaded ? -(limit + 1) : -limit; const auto maxId = 0; const auto minId = 0; + const auto history = ResolveHistory(entry); + const auto topic = entry->asTopic(); + using Flag = MTPmessages_GetUnreadReactions::Flag; const auto requestId = _api->request(MTPmessages_GetUnreadReactions( - MTP_flags(0), + MTP_flags(topic ? Flag::f_top_msg_id : Flag()), history->peer->input, - MTP_int(topMsgId), + MTP_int(topic ? topic->rootId() : 0), MTP_int(offsetId), MTP_int(addOffset), MTP_int(limit), diff --git a/Telegram/SourceFiles/api/api_unread_things.h b/Telegram/SourceFiles/api/api_unread_things.h index 4f6f423645..2e8563d48d 100644 --- a/Telegram/SourceFiles/api/api_unread_things.h +++ b/Telegram/SourceFiles/api/api_unread_things.h @@ -7,37 +7,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once -class History; class ApiWrap; class PeerData; class ChannelData; +namespace Dialogs { +class Entry; +} // namespace Dialogs + namespace Api { class UnreadThings final { public: + using DialogsEntry = Dialogs::Entry; + explicit UnreadThings(not_null api); [[nodiscard]] bool trackMentions(PeerData *peer) const; [[nodiscard]] bool trackReactions(PeerData *peer) const; - void preloadEnough(History *history); + void preloadEnough(DialogsEntry *entry); void mediaAndMentionsRead( const base::flat_set &readIds, ChannelData *channel = nullptr); -private: - void preloadEnoughMentions(not_null history); - void preloadEnoughReactions(not_null history); + void cancelRequests(not_null entry); - void requestMentions(not_null history, int loaded); - void requestReactions(not_null history, int loaded); +private: + void preloadEnoughMentions(not_null entry); + void preloadEnoughReactions(not_null entry); + + void requestMentions(not_null entry, int loaded); + void requestReactions(not_null entry, int loaded); const not_null _api; - base::flat_map, mtpRequestId> _mentionsRequests; - base::flat_map, mtpRequestId> _reactionsRequests; + base::flat_map, mtpRequestId> _mentionsRequests; + base::flat_map, mtpRequestId> _reactionsRequests; }; diff --git a/Telegram/SourceFiles/data/data_changes.cpp b/Telegram/SourceFiles/data/data_changes.cpp index 3f4518b5c4..58f973949e 100644 --- a/Telegram/SourceFiles/data/data_changes.cpp +++ b/Telegram/SourceFiles/data/data_changes.cpp @@ -163,6 +163,35 @@ rpl::producer Changes::realtimeHistoryUpdates( return _historyChanges.realtimeUpdates(flag); } +void Changes::topicUpdated( + not_null topic, + TopicUpdate::Flags flags) { + _topicChanges.updated(topic, flags); + scheduleNotifications(); +} + +rpl::producer Changes::topicUpdates( + TopicUpdate::Flags flags) const { + return _topicChanges.updates(flags); +} + +rpl::producer Changes::topicUpdates( + not_null topic, + TopicUpdate::Flags flags) const { + return _topicChanges.updates(topic, flags); +} + +rpl::producer Changes::topicFlagsValue( + not_null topic, + TopicUpdate::Flags flags) const { + return _topicChanges.flagsValue(topic, flags); +} + +rpl::producer Changes::realtimeTopicUpdates( + TopicUpdate::Flag flag) const { + return _topicChanges.realtimeUpdates(flag); +} + void Changes::messageUpdated( not_null item, MessageUpdate::Flags flags) { diff --git a/Telegram/SourceFiles/data/data_changes.h b/Telegram/SourceFiles/data/data_changes.h index f0a2f52907..5f53340579 100644 --- a/Telegram/SourceFiles/data/data_changes.h +++ b/Telegram/SourceFiles/data/data_changes.h @@ -21,9 +21,7 @@ namespace Main { class Session; } // namespace Main -namespace Data { - -namespace details { +namespace Data::details { template inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) { @@ -35,7 +33,11 @@ inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) { return i; } -} // namespace details +} // namespace Data::details + +namespace Data { + +class ForumTopic; struct NameUpdate { NameUpdate( @@ -139,6 +141,24 @@ struct HistoryUpdate { }; +struct TopicUpdate { + enum class Flag : uint32 { + None = 0, + + UnreadView = (1U << 1), + UnreadMentions = (1U << 2), + UnreadReactions = (1U << 3), + + LastUsedBit = (1U << 3), + }; + using Flags = base::flags; + friend inline constexpr auto is_flag_type(Flag) { return true; } + + not_null topic; + Flags flags = 0; + +}; + struct MessageUpdate { enum class Flag : uint32 { None = 0, @@ -219,6 +239,20 @@ public: [[nodiscard]] rpl::producer realtimeHistoryUpdates( HistoryUpdate::Flag flag) const; + void topicUpdated( + not_null topic, + TopicUpdate::Flags flags); + [[nodiscard]] rpl::producer topicUpdates( + TopicUpdate::Flags flags) const; + [[nodiscard]] rpl::producer topicUpdates( + not_null topic, + TopicUpdate::Flags flags) const; + [[nodiscard]] rpl::producer topicFlagsValue( + not_null topic, + TopicUpdate::Flags flags) const; + [[nodiscard]] rpl::producer realtimeTopicUpdates( + TopicUpdate::Flag flag) const; + void messageUpdated( not_null item, MessageUpdate::Flags flags); @@ -292,6 +326,7 @@ private: rpl::event_stream _nameStream; Manager _peerChanges; Manager _historyChanges; + Manager _topicChanges; Manager _messageChanges; Manager _entryChanges; diff --git a/Telegram/SourceFiles/data/data_forum.cpp b/Telegram/SourceFiles/data/data_forum.cpp index 5a107ce944..29cd4a6759 100644 --- a/Telegram/SourceFiles/data/data_forum.cpp +++ b/Telegram/SourceFiles/data/data_forum.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_unread_things.h" #include "main/main_session.h" #include "base/random.h" #include "apiwrap.h" @@ -188,12 +189,6 @@ void Forum::applyTopicAdded( } } -void Forum::applyTopicRemoved(MsgId rootId) { - //if (const auto i = _topics.find(rootId)) { - // _topics.erase(i); - //} -} - MsgId Forum::reserveCreatingId( const QString &title, int32 colorId, @@ -238,7 +233,13 @@ void Forum::created(MsgId rootId, MsgId realId) { owner().notifyItemIdChange({ id, rootId }); } -ForumTopic *Forum::topicFor(not_null item) { +void Forum::clearAllUnreadMentions() { + for (const auto &[rootId, topic] : _topics) { + topic->unreadMentions().clear(); + } +} + +ForumTopic *Forum::topicFor(not_null item) { const auto maybe = topicFor(item->replyToTop()); return maybe ? maybe : topicFor(item->topicRootId()); } diff --git a/Telegram/SourceFiles/data/data_forum.h b/Telegram/SourceFiles/data/data_forum.h index 5a486f996b..f7ab2c8828 100644 --- a/Telegram/SourceFiles/data/data_forum.h +++ b/Telegram/SourceFiles/data/data_forum.h @@ -45,9 +45,8 @@ public: const QString &title, int32 colorId, DocumentId iconId); - void applyTopicRemoved(MsgId rootId); void applyTopicCreated(MsgId rootId, MsgId realId); - [[nodiscard]] ForumTopic *topicFor(not_null item); + [[nodiscard]] ForumTopic *topicFor(not_null item); [[nodiscard]] ForumTopic *topicFor(MsgId rootId); void applyReceivedTopics(const MTPmessages_ForumTopics &topics); @@ -60,6 +59,8 @@ public: [[nodiscard]] bool creating(MsgId rootId) const; void created(MsgId rootId, MsgId realId); + void clearAllUnreadMentions(); + private: struct TopicRequest { mtpRequestId id = 0; diff --git a/Telegram/SourceFiles/data/data_forum_topic.cpp b/Telegram/SourceFiles/data/data_forum_topic.cpp index 88dc6196e8..eff99d5c93 100644 --- a/Telegram/SourceFiles/data/data_forum_topic.cpp +++ b/Telegram/SourceFiles/data/data_forum_topic.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_forum_topic.h" #include "data/data_channel.h" +#include "data/data_changes.h" #include "data/data_forum.h" #include "data/data_histories.h" #include "data/data_replies_list.h" @@ -17,9 +18,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "dialogs/ui/dialogs_layout.h" #include "core/application.h" #include "core/core_settings.h" +#include "apiwrap.h" +#include "api/api_unread_things.h" #include "history/history.h" #include "history/history_item.h" +#include "history/history_unread_things.h" #include "history/view/history_view_item_preview.h" +#include "main/main_session.h" #include "ui/painter.h" #include "ui/color_int_conversion.h" #include "styles/style_dialogs.h" @@ -28,6 +33,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Data { +namespace { + +using UpdateFlag = TopicUpdate::Flag; + +} // namespace const base::flat_map &ForumTopicIcons() { static const auto Result = base::flat_map{ @@ -135,6 +145,7 @@ ForumTopic::ForumTopic(not_null history, MsgId rootId) _replies->unreadCountValue( ) | rpl::combine_previous( ) | rpl::filter([=] { + session().changes().topicUpdated(this, UpdateFlag::UnreadView); return inChatList(); }) | rpl::start_with_next([=]( std::optional previous, @@ -145,7 +156,9 @@ ForumTopic::ForumTopic(not_null history, MsgId rootId) }, _replies->lifetime()); } -ForumTopic::~ForumTopic() = default; +ForumTopic::~ForumTopic() { + session().api().unreadThings().cancelRequests(this); +} std::shared_ptr ForumTopic::replies() const { return _replies; @@ -203,6 +216,8 @@ void ForumTopic::applyTopic(const MTPForumTopic &topic) { #if 0 // #TODO forum unread mark setUnreadMark(data.is_unread_mark()); #endif + unreadMentions().setCount(data.vunread_mentions_count().v); + unreadReactions().setCount(data.vunread_reactions_count().v); } void ForumTopic::indexTitleParts() { @@ -470,7 +485,7 @@ bool ForumTopic::unreadCountKnown() const { } void ForumTopic::setUnreadMark(bool unread) { - if (_unreadMark == unread) { + if (unreadMark() == unread) { return; } const auto noUnreadMessages = !unreadCount(); @@ -478,13 +493,18 @@ void ForumTopic::setUnreadMark(bool unread) { if (inChatList() && noUnreadMessages) { updateChatListEntry(); } + session().changes().topicUpdated(this, UpdateFlag::UnreadView); }); const auto notifier = unreadStateChangeNotifier(noUnreadMessages); - _unreadMark = unread; + if (unread) { + _flags |= Flag::UnreadMark; + } else { + _flags &= ~Flag::UnreadMark; + } } bool ForumTopic::unreadMark() const { - return _unreadMark; + return (_flags & Flag::UnreadMark); } int ForumTopic::chatListUnreadCount() const { @@ -503,7 +523,7 @@ Dialogs::UnreadState ForumTopic::unreadStateFor( int count, bool known) const { auto result = Dialogs::UnreadState(); - const auto mark = !count && _unreadMark; + const auto mark = !count && unreadMark(); const auto muted = _history->mute(); result.messages = count; result.messagesMuted = muted ? count : 0; diff --git a/Telegram/SourceFiles/data/data_forum_topic.h b/Telegram/SourceFiles/data/data_forum_topic.h index 3cb0af98d1..deeef3fecb 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 "base/flags.h" class ChannelData; @@ -111,6 +112,11 @@ public: Dialogs::Ui::MessageView lastItemDialogsView; private: + enum class Flag : uchar { + UnreadMark = 0x01, + }; + friend inline constexpr bool is_flag_type(Flag) { return true; } + void indexTitleParts(); void validateDefaultIcon() const; void applyTopicTopMessage(MsgId topMessageId); @@ -144,7 +150,7 @@ private: std::optional _lastServerMessage; std::optional _chatListMessage; base::flat_set _requestedGroups; - bool _unreadMark = false; + base::flags _flags; rpl::lifetime _lifetime; diff --git a/Telegram/SourceFiles/data/data_histories.h b/Telegram/SourceFiles/data/data_histories.h index d54f2a3bf7..780867f3cd 100644 --- a/Telegram/SourceFiles/data/data_histories.h +++ b/Telegram/SourceFiles/data/data_histories.h @@ -156,18 +156,18 @@ private: not_null history; MsgId rootId = 0; - friend inline constexpr auto operator<=>( + friend inline auto operator<=>( GroupRequestKey, GroupRequestKey) = default; }; template static auto ReplaceReplyTo(Arg arg, MsgId replyTo) { - return arg; - } - template <> - static auto ReplaceReplyTo(ReplyToPlaceholder, MsgId replyTo) { - return MTP_int(replyTo); + if constexpr (std::is_same_v) { + return MTP_int(replyTo); + } else { + return arg; + } } void readInboxTill(not_null history, MsgId tillId, bool force); diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 0b42bcd5b7..246dfd8b57 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -18,8 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "main/main_session.h" #include "main/main_session_settings.h" #include "ui/text/text_options.h" -#include "history/history_item.h" #include "history/history.h" +#include "history/history_item.h" +#include "history/history_unread_things.h" #include "styles/style_dialogs.h" // st::dialogsTextWidthMin namespace Dialogs { @@ -49,6 +50,8 @@ Entry::Entry(not_null owner, Type type) , _type(type) { } +Entry::~Entry() = default; + Data::Session &Entry::owner() const { return *_owner; } @@ -102,19 +105,30 @@ void Entry::cachePinnedIndex(FilterId filterId, int index) { } void Entry::cacheTopPromoted(bool promoted) { - if (_isTopPromoted == promoted) { + if (isTopPromoted() == promoted) { return; + } else if (promoted) { + _flags |= Flag::IsTopPromoted; + } else { + _flags &= ~Flag::IsTopPromoted; } - _isTopPromoted = promoted; updateChatListSortPosition(); updateChatListEntry(); - if (!_isTopPromoted) { + if (!isTopPromoted()) { updateChatListExistence(); } } bool Entry::isTopPromoted() const { - return _isTopPromoted; + return (_flags & Flag::IsTopPromoted); +} + +const base::flat_set &Entry::unreadMentionsIds() const { + if (!_unreadThings) { + static const auto Result = base::flat_set(); + return Result; + } + return _unreadThings->mentions.ids(); } bool Entry::needUpdateInChatList() const { @@ -198,6 +212,42 @@ TimeId Entry::adjustedChatListTimeId() const { return chatListTimeId(); } +void Entry::setUnreadThingsKnown() { + _flags |= Flag::UnreadThingsKnown; +} + +HistoryUnreadThings::Proxy Entry::unreadMentions() { + return { + this, + _unreadThings, + HistoryUnreadThings::Type::Mentions, + !!(_flags & Flag::UnreadThingsKnown), + }; +} + +HistoryUnreadThings::ConstProxy Entry::unreadMentions() const { + return { + _unreadThings ? &_unreadThings->mentions : nullptr, + !!(_flags & Flag::UnreadThingsKnown), + }; +} + +HistoryUnreadThings::Proxy Entry::unreadReactions() { + return { + this, + _unreadThings, + HistoryUnreadThings::Type::Reactions, + !!(_flags & Flag::UnreadThingsKnown), + }; +} + +HistoryUnreadThings::ConstProxy Entry::unreadReactions() const { + return { + _unreadThings ? &_unreadThings->reactions : nullptr, + !!(_flags & Flag::UnreadThingsKnown), + }; +} + void Entry::changedChatListPinHook() { } diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.h b/Telegram/SourceFiles/dialogs/dialogs_entry.h index 6d9ba5b860..73ccb4b47f 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.h +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.h @@ -23,6 +23,13 @@ class ForumTopic; class CloudImageView; } // namespace Data +namespace HistoryUnreadThings { +enum class AddType; +struct All; +class Proxy; +class ConstProxy; +} // namespace HistoryUnreadThings + namespace Ui { } // namespace Ui @@ -108,7 +115,7 @@ public: Entry(not_null owner, Type type); Entry(const Entry &other) = delete; Entry &operator=(const Entry &other) = delete; - virtual ~Entry() = default; + virtual ~Entry(); [[nodiscard]] Data::Session &owner() const; [[nodiscard]] Main::Session &session() const; @@ -154,6 +161,12 @@ public: bool needUpdateInChatList() const; virtual TimeId adjustedChatListTimeId() const; + void setUnreadThingsKnown(); + [[nodiscard]] HistoryUnreadThings::Proxy unreadMentions(); + [[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const; + [[nodiscard]] HistoryUnreadThings::Proxy unreadReactions(); + [[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const; + virtual int fixedOnTopIndex() const = 0; static constexpr auto kArchiveFixOnTopIndex = 1; static constexpr auto kTopPromotionFixOnTopIndex = 2; @@ -209,7 +222,15 @@ protected: void cacheTopPromoted(bool promoted); + [[nodiscard]] const base::flat_set &unreadMentionsIds() const; + private: + enum class Flag : uchar { + IsTopPromoted = 0x01, + UnreadThingsKnown = 0x02, + }; + friend inline constexpr bool is_flag_type(Flag) { return true; } + virtual void changedChatListPinHook(); void pinnedIndexChanged(FilterId filterId, int was, int now); [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; @@ -225,11 +246,12 @@ private: uint64 _sortKeyInChatList = 0; uint64 _sortKeyByDate = 0; base::flat_map _pinnedIndex; + std::unique_ptr _unreadThings; mutable Ui::PeerBadge _chatListBadge; mutable Ui::Text::String _chatListNameText; mutable int _chatListNameVersion = 0; TimeId _timeId = 0; - bool _isTopPromoted = false; + base::flags _flags; const Type _type; }; diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index 046a8b7e01..7d70fe021a 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -916,11 +916,12 @@ void RowPainter::Paint( ? base::unixtime::parse(cloudDraft->date) : QDateTime(); }(); - const auto displayMentionBadge = history - && history->unreadMentions().has(); + const auto displayMentionBadge = (history + && history->unreadMentions().has()) + || (topic && topic->unreadMentions().has()); const auto displayReactionBadge = !displayMentionBadge - && history - && history->unreadReactions().has(); + && ((history && history->unreadReactions().has()) + || (topic && topic->unreadReactions().has())); const auto mentionOrReactionMuted = (entry->folder() != nullptr) || (!displayMentionBadge && unreadMuted); const auto displayUnreadCounter = [&] { diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 647d276628..faa4105001 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -161,14 +161,8 @@ void History::itemRemoved(not_null item) { if (IsClientMsgId(item->id)) { unregisterClientSideMessage(item); } - if (const auto forum = peer->forum()) { - if (const auto topic = forum->topicFor(item)) { - if (topic->rootId() == item->id) { - forum->applyTopicRemoved(item->id); - } else { - topic->applyItemRemoved(item->id); - } - } + if (const auto topic = item->topic()) { + topic->applyItemRemoved(item->id); } if (const auto chat = peer->asChat()) { if (const auto to = chat->getMigrateToChannel()) { @@ -705,40 +699,25 @@ not_null History::addNewLocalMessage( true); } -void History::setUnreadThingsKnown() { - _flags |= Flag::UnreadThingsKnown; -} - -HistoryUnreadThings::Proxy History::unreadMentions() { - return { - this, - _unreadThings, - HistoryUnreadThings::Type::Mentions, - !!(_flags & Flag::UnreadThingsKnown), - }; -} - -HistoryUnreadThings::ConstProxy History::unreadMentions() const { - return { - _unreadThings ? &_unreadThings->mentions : nullptr, - !!(_flags & Flag::UnreadThingsKnown), - }; -} - -HistoryUnreadThings::Proxy History::unreadReactions() { - return { - this, - _unreadThings, - HistoryUnreadThings::Type::Reactions, - !!(_flags & Flag::UnreadThingsKnown), - }; -} - -HistoryUnreadThings::ConstProxy History::unreadReactions() const { - return { - _unreadThings ? &_unreadThings->reactions : nullptr, - !!(_flags & Flag::UnreadThingsKnown), - }; +void History::clearUnreadMentionsFor(MsgId topicRootId) { + const auto &ids = unreadMentionsIds(); + if (ids.empty()) { + return; + } + const auto owner = &this->owner(); + const auto peerId = peer->id; + auto items = base::flat_set(); + items.reserve(ids.size()); + for (const auto &id : ids) { + if (const auto item = owner->message(peerId, id)) { + if (item->topicRootId() == topicRootId) { + items.emplace(id); + } + } + } + for (const auto &id : items) { + unreadMentions().erase(id); + } } not_null History::addNewToBack( @@ -1084,14 +1063,12 @@ void History::applyServiceChanges( data.vicon_emoji_id().value_or(DocumentId())); } }, [&](const MTPDmessageActionTopicEdit &data) { - if (const auto forum = peer->forum()) { - if (const auto topic = forum->topicFor(item)) { - if (const auto &title = data.vtitle()) { - topic->applyTitle(qs(*title)); - } - if (const auto icon = data.vicon_emoji_id()) { - topic->applyIconId(icon->v); - } + if (const auto topic = item->topic()) { + if (const auto &title = data.vtitle()) { + topic->applyTitle(qs(*title)); + } + if (const auto icon = data.vicon_emoji_id()) { + topic->applyIconId(icon->v); } } }, [](const auto &) { @@ -1154,10 +1131,8 @@ void History::newItemAdded(not_null item) { if (!folderKnown()) { owner().histories().requestDialogEntry(this); } - if (const auto forum = peer->forum()) { - if (const auto topic = forum->topicFor(item)) { - topic->applyItemAdded(item); - } + if (const auto topic = item->topic()) { + topic->applyItemAdded(item); } } diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index 2c778017c7..15d842bdbc 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -27,13 +27,6 @@ class HistoryService; struct HistoryMessageMarkupData; class HistoryMainElementDelegateMixin; -namespace HistoryUnreadThings { -enum class AddType; -struct All; -class Proxy; -class ConstProxy; -} // namespace HistoryUnreadThings - namespace Main { class Session; } // namespace Main @@ -337,12 +330,7 @@ public: } void clearLastKeyboard(); - - void setUnreadThingsKnown(); - [[nodiscard]] HistoryUnreadThings::Proxy unreadMentions(); - [[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const; - [[nodiscard]] HistoryUnreadThings::Proxy unreadReactions(); - [[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const; + void clearUnreadMentionsFor(MsgId topicRootId); Data::Draft *draft(Data::DraftKey key) const; void setDraft(Data::DraftKey key, std::unique_ptr &&draft); @@ -483,7 +471,6 @@ private: enum class Flag : uchar { HasPendingResizedItems = (1 << 0), - UnreadThingsKnown = (1 << 1), }; using Flags = base::flags; friend inline constexpr auto is_flag_type(Flag) { @@ -618,7 +605,6 @@ private: std::optional _lastServerMessage; base::flat_set> _clientSideMessages; std::unordered_set> _messages; - std::unique_ptr _unreadThings; // This almost always is equal to _lastMessage. The only difference is // for a group that migrated to a supergroup. Then _lastMessage can diff --git a/Telegram/SourceFiles/history/history_item.cpp b/Telegram/SourceFiles/history/history_item.cpp index 5771ac7db6..89afaf60fc 100644 --- a/Telegram/SourceFiles/history/history_item.cpp +++ b/Telegram/SourceFiles/history/history_item.cpp @@ -375,10 +375,8 @@ void HistoryItem::invalidateChatListEntry() { this, Data::MessageUpdate::Flag::DialogRowRefresh); history()->lastItemDialogsView.itemInvalidated(this); - if (const auto forum = history()->peer->forum()) { - if (const auto topic = forum->topicFor(this)) { - topic->lastItemDialogsView.itemInvalidated(this); - } + if (const auto topic = this->topic()) { + topic->lastItemDialogsView.itemInvalidated(this); } } @@ -442,8 +440,12 @@ void HistoryItem::markMediaAndMentionRead() { _flags &= ~MessageFlag::MediaIsUnread; if (mentionsMe()) { - history()->updateChatListEntry(); - history()->unreadMentions().erase(id); + _history->updateChatListEntry(); + _history->unreadMentions().erase(id); + if (const auto topic = this->topic()) { + topic->updateChatListEntry(); + topic->unreadMentions().erase(id); + } } } @@ -452,8 +454,12 @@ void HistoryItem::markReactionsRead() { _reactions->markRead(); } _flags &= ~MessageFlag::HasUnreadReaction; - history()->updateChatListEntry(); - history()->unreadReactions().erase(id); + _history->updateChatListEntry(); + _history->unreadReactions().erase(id); + if (const auto topic = this->topic()) { + topic->updateChatListEntry(); + topic->unreadReactions().erase(id); + } } bool HistoryItem::markContentsRead(bool fromThisClient) { @@ -613,6 +619,11 @@ void HistoryItem::destroy() { _history->destroyMessage(this); } +Data::ForumTopic *HistoryItem::topic() const { + const auto forum = _history->peer->forum(); + return forum ? forum->topicFor(this) : nullptr; +} + void HistoryItem::refreshMainView() { if (const auto view = mainView()) { _history->owner().notifyHistoryChangeDelayed(_history); diff --git a/Telegram/SourceFiles/history/history_item.h b/Telegram/SourceFiles/history/history_item.h index 03c8e3b025..db91f82aa4 100644 --- a/Telegram/SourceFiles/history/history_item.h +++ b/Telegram/SourceFiles/history/history_item.h @@ -46,6 +46,7 @@ struct ReactionId; class Media; struct MessageReaction; class MessageReactions; +class ForumTopic; } // namespace Data namespace Main { @@ -122,13 +123,14 @@ public: const QString &label, const TextWithEntities &content); - not_null history() const { + [[nodiscard]] not_null history() const { return _history; } - not_null from() const { + [[nodiscard]] Data::ForumTopic *topic() const; + [[nodiscard]] not_null from() const { return _from; } - HistoryView::Element *mainView() const { + [[nodiscard]] HistoryView::Element *mainView() const { return _mainView; } void setMainView(not_null view) { diff --git a/Telegram/SourceFiles/history/history_message.cpp b/Telegram/SourceFiles/history/history_message.cpp index 967aa3730f..ca0b084efd 100644 --- a/Telegram/SourceFiles/history/history_message.cpp +++ b/Telegram/SourceFiles/history/history_message.cpp @@ -1331,24 +1331,46 @@ void HistoryMessage::addToUnreadThings(HistoryUnreadThings::AddType type) { if (!isRegular()) { return; } - if (isUnreadMention()) { - if (history()->unreadMentions().add(id, type)) { - history()->session().changes().historyUpdated( - history(), + const auto mention = isUnreadMention(); + const auto reaction = hasUnreadReaction(); + if (!mention && !reaction) { + return; + } + const auto topic = this->topic(); + const auto history = this->history(); + const auto changes = &history->session().changes(); + if (mention) { + if (history->unreadMentions().add(id, type)) { + changes->historyUpdated( + history, Data::HistoryUpdate::Flag::UnreadMentions); } + if (topic && topic->unreadMentions().add(id, type)) { + changes->topicUpdated( + topic, + Data::TopicUpdate::Flag::UnreadMentions); + } } - if (hasUnreadReaction()) { - if (history()->unreadReactions().add(id, type)) { + if (reaction) { + const auto toHistory = history->unreadReactions().add(id, type); + const auto toTopic = topic && topic->unreadReactions().add(id, type); + if (toHistory || toTopic) { if (type == HistoryUnreadThings::AddType::New) { - history()->session().changes().messageUpdated( + changes->messageUpdated( this, Data::MessageUpdate::Flag::NewUnreadReaction); } if (hasUnreadReaction()) { - history()->session().changes().historyUpdated( - history(), - Data::HistoryUpdate::Flag::UnreadReactions); + if (toHistory) { + changes->historyUpdated( + history, + Data::HistoryUpdate::Flag::UnreadReactions); + } + if (toTopic) { + changes->topicUpdated( + topic, + Data::TopicUpdate::Flag::UnreadReactions); + } } } } @@ -1357,9 +1379,15 @@ void HistoryMessage::addToUnreadThings(HistoryUnreadThings::AddType type) { void HistoryMessage::destroyHistoryEntry() { if (isUnreadMention()) { history()->unreadMentions().erase(id); + if (const auto topic = this->topic()) { + topic->unreadMentions().erase(id); + } } if (hasUnreadReaction()) { history()->unreadReactions().erase(id); + if (const auto topic = this->topic()) { + topic->unreadReactions().erase(id); + } } if (const auto reply = Get()) { changeReplyToTopCounter(reply, -1); diff --git a/Telegram/SourceFiles/history/history_unread_things.cpp b/Telegram/SourceFiles/history/history_unread_things.cpp index 7a850a7189..4ce2fc0383 100644 --- a/Telegram/SourceFiles/history/history_unread_things.cpp +++ b/Telegram/SourceFiles/history/history_unread_things.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_session.h" #include "data/data_changes.h" #include "data/data_channel.h" +#include "data/data_forum_topic.h" #include "data/data_chat_filters.h" #include "history/history.h" #include "history/history_item.h" @@ -19,20 +20,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace HistoryUnreadThings { namespace { -[[nodiscard]] Data::HistoryUpdate::Flag UpdateFlag(Type type) { - using Flag = Data::HistoryUpdate::Flag; +template +[[nodiscard]] typename Update::Flag UpdateFlag(Type type) { + using Flag = typename Update::Flag; switch (type) { case Type::Mentions: return Flag::UnreadMentions; case Type::Reactions: return Flag::UnreadReactions; } - Unexpected("Type in Proxy::addSlice."); + Unexpected("Type in HistoryUnreadThings::UpdateFlag."); +} + +[[nodiscard]] Data::HistoryUpdate::Flag HistoryUpdateFlag(Type type) { + return UpdateFlag(type); +} + +[[nodiscard]] Data::TopicUpdate::Flag TopicUpdateFlag(Type type) { + return UpdateFlag(type); } } // namespace void Proxy::setCount(int count) { if (!_known) { - _history->setUnreadThingsKnown(); + _entry->setUnreadThingsKnown(); } if (!_data) { if (!count) { @@ -59,16 +69,19 @@ void Proxy::setCount(int count) { const auto has = (count > 0); if (has != had) { if (_type == Type::Mentions) { - _history->owner().chatsFilters().refreshHistory(_history); + if (const auto history = _entry->asHistory()) { + _entry->owner().chatsFilters().refreshHistory(history); + } } - _history->updateChatListEntry(); + _entry->updateChatListEntry(); } } bool Proxy::add(MsgId msgId, AddType type) { - const auto peer = _history->peer; - if (peer->isChannel() && !peer->isMegagroup()) { - return false; + if (const auto history = _entry->asHistory()) { + if (history->peer->isBroadcast()) { + return false; + } } if (!_data) { @@ -89,7 +102,6 @@ bool Proxy::add(MsgId msgId, AddType type) { return true; } return false; - } void Proxy::erase(MsgId msgId) { @@ -101,9 +113,7 @@ void Proxy::erase(MsgId msgId) { if (const auto count = list.count(); count > 0) { setCount(count - 1); } - _history->session().changes().historyUpdated( - _history, - UpdateFlag(_type)); + notifyUpdated(); } void Proxy::clear() { @@ -113,15 +123,14 @@ void Proxy::clear() { auto &list = resolveList(); list.clear(); setCount(0); - _history->session().changes().historyUpdated( - _history, - UpdateFlag(_type)); + notifyUpdated(); } void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { if (!alreadyLoaded && _data) { resolveList().clear(); } + const auto history = resolveHistory(); auto fullCount = slice.match([&]( const MTPDmessages_messagesNotModified &) { LOG(("API Error: received messages.messagesNotModified! " @@ -132,8 +141,8 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { }, [&](const MTPDmessages_messagesSlice &data) { return data.vcount().v; }, [&](const MTPDmessages_channelMessages &data) { - if (_history->peer->isChannel()) { - _history->peer->asChannel()->ptsReceived(data.vpts().v); + if (const auto channel = history->peer->asChannel()) { + channel->ptsReceived(data.vpts().v); } else { LOG(("API Error: received messages.channelMessages when " "no channel was passed! (Proxy::addSlice)")); @@ -141,7 +150,7 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { return data.vcount().v; }); - auto &owner = _history->owner(); + auto &owner = _entry->owner(); const auto messages = slice.match([&]( const MTPDmessages_messagesNotModified &) { LOG(("API Error: received messages.messagesNotModified! " @@ -160,7 +169,7 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { const auto localFlags = MessageFlags(); const auto type = NewMessageType::Existing; for (const auto &message : messages) { - const auto item = _history->addNewMessage( + const auto item = history->addNewMessage( IdFromMessage(message), message, localFlags, @@ -181,9 +190,7 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { fullCount = loadedCount(); } setCount(fullCount); - _history->session().changes().historyUpdated( - _history, - UpdateFlag(_type)); + notifyUpdated(); } void Proxy::checkAdd(MsgId msgId, bool resolved) { @@ -196,7 +203,7 @@ void Proxy::checkAdd(MsgId msgId, bool resolved) { if (!list.loadedCount() || list.maxLoaded() <= msgId) { return; } - const auto history = _history; + const auto history = resolveHistory(); const auto peer = history->peer; const auto item = peer->owner().message(peer, msgId); if (item && item->hasUnreadReaction()) { @@ -208,6 +215,18 @@ void Proxy::checkAdd(MsgId msgId, bool resolved) { } } +void Proxy::notifyUpdated() { + if (const auto history = _entry->asHistory()) { + history->session().changes().historyUpdated( + history, + HistoryUpdateFlag(_type)); + } else if (const auto topic = _entry->asTopic()) { + topic->session().changes().topicUpdated( + topic, + TopicUpdateFlag(_type)); + } +} + void Proxy::createData() { _data = std::make_unique(); if (_known) { @@ -216,7 +235,7 @@ void Proxy::createData() { } } -[[nodiscard]] List &Proxy::resolveList() { +List &Proxy::resolveList() { Expects(_data != nullptr); switch (_type) { @@ -226,4 +245,9 @@ void Proxy::createData() { Unexpected("Unread things type in Proxy::resolveList."); } +not_null Proxy::resolveHistory() const { + const auto result = _entry->asHistory(); + return result ? not_null(result) : _entry->asTopic()->history(); +} + } // namespace HistoryUnreadThings diff --git a/Telegram/SourceFiles/history/history_unread_things.h b/Telegram/SourceFiles/history/history_unread_things.h index 6dba48e0fe..431dd3d643 100644 --- a/Telegram/SourceFiles/history/history_unread_things.h +++ b/Telegram/SourceFiles/history/history_unread_things.h @@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL class History; +namespace Dialogs { +class Entry; +} // namespace Data + namespace HistoryUnreadThings { enum class AddType { @@ -41,6 +45,9 @@ public: [[nodiscard]] bool contains(MsgId msgId) const { return _messages.contains(msgId); } + [[nodiscard]] const base::flat_set &ids() const { + return _messages; + } void setCount(int count) { _count = count; } @@ -101,7 +108,7 @@ private: class Proxy final : public ConstProxy { public: Proxy( - not_null history, + not_null entry, std::unique_ptr &data, Type type, bool known) @@ -112,7 +119,7 @@ public: ? &data->mentions : &data->reactions), known) - , _history(history) + , _entry(entry) , _data(data) , _type(type) , _known(known) { @@ -129,9 +136,11 @@ public: private: void createData(); + void notifyUpdated(); [[nodiscard]] List &resolveList(); + [[nodiscard]] not_null resolveHistory() const; - const not_null _history; + const not_null _entry; std::unique_ptr &_data; Type _type = Type::Mentions; bool _known = false; diff --git a/Telegram/SourceFiles/history/history_widget.cpp b/Telegram/SourceFiles/history/history_widget.cpp index 1e2d8ceff7..e6f5e7d0fd 100644 --- a/Telegram/SourceFiles/history/history_widget.cpp +++ b/Telegram/SourceFiles/history/history_widget.cpp @@ -390,7 +390,7 @@ HistoryWidget::HistoryWidget( _unreadReactions.widget->installEventFilter(this); SendMenu::SetupUnreadMentionsMenu(_unreadMentions.widget.data(), [=] { return _history ? _history->peer.get() : nullptr; - }); + }, MsgId(0)); SendMenu::SetupUnreadReactionsMenu(_unreadReactions.widget.data(), [=] { return _history ? _history->peer.get() : nullptr; }); diff --git a/Telegram/SourceFiles/menu/menu_send.cpp b/Telegram/SourceFiles/menu/menu_send.cpp index 2fcd0c92d5..eddf8a84f6 100644 --- a/Telegram/SourceFiles/menu/menu_send.cpp +++ b/Telegram/SourceFiles/menu/menu_send.cpp @@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "lang/lang_keys.h" #include "ui/widgets/popup_menu.h" #include "data/data_peer.h" +#include "data/data_forum.h" +#include "data/data_forum_topic.h" #include "data/data_session.h" #include "main/main_session.h" #include "history/history.h" @@ -186,18 +188,33 @@ void SetupReadAllMenu( void SetupUnreadMentionsMenu( not_null button, - Fn currentPeer) { - const auto topMsgId = 0; + Fn currentPeer, + MsgId topicRootId) { const auto text = tr::lng_context_mark_read_mentions_all(tr::now); const auto sendRequest = [=](not_null peer, Fn done) { + using Flag = MTPmessages_ReadMentions::Flag; peer->session().api().request(MTPmessages_ReadMentions( - MTP_flags(0), + MTP_flags(topicRootId ? Flag::f_top_msg_id : Flag()), peer->input, - MTP_int(topMsgId) + MTP_int(topicRootId) )).done([=](const MTPmessages_AffectedHistory &result) { done(); peer->session().api().applyAffectedHistory(peer, result); - peer->owner().history(peer)->unreadMentions().clear(); + const auto forum = peer->forum(); + const auto history = peer->owner().history(peer); + if (!topicRootId) { + history->unreadMentions().clear(); + if (forum) { + forum->clearAllUnreadMentions(); + } + } else { + if (forum) { + if (const auto topic = forum->topicFor(topicRootId)) { + topic->unreadMentions().clear(); + } + } + history->clearUnreadMentionsFor(topicRootId); + } }).fail(done).send(); }; SetupReadAllMenu(button, currentPeer, text, sendRequest); diff --git a/Telegram/SourceFiles/menu/menu_send.h b/Telegram/SourceFiles/menu/menu_send.h index 997c84cbaf..b76c090fed 100644 --- a/Telegram/SourceFiles/menu/menu_send.h +++ b/Telegram/SourceFiles/menu/menu_send.h @@ -51,7 +51,8 @@ void SetupMenuAndShortcuts( void SetupUnreadMentionsMenu( not_null button, - Fn currentPeer); + Fn currentPeer, + MsgId topicRootId); void SetupUnreadReactionsMenu( not_null button,