Track unread mentions / reactions in topics.

This commit is contained in:
John Preston 2022-10-07 17:56:07 +04:00
parent 9348039313
commit 2c0b5b3210
22 changed files with 448 additions and 192 deletions

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "history/history.h" #include "history/history.h"
@ -23,6 +24,18 @@ constexpr auto kPreloadIfLess = 5;
constexpr auto kFirstRequestLimit = 10; constexpr auto kFirstRequestLimit = 10;
constexpr auto kNextRequestLimit = 100; constexpr auto kNextRequestLimit = 100;
[[nodiscard]] not_null<History*> ResolveHistory(
not_null<Dialogs::Entry*> entry) {
if (const auto history = entry->asHistory()) {
return history;
}
const auto topic = entry->asTopic();
Ensures(topic != nullptr);
return topic->history();
}
} // namespace } // namespace
UnreadThings::UnreadThings(not_null<ApiWrap*> api) : _api(api) { UnreadThings::UnreadThings(not_null<ApiWrap*> api) : _api(api) {
@ -36,10 +49,11 @@ bool UnreadThings::trackReactions(PeerData *peer) const {
return trackMentions(peer) || (peer && peer->isUser()); return trackMentions(peer) || (peer && peer->isUser());
} }
void UnreadThings::preloadEnough(History *history) { void UnreadThings::preloadEnough(DialogsEntry *entry) {
if (!history) { if (!entry) {
return; return;
} }
const auto history = ResolveHistory(entry);
if (trackMentions(history->peer)) { if (trackMentions(history->peer)) {
preloadEnoughMentions(history); preloadEnoughMentions(history);
} }
@ -63,40 +77,53 @@ void UnreadThings::mediaAndMentionsRead(
} }
} }
void UnreadThings::preloadEnoughMentions(not_null<History*> history) { void UnreadThings::preloadEnoughMentions(not_null<DialogsEntry*> entry) {
const auto fullCount = history->unreadMentions().count(); const auto fullCount = entry->unreadMentions().count();
const auto loadedCount = history->unreadMentions().loadedCount(); const auto loadedCount = entry->unreadMentions().loadedCount();
const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount); const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount);
if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) { if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) {
requestMentions(history, loadedCount); requestMentions(entry, loadedCount);
} }
} }
void UnreadThings::preloadEnoughReactions(not_null<History*> history) { void UnreadThings::preloadEnoughReactions(not_null<DialogsEntry*> entry) {
const auto fullCount = history->unreadReactions().count(); const auto fullCount = entry->unreadReactions().count();
const auto loadedCount = history->unreadReactions().loadedCount(); const auto loadedCount = entry->unreadReactions().loadedCount();
const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount); const auto allLoaded = (fullCount >= 0) && (loadedCount >= fullCount);
if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) { if (fullCount >= 0 && loadedCount < kPreloadIfLess && !allLoaded) {
requestReactions(history, loadedCount); requestReactions(entry, loadedCount);
} }
} }
void UnreadThings::requestMentions(not_null<History*> history, int loaded) { void UnreadThings::cancelRequests(not_null<DialogsEntry*> entry) {
if (_mentionsRequests.contains(history)) { 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<DialogsEntry*> entry,
int loaded) {
if (_mentionsRequests.contains(entry)) {
return; return;
} }
const auto topMsgId = 0;
const auto offsetId = std::max( const auto offsetId = std::max(
history->unreadMentions().maxLoaded(), entry->unreadMentions().maxLoaded(),
MsgId(1)); MsgId(1));
const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit;
const auto addOffset = loaded ? -(limit + 1) : -limit; const auto addOffset = loaded ? -(limit + 1) : -limit;
const auto maxId = 0; const auto maxId = 0;
const auto minId = 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( const auto requestId = _api->request(MTPmessages_GetUnreadMentions(
MTP_flags(0), MTP_flags(topic ? Flag::f_top_msg_id : Flag()),
history->peer->input, history->peer->input,
MTP_int(topMsgId), MTP_int(topic ? topic->rootId() : 0),
MTP_int(offsetId), MTP_int(offsetId),
MTP_int(addOffset), MTP_int(addOffset),
MTP_int(limit), MTP_int(limit),
@ -111,22 +138,26 @@ void UnreadThings::requestMentions(not_null<History*> history, int loaded) {
_mentionsRequests.emplace(history, requestId); _mentionsRequests.emplace(history, requestId);
} }
void UnreadThings::requestReactions(not_null<History*> history, int loaded) { void UnreadThings::requestReactions(
if (_reactionsRequests.contains(history)) { not_null<DialogsEntry*> entry,
int loaded) {
if (_reactionsRequests.contains(entry)) {
return; return;
} }
const auto topMsgId = 0;
const auto offsetId = loaded const auto offsetId = loaded
? std::max(history->unreadReactions().maxLoaded(), MsgId(1)) ? std::max(entry->unreadReactions().maxLoaded(), MsgId(1))
: MsgId(1); : MsgId(1);
const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit; const auto limit = loaded ? kNextRequestLimit : kFirstRequestLimit;
const auto addOffset = loaded ? -(limit + 1) : -limit; const auto addOffset = loaded ? -(limit + 1) : -limit;
const auto maxId = 0; const auto maxId = 0;
const auto minId = 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( const auto requestId = _api->request(MTPmessages_GetUnreadReactions(
MTP_flags(0), MTP_flags(topic ? Flag::f_top_msg_id : Flag()),
history->peer->input, history->peer->input,
MTP_int(topMsgId), MTP_int(topic ? topic->rootId() : 0),
MTP_int(offsetId), MTP_int(offsetId),
MTP_int(addOffset), MTP_int(addOffset),
MTP_int(limit), MTP_int(limit),

View file

@ -7,37 +7,44 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#pragma once #pragma once
class History;
class ApiWrap; class ApiWrap;
class PeerData; class PeerData;
class ChannelData; class ChannelData;
namespace Dialogs {
class Entry;
} // namespace Dialogs
namespace Api { namespace Api {
class UnreadThings final { class UnreadThings final {
public: public:
using DialogsEntry = Dialogs::Entry;
explicit UnreadThings(not_null<ApiWrap*> api); explicit UnreadThings(not_null<ApiWrap*> api);
[[nodiscard]] bool trackMentions(PeerData *peer) const; [[nodiscard]] bool trackMentions(PeerData *peer) const;
[[nodiscard]] bool trackReactions(PeerData *peer) const; [[nodiscard]] bool trackReactions(PeerData *peer) const;
void preloadEnough(History *history); void preloadEnough(DialogsEntry *entry);
void mediaAndMentionsRead( void mediaAndMentionsRead(
const base::flat_set<MsgId> &readIds, const base::flat_set<MsgId> &readIds,
ChannelData *channel = nullptr); ChannelData *channel = nullptr);
private: void cancelRequests(not_null<DialogsEntry*> entry);
void preloadEnoughMentions(not_null<History*> history);
void preloadEnoughReactions(not_null<History*> history);
void requestMentions(not_null<History*> history, int loaded); private:
void requestReactions(not_null<History*> history, int loaded); void preloadEnoughMentions(not_null<DialogsEntry*> entry);
void preloadEnoughReactions(not_null<DialogsEntry*> entry);
void requestMentions(not_null<DialogsEntry*> entry, int loaded);
void requestReactions(not_null<DialogsEntry*> entry, int loaded);
const not_null<ApiWrap*> _api; const not_null<ApiWrap*> _api;
base::flat_map<not_null<History*>, mtpRequestId> _mentionsRequests; base::flat_map<not_null<DialogsEntry*>, mtpRequestId> _mentionsRequests;
base::flat_map<not_null<History*>, mtpRequestId> _reactionsRequests; base::flat_map<not_null<DialogsEntry*>, mtpRequestId> _reactionsRequests;
}; };

View file

@ -163,6 +163,35 @@ rpl::producer<HistoryUpdate> Changes::realtimeHistoryUpdates(
return _historyChanges.realtimeUpdates(flag); return _historyChanges.realtimeUpdates(flag);
} }
void Changes::topicUpdated(
not_null<ForumTopic*> topic,
TopicUpdate::Flags flags) {
_topicChanges.updated(topic, flags);
scheduleNotifications();
}
rpl::producer<TopicUpdate> Changes::topicUpdates(
TopicUpdate::Flags flags) const {
return _topicChanges.updates(flags);
}
rpl::producer<TopicUpdate> Changes::topicUpdates(
not_null<ForumTopic*> topic,
TopicUpdate::Flags flags) const {
return _topicChanges.updates(topic, flags);
}
rpl::producer<TopicUpdate> Changes::topicFlagsValue(
not_null<ForumTopic*> topic,
TopicUpdate::Flags flags) const {
return _topicChanges.flagsValue(topic, flags);
}
rpl::producer<TopicUpdate> Changes::realtimeTopicUpdates(
TopicUpdate::Flag flag) const {
return _topicChanges.realtimeUpdates(flag);
}
void Changes::messageUpdated( void Changes::messageUpdated(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
MessageUpdate::Flags flags) { MessageUpdate::Flags flags) {

View file

@ -21,9 +21,7 @@ namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
namespace Data { namespace Data::details {
namespace details {
template <typename Flag> template <typename Flag>
inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) { inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) {
@ -35,7 +33,11 @@ inline constexpr int CountBit(Flag Last = Flag::LastUsedBit) {
return i; return i;
} }
} // namespace details } // namespace Data::details
namespace Data {
class ForumTopic;
struct NameUpdate { struct NameUpdate {
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<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }
not_null<ForumTopic*> topic;
Flags flags = 0;
};
struct MessageUpdate { struct MessageUpdate {
enum class Flag : uint32 { enum class Flag : uint32 {
None = 0, None = 0,
@ -219,6 +239,20 @@ public:
[[nodiscard]] rpl::producer<HistoryUpdate> realtimeHistoryUpdates( [[nodiscard]] rpl::producer<HistoryUpdate> realtimeHistoryUpdates(
HistoryUpdate::Flag flag) const; HistoryUpdate::Flag flag) const;
void topicUpdated(
not_null<ForumTopic*> topic,
TopicUpdate::Flags flags);
[[nodiscard]] rpl::producer<TopicUpdate> topicUpdates(
TopicUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<TopicUpdate> topicUpdates(
not_null<ForumTopic*> topic,
TopicUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<TopicUpdate> topicFlagsValue(
not_null<ForumTopic*> topic,
TopicUpdate::Flags flags) const;
[[nodiscard]] rpl::producer<TopicUpdate> realtimeTopicUpdates(
TopicUpdate::Flag flag) const;
void messageUpdated( void messageUpdated(
not_null<HistoryItem*> item, not_null<HistoryItem*> item,
MessageUpdate::Flags flags); MessageUpdate::Flags flags);
@ -292,6 +326,7 @@ private:
rpl::event_stream<NameUpdate> _nameStream; rpl::event_stream<NameUpdate> _nameStream;
Manager<PeerData, PeerUpdate> _peerChanges; Manager<PeerData, PeerUpdate> _peerChanges;
Manager<History, HistoryUpdate> _historyChanges; Manager<History, HistoryUpdate> _historyChanges;
Manager<ForumTopic, TopicUpdate> _topicChanges;
Manager<HistoryItem, MessageUpdate> _messageChanges; Manager<HistoryItem, MessageUpdate> _messageChanges;
Manager<Dialogs::Entry, EntryUpdate> _entryChanges; Manager<Dialogs::Entry, EntryUpdate> _entryChanges;

View file

@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_unread_things.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "base/random.h" #include "base/random.h"
#include "apiwrap.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( MsgId Forum::reserveCreatingId(
const QString &title, const QString &title,
int32 colorId, int32 colorId,
@ -238,7 +233,13 @@ void Forum::created(MsgId rootId, MsgId realId) {
owner().notifyItemIdChange({ id, rootId }); owner().notifyItemIdChange({ id, rootId });
} }
ForumTopic *Forum::topicFor(not_null<HistoryItem*> item) { void Forum::clearAllUnreadMentions() {
for (const auto &[rootId, topic] : _topics) {
topic->unreadMentions().clear();
}
}
ForumTopic *Forum::topicFor(not_null<const HistoryItem*> item) {
const auto maybe = topicFor(item->replyToTop()); const auto maybe = topicFor(item->replyToTop());
return maybe ? maybe : topicFor(item->topicRootId()); return maybe ? maybe : topicFor(item->topicRootId());
} }

View file

@ -45,9 +45,8 @@ public:
const QString &title, const QString &title,
int32 colorId, int32 colorId,
DocumentId iconId); DocumentId iconId);
void applyTopicRemoved(MsgId rootId);
void applyTopicCreated(MsgId rootId, MsgId realId); void applyTopicCreated(MsgId rootId, MsgId realId);
[[nodiscard]] ForumTopic *topicFor(not_null<HistoryItem*> item); [[nodiscard]] ForumTopic *topicFor(not_null<const HistoryItem*> item);
[[nodiscard]] ForumTopic *topicFor(MsgId rootId); [[nodiscard]] ForumTopic *topicFor(MsgId rootId);
void applyReceivedTopics(const MTPmessages_ForumTopics &topics); void applyReceivedTopics(const MTPmessages_ForumTopics &topics);
@ -60,6 +59,8 @@ public:
[[nodiscard]] bool creating(MsgId rootId) const; [[nodiscard]] bool creating(MsgId rootId) const;
void created(MsgId rootId, MsgId realId); void created(MsgId rootId, MsgId realId);
void clearAllUnreadMentions();
private: private:
struct TopicRequest { struct TopicRequest {
mtpRequestId id = 0; mtpRequestId id = 0;

View file

@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_forum_topic.h" #include "data/data_forum_topic.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_changes.h"
#include "data/data_forum.h" #include "data/data_forum.h"
#include "data/data_histories.h" #include "data/data_histories.h"
#include "data/data_replies_list.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 "dialogs/ui/dialogs_layout.h"
#include "core/application.h" #include "core/application.h"
#include "core/core_settings.h" #include "core/core_settings.h"
#include "apiwrap.h"
#include "api/api_unread_things.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
#include "history/history_unread_things.h"
#include "history/view/history_view_item_preview.h" #include "history/view/history_view_item_preview.h"
#include "main/main_session.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/color_int_conversion.h" #include "ui/color_int_conversion.h"
#include "styles/style_dialogs.h" #include "styles/style_dialogs.h"
@ -28,6 +33,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtSvg/QSvgRenderer> #include <QtSvg/QSvgRenderer>
namespace Data { namespace Data {
namespace {
using UpdateFlag = TopicUpdate::Flag;
} // namespace
const base::flat_map<int32, QString> &ForumTopicIcons() { const base::flat_map<int32, QString> &ForumTopicIcons() {
static const auto Result = base::flat_map<int32, QString>{ static const auto Result = base::flat_map<int32, QString>{
@ -135,6 +145,7 @@ ForumTopic::ForumTopic(not_null<History*> history, MsgId rootId)
_replies->unreadCountValue( _replies->unreadCountValue(
) | rpl::combine_previous( ) | rpl::combine_previous(
) | rpl::filter([=] { ) | rpl::filter([=] {
session().changes().topicUpdated(this, UpdateFlag::UnreadView);
return inChatList(); return inChatList();
}) | rpl::start_with_next([=]( }) | rpl::start_with_next([=](
std::optional<int> previous, std::optional<int> previous,
@ -145,7 +156,9 @@ ForumTopic::ForumTopic(not_null<History*> history, MsgId rootId)
}, _replies->lifetime()); }, _replies->lifetime());
} }
ForumTopic::~ForumTopic() = default; ForumTopic::~ForumTopic() {
session().api().unreadThings().cancelRequests(this);
}
std::shared_ptr<Data::RepliesList> ForumTopic::replies() const { std::shared_ptr<Data::RepliesList> ForumTopic::replies() const {
return _replies; return _replies;
@ -203,6 +216,8 @@ void ForumTopic::applyTopic(const MTPForumTopic &topic) {
#if 0 // #TODO forum unread mark #if 0 // #TODO forum unread mark
setUnreadMark(data.is_unread_mark()); setUnreadMark(data.is_unread_mark());
#endif #endif
unreadMentions().setCount(data.vunread_mentions_count().v);
unreadReactions().setCount(data.vunread_reactions_count().v);
} }
void ForumTopic::indexTitleParts() { void ForumTopic::indexTitleParts() {
@ -470,7 +485,7 @@ bool ForumTopic::unreadCountKnown() const {
} }
void ForumTopic::setUnreadMark(bool unread) { void ForumTopic::setUnreadMark(bool unread) {
if (_unreadMark == unread) { if (unreadMark() == unread) {
return; return;
} }
const auto noUnreadMessages = !unreadCount(); const auto noUnreadMessages = !unreadCount();
@ -478,13 +493,18 @@ void ForumTopic::setUnreadMark(bool unread) {
if (inChatList() && noUnreadMessages) { if (inChatList() && noUnreadMessages) {
updateChatListEntry(); updateChatListEntry();
} }
session().changes().topicUpdated(this, UpdateFlag::UnreadView);
}); });
const auto notifier = unreadStateChangeNotifier(noUnreadMessages); const auto notifier = unreadStateChangeNotifier(noUnreadMessages);
_unreadMark = unread; if (unread) {
_flags |= Flag::UnreadMark;
} else {
_flags &= ~Flag::UnreadMark;
}
} }
bool ForumTopic::unreadMark() const { bool ForumTopic::unreadMark() const {
return _unreadMark; return (_flags & Flag::UnreadMark);
} }
int ForumTopic::chatListUnreadCount() const { int ForumTopic::chatListUnreadCount() const {
@ -503,7 +523,7 @@ Dialogs::UnreadState ForumTopic::unreadStateFor(
int count, int count,
bool known) const { bool known) const {
auto result = Dialogs::UnreadState(); auto result = Dialogs::UnreadState();
const auto mark = !count && _unreadMark; const auto mark = !count && unreadMark();
const auto muted = _history->mute(); const auto muted = _history->mute();
result.messages = count; result.messages = count;
result.messagesMuted = muted ? count : 0; result.messagesMuted = muted ? count : 0;

View file

@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "dialogs/dialogs_entry.h" #include "dialogs/dialogs_entry.h"
#include "dialogs/ui/dialogs_message_view.h" #include "dialogs/ui/dialogs_message_view.h"
#include "base/flags.h"
class ChannelData; class ChannelData;
@ -111,6 +112,11 @@ public:
Dialogs::Ui::MessageView lastItemDialogsView; Dialogs::Ui::MessageView lastItemDialogsView;
private: private:
enum class Flag : uchar {
UnreadMark = 0x01,
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
void indexTitleParts(); void indexTitleParts();
void validateDefaultIcon() const; void validateDefaultIcon() const;
void applyTopicTopMessage(MsgId topMessageId); void applyTopicTopMessage(MsgId topMessageId);
@ -144,7 +150,7 @@ private:
std::optional<HistoryItem*> _lastServerMessage; std::optional<HistoryItem*> _lastServerMessage;
std::optional<HistoryItem*> _chatListMessage; std::optional<HistoryItem*> _chatListMessage;
base::flat_set<FullMsgId> _requestedGroups; base::flat_set<FullMsgId> _requestedGroups;
bool _unreadMark = false; base::flags<Flag> _flags;
rpl::lifetime _lifetime; rpl::lifetime _lifetime;

View file

@ -156,18 +156,18 @@ private:
not_null<History*> history; not_null<History*> history;
MsgId rootId = 0; MsgId rootId = 0;
friend inline constexpr auto operator<=>( friend inline auto operator<=>(
GroupRequestKey, GroupRequestKey,
GroupRequestKey) = default; GroupRequestKey) = default;
}; };
template <typename Arg> template <typename Arg>
static auto ReplaceReplyTo(Arg arg, MsgId replyTo) { static auto ReplaceReplyTo(Arg arg, MsgId replyTo) {
return arg; if constexpr (std::is_same_v<Arg, ReplyToPlaceholder>) {
} return MTP_int(replyTo);
template <> } else {
static auto ReplaceReplyTo(ReplyToPlaceholder, MsgId replyTo) { return arg;
return MTP_int(replyTo); }
} }
void readInboxTill(not_null<History*> history, MsgId tillId, bool force); void readInboxTill(not_null<History*> history, MsgId tillId, bool force);

View file

@ -18,8 +18,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "main/main_session.h" #include "main/main_session.h"
#include "main/main_session_settings.h" #include "main/main_session_settings.h"
#include "ui/text/text_options.h" #include "ui/text/text_options.h"
#include "history/history_item.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h"
#include "history/history_unread_things.h"
#include "styles/style_dialogs.h" // st::dialogsTextWidthMin #include "styles/style_dialogs.h" // st::dialogsTextWidthMin
namespace Dialogs { namespace Dialogs {
@ -49,6 +50,8 @@ Entry::Entry(not_null<Data::Session*> owner, Type type)
, _type(type) { , _type(type) {
} }
Entry::~Entry() = default;
Data::Session &Entry::owner() const { Data::Session &Entry::owner() const {
return *_owner; return *_owner;
} }
@ -102,19 +105,30 @@ void Entry::cachePinnedIndex(FilterId filterId, int index) {
} }
void Entry::cacheTopPromoted(bool promoted) { void Entry::cacheTopPromoted(bool promoted) {
if (_isTopPromoted == promoted) { if (isTopPromoted() == promoted) {
return; return;
} else if (promoted) {
_flags |= Flag::IsTopPromoted;
} else {
_flags &= ~Flag::IsTopPromoted;
} }
_isTopPromoted = promoted;
updateChatListSortPosition(); updateChatListSortPosition();
updateChatListEntry(); updateChatListEntry();
if (!_isTopPromoted) { if (!isTopPromoted()) {
updateChatListExistence(); updateChatListExistence();
} }
} }
bool Entry::isTopPromoted() const { bool Entry::isTopPromoted() const {
return _isTopPromoted; return (_flags & Flag::IsTopPromoted);
}
const base::flat_set<MsgId> &Entry::unreadMentionsIds() const {
if (!_unreadThings) {
static const auto Result = base::flat_set<MsgId>();
return Result;
}
return _unreadThings->mentions.ids();
} }
bool Entry::needUpdateInChatList() const { bool Entry::needUpdateInChatList() const {
@ -198,6 +212,42 @@ TimeId Entry::adjustedChatListTimeId() const {
return chatListTimeId(); 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() { void Entry::changedChatListPinHook() {
} }

View file

@ -23,6 +23,13 @@ class ForumTopic;
class CloudImageView; class CloudImageView;
} // namespace Data } // namespace Data
namespace HistoryUnreadThings {
enum class AddType;
struct All;
class Proxy;
class ConstProxy;
} // namespace HistoryUnreadThings
namespace Ui { namespace Ui {
} // namespace Ui } // namespace Ui
@ -108,7 +115,7 @@ public:
Entry(not_null<Data::Session*> owner, Type type); Entry(not_null<Data::Session*> owner, Type type);
Entry(const Entry &other) = delete; Entry(const Entry &other) = delete;
Entry &operator=(const Entry &other) = delete; Entry &operator=(const Entry &other) = delete;
virtual ~Entry() = default; virtual ~Entry();
[[nodiscard]] Data::Session &owner() const; [[nodiscard]] Data::Session &owner() const;
[[nodiscard]] Main::Session &session() const; [[nodiscard]] Main::Session &session() const;
@ -154,6 +161,12 @@ public:
bool needUpdateInChatList() const; bool needUpdateInChatList() const;
virtual TimeId adjustedChatListTimeId() 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; virtual int fixedOnTopIndex() const = 0;
static constexpr auto kArchiveFixOnTopIndex = 1; static constexpr auto kArchiveFixOnTopIndex = 1;
static constexpr auto kTopPromotionFixOnTopIndex = 2; static constexpr auto kTopPromotionFixOnTopIndex = 2;
@ -209,7 +222,15 @@ protected:
void cacheTopPromoted(bool promoted); void cacheTopPromoted(bool promoted);
[[nodiscard]] const base::flat_set<MsgId> &unreadMentionsIds() const;
private: private:
enum class Flag : uchar {
IsTopPromoted = 0x01,
UnreadThingsKnown = 0x02,
};
friend inline constexpr bool is_flag_type(Flag) { return true; }
virtual void changedChatListPinHook(); virtual void changedChatListPinHook();
void pinnedIndexChanged(FilterId filterId, int was, int now); void pinnedIndexChanged(FilterId filterId, int was, int now);
[[nodiscard]] uint64 computeSortPosition(FilterId filterId) const; [[nodiscard]] uint64 computeSortPosition(FilterId filterId) const;
@ -225,11 +246,12 @@ private:
uint64 _sortKeyInChatList = 0; uint64 _sortKeyInChatList = 0;
uint64 _sortKeyByDate = 0; uint64 _sortKeyByDate = 0;
base::flat_map<FilterId, int> _pinnedIndex; base::flat_map<FilterId, int> _pinnedIndex;
std::unique_ptr<HistoryUnreadThings::All> _unreadThings;
mutable Ui::PeerBadge _chatListBadge; mutable Ui::PeerBadge _chatListBadge;
mutable Ui::Text::String _chatListNameText; mutable Ui::Text::String _chatListNameText;
mutable int _chatListNameVersion = 0; mutable int _chatListNameVersion = 0;
TimeId _timeId = 0; TimeId _timeId = 0;
bool _isTopPromoted = false; base::flags<Flag> _flags;
const Type _type; const Type _type;
}; };

View file

@ -916,11 +916,12 @@ void RowPainter::Paint(
? base::unixtime::parse(cloudDraft->date) ? base::unixtime::parse(cloudDraft->date)
: QDateTime(); : QDateTime();
}(); }();
const auto displayMentionBadge = history const auto displayMentionBadge = (history
&& history->unreadMentions().has(); && history->unreadMentions().has())
|| (topic && topic->unreadMentions().has());
const auto displayReactionBadge = !displayMentionBadge const auto displayReactionBadge = !displayMentionBadge
&& history && ((history && history->unreadReactions().has())
&& history->unreadReactions().has(); || (topic && topic->unreadReactions().has()));
const auto mentionOrReactionMuted = (entry->folder() != nullptr) const auto mentionOrReactionMuted = (entry->folder() != nullptr)
|| (!displayMentionBadge && unreadMuted); || (!displayMentionBadge && unreadMuted);
const auto displayUnreadCounter = [&] { const auto displayUnreadCounter = [&] {

View file

@ -161,14 +161,8 @@ void History::itemRemoved(not_null<HistoryItem*> item) {
if (IsClientMsgId(item->id)) { if (IsClientMsgId(item->id)) {
unregisterClientSideMessage(item); unregisterClientSideMessage(item);
} }
if (const auto forum = peer->forum()) { if (const auto topic = item->topic()) {
if (const auto topic = forum->topicFor(item)) { topic->applyItemRemoved(item->id);
if (topic->rootId() == item->id) {
forum->applyTopicRemoved(item->id);
} else {
topic->applyItemRemoved(item->id);
}
}
} }
if (const auto chat = peer->asChat()) { if (const auto chat = peer->asChat()) {
if (const auto to = chat->getMigrateToChannel()) { if (const auto to = chat->getMigrateToChannel()) {
@ -705,40 +699,25 @@ not_null<HistoryItem*> History::addNewLocalMessage(
true); true);
} }
void History::setUnreadThingsKnown() { void History::clearUnreadMentionsFor(MsgId topicRootId) {
_flags |= Flag::UnreadThingsKnown; const auto &ids = unreadMentionsIds();
} if (ids.empty()) {
return;
HistoryUnreadThings::Proxy History::unreadMentions() { }
return { const auto owner = &this->owner();
this, const auto peerId = peer->id;
_unreadThings, auto items = base::flat_set<MsgId>();
HistoryUnreadThings::Type::Mentions, items.reserve(ids.size());
!!(_flags & Flag::UnreadThingsKnown), for (const auto &id : ids) {
}; if (const auto item = owner->message(peerId, id)) {
} if (item->topicRootId() == topicRootId) {
items.emplace(id);
HistoryUnreadThings::ConstProxy History::unreadMentions() const { }
return { }
_unreadThings ? &_unreadThings->mentions : nullptr, }
!!(_flags & Flag::UnreadThingsKnown), for (const auto &id : items) {
}; unreadMentions().erase(id);
} }
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),
};
} }
not_null<HistoryItem*> History::addNewToBack( not_null<HistoryItem*> History::addNewToBack(
@ -1084,14 +1063,12 @@ void History::applyServiceChanges(
data.vicon_emoji_id().value_or(DocumentId())); data.vicon_emoji_id().value_or(DocumentId()));
} }
}, [&](const MTPDmessageActionTopicEdit &data) { }, [&](const MTPDmessageActionTopicEdit &data) {
if (const auto forum = peer->forum()) { if (const auto topic = item->topic()) {
if (const auto topic = forum->topicFor(item)) { if (const auto &title = data.vtitle()) {
if (const auto &title = data.vtitle()) { topic->applyTitle(qs(*title));
topic->applyTitle(qs(*title)); }
} if (const auto icon = data.vicon_emoji_id()) {
if (const auto icon = data.vicon_emoji_id()) { topic->applyIconId(icon->v);
topic->applyIconId(icon->v);
}
} }
} }
}, [](const auto &) { }, [](const auto &) {
@ -1154,10 +1131,8 @@ void History::newItemAdded(not_null<HistoryItem*> item) {
if (!folderKnown()) { if (!folderKnown()) {
owner().histories().requestDialogEntry(this); owner().histories().requestDialogEntry(this);
} }
if (const auto forum = peer->forum()) { if (const auto topic = item->topic()) {
if (const auto topic = forum->topicFor(item)) { topic->applyItemAdded(item);
topic->applyItemAdded(item);
}
} }
} }

View file

@ -27,13 +27,6 @@ class HistoryService;
struct HistoryMessageMarkupData; struct HistoryMessageMarkupData;
class HistoryMainElementDelegateMixin; class HistoryMainElementDelegateMixin;
namespace HistoryUnreadThings {
enum class AddType;
struct All;
class Proxy;
class ConstProxy;
} // namespace HistoryUnreadThings
namespace Main { namespace Main {
class Session; class Session;
} // namespace Main } // namespace Main
@ -337,12 +330,7 @@ public:
} }
void clearLastKeyboard(); void clearLastKeyboard();
void clearUnreadMentionsFor(MsgId topicRootId);
void setUnreadThingsKnown();
[[nodiscard]] HistoryUnreadThings::Proxy unreadMentions();
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadMentions() const;
[[nodiscard]] HistoryUnreadThings::Proxy unreadReactions();
[[nodiscard]] HistoryUnreadThings::ConstProxy unreadReactions() const;
Data::Draft *draft(Data::DraftKey key) const; Data::Draft *draft(Data::DraftKey key) const;
void setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft); void setDraft(Data::DraftKey key, std::unique_ptr<Data::Draft> &&draft);
@ -483,7 +471,6 @@ private:
enum class Flag : uchar { enum class Flag : uchar {
HasPendingResizedItems = (1 << 0), HasPendingResizedItems = (1 << 0),
UnreadThingsKnown = (1 << 1),
}; };
using Flags = base::flags<Flag>; using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { friend inline constexpr auto is_flag_type(Flag) {
@ -618,7 +605,6 @@ private:
std::optional<HistoryItem*> _lastServerMessage; std::optional<HistoryItem*> _lastServerMessage;
base::flat_set<not_null<HistoryItem*>> _clientSideMessages; base::flat_set<not_null<HistoryItem*>> _clientSideMessages;
std::unordered_set<std::unique_ptr<HistoryItem>> _messages; std::unordered_set<std::unique_ptr<HistoryItem>> _messages;
std::unique_ptr<HistoryUnreadThings::All> _unreadThings;
// This almost always is equal to _lastMessage. The only difference is // This almost always is equal to _lastMessage. The only difference is
// for a group that migrated to a supergroup. Then _lastMessage can // for a group that migrated to a supergroup. Then _lastMessage can

View file

@ -375,10 +375,8 @@ void HistoryItem::invalidateChatListEntry() {
this, this,
Data::MessageUpdate::Flag::DialogRowRefresh); Data::MessageUpdate::Flag::DialogRowRefresh);
history()->lastItemDialogsView.itemInvalidated(this); history()->lastItemDialogsView.itemInvalidated(this);
if (const auto forum = history()->peer->forum()) { if (const auto topic = this->topic()) {
if (const auto topic = forum->topicFor(this)) { topic->lastItemDialogsView.itemInvalidated(this);
topic->lastItemDialogsView.itemInvalidated(this);
}
} }
} }
@ -442,8 +440,12 @@ void HistoryItem::markMediaAndMentionRead() {
_flags &= ~MessageFlag::MediaIsUnread; _flags &= ~MessageFlag::MediaIsUnread;
if (mentionsMe()) { if (mentionsMe()) {
history()->updateChatListEntry(); _history->updateChatListEntry();
history()->unreadMentions().erase(id); _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(); _reactions->markRead();
} }
_flags &= ~MessageFlag::HasUnreadReaction; _flags &= ~MessageFlag::HasUnreadReaction;
history()->updateChatListEntry(); _history->updateChatListEntry();
history()->unreadReactions().erase(id); _history->unreadReactions().erase(id);
if (const auto topic = this->topic()) {
topic->updateChatListEntry();
topic->unreadReactions().erase(id);
}
} }
bool HistoryItem::markContentsRead(bool fromThisClient) { bool HistoryItem::markContentsRead(bool fromThisClient) {
@ -613,6 +619,11 @@ void HistoryItem::destroy() {
_history->destroyMessage(this); _history->destroyMessage(this);
} }
Data::ForumTopic *HistoryItem::topic() const {
const auto forum = _history->peer->forum();
return forum ? forum->topicFor(this) : nullptr;
}
void HistoryItem::refreshMainView() { void HistoryItem::refreshMainView() {
if (const auto view = mainView()) { if (const auto view = mainView()) {
_history->owner().notifyHistoryChangeDelayed(_history); _history->owner().notifyHistoryChangeDelayed(_history);

View file

@ -46,6 +46,7 @@ struct ReactionId;
class Media; class Media;
struct MessageReaction; struct MessageReaction;
class MessageReactions; class MessageReactions;
class ForumTopic;
} // namespace Data } // namespace Data
namespace Main { namespace Main {
@ -122,13 +123,14 @@ public:
const QString &label, const QString &label,
const TextWithEntities &content); const TextWithEntities &content);
not_null<History*> history() const { [[nodiscard]] not_null<History*> history() const {
return _history; return _history;
} }
not_null<PeerData*> from() const { [[nodiscard]] Data::ForumTopic *topic() const;
[[nodiscard]] not_null<PeerData*> from() const {
return _from; return _from;
} }
HistoryView::Element *mainView() const { [[nodiscard]] HistoryView::Element *mainView() const {
return _mainView; return _mainView;
} }
void setMainView(not_null<HistoryView::Element*> view) { void setMainView(not_null<HistoryView::Element*> view) {

View file

@ -1331,24 +1331,46 @@ void HistoryMessage::addToUnreadThings(HistoryUnreadThings::AddType type) {
if (!isRegular()) { if (!isRegular()) {
return; return;
} }
if (isUnreadMention()) { const auto mention = isUnreadMention();
if (history()->unreadMentions().add(id, type)) { const auto reaction = hasUnreadReaction();
history()->session().changes().historyUpdated( if (!mention && !reaction) {
history(), 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); Data::HistoryUpdate::Flag::UnreadMentions);
} }
if (topic && topic->unreadMentions().add(id, type)) {
changes->topicUpdated(
topic,
Data::TopicUpdate::Flag::UnreadMentions);
}
} }
if (hasUnreadReaction()) { if (reaction) {
if (history()->unreadReactions().add(id, type)) { 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) { if (type == HistoryUnreadThings::AddType::New) {
history()->session().changes().messageUpdated( changes->messageUpdated(
this, this,
Data::MessageUpdate::Flag::NewUnreadReaction); Data::MessageUpdate::Flag::NewUnreadReaction);
} }
if (hasUnreadReaction()) { if (hasUnreadReaction()) {
history()->session().changes().historyUpdated( if (toHistory) {
history(), changes->historyUpdated(
Data::HistoryUpdate::Flag::UnreadReactions); 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() { void HistoryMessage::destroyHistoryEntry() {
if (isUnreadMention()) { if (isUnreadMention()) {
history()->unreadMentions().erase(id); history()->unreadMentions().erase(id);
if (const auto topic = this->topic()) {
topic->unreadMentions().erase(id);
}
} }
if (hasUnreadReaction()) { if (hasUnreadReaction()) {
history()->unreadReactions().erase(id); history()->unreadReactions().erase(id);
if (const auto topic = this->topic()) {
topic->unreadReactions().erase(id);
}
} }
if (const auto reply = Get<HistoryMessageReply>()) { if (const auto reply = Get<HistoryMessageReply>()) {
changeReplyToTopCounter(reply, -1); changeReplyToTopCounter(reply, -1);

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_forum_topic.h"
#include "data/data_chat_filters.h" #include "data/data_chat_filters.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item.h" #include "history/history_item.h"
@ -19,20 +20,29 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace HistoryUnreadThings { namespace HistoryUnreadThings {
namespace { namespace {
[[nodiscard]] Data::HistoryUpdate::Flag UpdateFlag(Type type) { template <typename Update>
using Flag = Data::HistoryUpdate::Flag; [[nodiscard]] typename Update::Flag UpdateFlag(Type type) {
using Flag = typename Update::Flag;
switch (type) { switch (type) {
case Type::Mentions: return Flag::UnreadMentions; case Type::Mentions: return Flag::UnreadMentions;
case Type::Reactions: return Flag::UnreadReactions; 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<Data::HistoryUpdate>(type);
}
[[nodiscard]] Data::TopicUpdate::Flag TopicUpdateFlag(Type type) {
return UpdateFlag<Data::TopicUpdate>(type);
} }
} // namespace } // namespace
void Proxy::setCount(int count) { void Proxy::setCount(int count) {
if (!_known) { if (!_known) {
_history->setUnreadThingsKnown(); _entry->setUnreadThingsKnown();
} }
if (!_data) { if (!_data) {
if (!count) { if (!count) {
@ -59,16 +69,19 @@ void Proxy::setCount(int count) {
const auto has = (count > 0); const auto has = (count > 0);
if (has != had) { if (has != had) {
if (_type == Type::Mentions) { 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) { bool Proxy::add(MsgId msgId, AddType type) {
const auto peer = _history->peer; if (const auto history = _entry->asHistory()) {
if (peer->isChannel() && !peer->isMegagroup()) { if (history->peer->isBroadcast()) {
return false; return false;
}
} }
if (!_data) { if (!_data) {
@ -89,7 +102,6 @@ bool Proxy::add(MsgId msgId, AddType type) {
return true; return true;
} }
return false; return false;
} }
void Proxy::erase(MsgId msgId) { void Proxy::erase(MsgId msgId) {
@ -101,9 +113,7 @@ void Proxy::erase(MsgId msgId) {
if (const auto count = list.count(); count > 0) { if (const auto count = list.count(); count > 0) {
setCount(count - 1); setCount(count - 1);
} }
_history->session().changes().historyUpdated( notifyUpdated();
_history,
UpdateFlag(_type));
} }
void Proxy::clear() { void Proxy::clear() {
@ -113,15 +123,14 @@ void Proxy::clear() {
auto &list = resolveList(); auto &list = resolveList();
list.clear(); list.clear();
setCount(0); setCount(0);
_history->session().changes().historyUpdated( notifyUpdated();
_history,
UpdateFlag(_type));
} }
void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) { void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) {
if (!alreadyLoaded && _data) { if (!alreadyLoaded && _data) {
resolveList().clear(); resolveList().clear();
} }
const auto history = resolveHistory();
auto fullCount = slice.match([&]( auto fullCount = slice.match([&](
const MTPDmessages_messagesNotModified &) { const MTPDmessages_messagesNotModified &) {
LOG(("API Error: received messages.messagesNotModified! " LOG(("API Error: received messages.messagesNotModified! "
@ -132,8 +141,8 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) {
}, [&](const MTPDmessages_messagesSlice &data) { }, [&](const MTPDmessages_messagesSlice &data) {
return data.vcount().v; return data.vcount().v;
}, [&](const MTPDmessages_channelMessages &data) { }, [&](const MTPDmessages_channelMessages &data) {
if (_history->peer->isChannel()) { if (const auto channel = history->peer->asChannel()) {
_history->peer->asChannel()->ptsReceived(data.vpts().v); channel->ptsReceived(data.vpts().v);
} else { } else {
LOG(("API Error: received messages.channelMessages when " LOG(("API Error: received messages.channelMessages when "
"no channel was passed! (Proxy::addSlice)")); "no channel was passed! (Proxy::addSlice)"));
@ -141,7 +150,7 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) {
return data.vcount().v; return data.vcount().v;
}); });
auto &owner = _history->owner(); auto &owner = _entry->owner();
const auto messages = slice.match([&]( const auto messages = slice.match([&](
const MTPDmessages_messagesNotModified &) { const MTPDmessages_messagesNotModified &) {
LOG(("API Error: received messages.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 localFlags = MessageFlags();
const auto type = NewMessageType::Existing; const auto type = NewMessageType::Existing;
for (const auto &message : messages) { for (const auto &message : messages) {
const auto item = _history->addNewMessage( const auto item = history->addNewMessage(
IdFromMessage(message), IdFromMessage(message),
message, message,
localFlags, localFlags,
@ -181,9 +190,7 @@ void Proxy::addSlice(const MTPmessages_Messages &slice, int alreadyLoaded) {
fullCount = loadedCount(); fullCount = loadedCount();
} }
setCount(fullCount); setCount(fullCount);
_history->session().changes().historyUpdated( notifyUpdated();
_history,
UpdateFlag(_type));
} }
void Proxy::checkAdd(MsgId msgId, bool resolved) { void Proxy::checkAdd(MsgId msgId, bool resolved) {
@ -196,7 +203,7 @@ void Proxy::checkAdd(MsgId msgId, bool resolved) {
if (!list.loadedCount() || list.maxLoaded() <= msgId) { if (!list.loadedCount() || list.maxLoaded() <= msgId) {
return; return;
} }
const auto history = _history; const auto history = resolveHistory();
const auto peer = history->peer; const auto peer = history->peer;
const auto item = peer->owner().message(peer, msgId); const auto item = peer->owner().message(peer, msgId);
if (item && item->hasUnreadReaction()) { 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() { void Proxy::createData() {
_data = std::make_unique<All>(); _data = std::make_unique<All>();
if (_known) { if (_known) {
@ -216,7 +235,7 @@ void Proxy::createData() {
} }
} }
[[nodiscard]] List &Proxy::resolveList() { List &Proxy::resolveList() {
Expects(_data != nullptr); Expects(_data != nullptr);
switch (_type) { switch (_type) {
@ -226,4 +245,9 @@ void Proxy::createData() {
Unexpected("Unread things type in Proxy::resolveList."); Unexpected("Unread things type in Proxy::resolveList.");
} }
not_null<History*> Proxy::resolveHistory() const {
const auto result = _entry->asHistory();
return result ? not_null(result) : _entry->asTopic()->history();
}
} // namespace HistoryUnreadThings } // namespace HistoryUnreadThings

View file

@ -9,6 +9,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class History; class History;
namespace Dialogs {
class Entry;
} // namespace Data
namespace HistoryUnreadThings { namespace HistoryUnreadThings {
enum class AddType { enum class AddType {
@ -41,6 +45,9 @@ public:
[[nodiscard]] bool contains(MsgId msgId) const { [[nodiscard]] bool contains(MsgId msgId) const {
return _messages.contains(msgId); return _messages.contains(msgId);
} }
[[nodiscard]] const base::flat_set<MsgId> &ids() const {
return _messages;
}
void setCount(int count) { void setCount(int count) {
_count = count; _count = count;
} }
@ -101,7 +108,7 @@ private:
class Proxy final : public ConstProxy { class Proxy final : public ConstProxy {
public: public:
Proxy( Proxy(
not_null<History*> history, not_null<Dialogs::Entry*> entry,
std::unique_ptr<All> &data, std::unique_ptr<All> &data,
Type type, Type type,
bool known) bool known)
@ -112,7 +119,7 @@ public:
? &data->mentions ? &data->mentions
: &data->reactions), : &data->reactions),
known) known)
, _history(history) , _entry(entry)
, _data(data) , _data(data)
, _type(type) , _type(type)
, _known(known) { , _known(known) {
@ -129,9 +136,11 @@ public:
private: private:
void createData(); void createData();
void notifyUpdated();
[[nodiscard]] List &resolveList(); [[nodiscard]] List &resolveList();
[[nodiscard]] not_null<History*> resolveHistory() const;
const not_null<History*> _history; const not_null<Dialogs::Entry*> _entry;
std::unique_ptr<All> &_data; std::unique_ptr<All> &_data;
Type _type = Type::Mentions; Type _type = Type::Mentions;
bool _known = false; bool _known = false;

View file

@ -390,7 +390,7 @@ HistoryWidget::HistoryWidget(
_unreadReactions.widget->installEventFilter(this); _unreadReactions.widget->installEventFilter(this);
SendMenu::SetupUnreadMentionsMenu(_unreadMentions.widget.data(), [=] { SendMenu::SetupUnreadMentionsMenu(_unreadMentions.widget.data(), [=] {
return _history ? _history->peer.get() : nullptr; return _history ? _history->peer.get() : nullptr;
}); }, MsgId(0));
SendMenu::SetupUnreadReactionsMenu(_unreadReactions.widget.data(), [=] { SendMenu::SetupUnreadReactionsMenu(_unreadReactions.widget.data(), [=] {
return _history ? _history->peer.get() : nullptr; return _history ? _history->peer.get() : nullptr;
}); });

View file

@ -15,6 +15,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "ui/widgets/popup_menu.h" #include "ui/widgets/popup_menu.h"
#include "data/data_peer.h" #include "data/data_peer.h"
#include "data/data_forum.h"
#include "data/data_forum_topic.h"
#include "data/data_session.h" #include "data/data_session.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "history/history.h" #include "history/history.h"
@ -186,18 +188,33 @@ void SetupReadAllMenu(
void SetupUnreadMentionsMenu( void SetupUnreadMentionsMenu(
not_null<Ui::RpWidget*> button, not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer) { Fn<PeerData*()> currentPeer,
const auto topMsgId = 0; MsgId topicRootId) {
const auto text = tr::lng_context_mark_read_mentions_all(tr::now); const auto text = tr::lng_context_mark_read_mentions_all(tr::now);
const auto sendRequest = [=](not_null<PeerData*> peer, Fn<void()> done) { const auto sendRequest = [=](not_null<PeerData*> peer, Fn<void()> done) {
using Flag = MTPmessages_ReadMentions::Flag;
peer->session().api().request(MTPmessages_ReadMentions( peer->session().api().request(MTPmessages_ReadMentions(
MTP_flags(0), MTP_flags(topicRootId ? Flag::f_top_msg_id : Flag()),
peer->input, peer->input,
MTP_int(topMsgId) MTP_int(topicRootId)
)).done([=](const MTPmessages_AffectedHistory &result) { )).done([=](const MTPmessages_AffectedHistory &result) {
done(); done();
peer->session().api().applyAffectedHistory(peer, result); 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(); }).fail(done).send();
}; };
SetupReadAllMenu(button, currentPeer, text, sendRequest); SetupReadAllMenu(button, currentPeer, text, sendRequest);

View file

@ -51,7 +51,8 @@ void SetupMenuAndShortcuts(
void SetupUnreadMentionsMenu( void SetupUnreadMentionsMenu(
not_null<Ui::RpWidget*> button, not_null<Ui::RpWidget*> button,
Fn<PeerData*()> currentPeer); Fn<PeerData*()> currentPeer,
MsgId topicRootId);
void SetupUnreadReactionsMenu( void SetupUnreadReactionsMenu(
not_null<Ui::RpWidget*> button, not_null<Ui::RpWidget*> button,