From 31cd148e87e82a775494df51c34c704205aa451c Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 12 Jun 2026 14:32:58 +0400 Subject: [PATCH] Group community chats into one chat list row. --- Telegram/SourceFiles/api/api_communities.cpp | 11 + Telegram/SourceFiles/data/data_channel.cpp | 29 +- Telegram/SourceFiles/data/data_channel.h | 1 + Telegram/SourceFiles/data/data_community.cpp | 251 +++++++++++++++++- Telegram/SourceFiles/data/data_community.h | 44 +++ Telegram/SourceFiles/data/data_histories.cpp | 5 + Telegram/SourceFiles/data/data_session.cpp | 11 +- .../SourceFiles/dialogs/dialogs_entry.cpp | 7 +- .../SourceFiles/dialogs/ui/dialogs_layout.cpp | 53 ++++ Telegram/SourceFiles/history/history.cpp | 79 +++++- Telegram/SourceFiles/history/history.h | 8 + 11 files changed, 471 insertions(+), 28 deletions(-) diff --git a/Telegram/SourceFiles/api/api_communities.cpp b/Telegram/SourceFiles/api/api_communities.cpp index 2c08bb83a3..0e7f428ca3 100644 --- a/Telegram/SourceFiles/api/api_communities.cpp +++ b/Telegram/SourceFiles/api/api_communities.cpp @@ -179,6 +179,16 @@ void Communities::toggleCollapsedInDialogs( if (!_collapseRequests.emplace(community).second) { return; } + const auto was = (community->flags() + & ChannelDataFlag::CommunityCollapsed) != 0; + const auto apply = [=](bool value) { + if (value) { + community->addFlags(ChannelDataFlag::CommunityCollapsed); + } else { + community->removeFlags(ChannelDataFlag::CommunityCollapsed); + } + }; + apply(collapsed); using Flag = MTPcommunities_ToggleCommunityCollapsedInDialogs::Flag; _api.request(MTPcommunities_ToggleCommunityCollapsedInDialogs( MTP_flags(collapsed ? Flag::f_collapsed : Flag()), @@ -188,6 +198,7 @@ void Communities::toggleCollapsedInDialogs( _session->api().applyUpdates(result); }).fail([=] { _collapseRequests.remove(community); + apply(was); }).send(); } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 656f38e0f2..d16d751252 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -264,18 +264,14 @@ void ChannelData::setFlags(ChannelDataFlags which) { } } } - if (diff & Flag::CommunityCollapsed) { - const auto communityId = peerToChannel(id); - const auto refresh = [&](not_null peer) { - const auto channel = peer->asChannel(); - if (channel && channel->linkedCommunityId() == communityId) { - if (const auto history = owner().historyLoaded(channel)) { - history->updateChatListExistence(); - } - } - }; - owner().enumerateGroups(refresh); - owner().enumerateBroadcasts(refresh); + if ((which & Flag::Community) + && (diff & (Flag::Community + | Flag::CommunityCollapsed + | Flag::Forbidden + | Flag::Left))) { + if (const auto info = communityInfo()) { + info->collapsedChanged(); + } } if (const auto raw = takenForum.get()) { owner().forumIcons().clearUserpicsReset(raw); @@ -386,10 +382,19 @@ void ChannelData::setLinkedCommunityId(ChannelId id) { } _linkedCommunityId = id; if (const auto history = owner().historyLoaded(this)) { + history->updateCommunityRegistration(); + history->updateChatListSortPosition(); history->updateChatListExistence(); } } +not_null ChannelData::ensuredCommunityInfo() { + if (!_communityInfo) { + _communityInfo = std::make_unique(this); + } + return _communityInfo.get(); +} + ChannelId ChannelData::linkedCommunityId() const { return _linkedCommunityId; } diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index 0c0d4784f6..01e2214148 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -466,6 +466,7 @@ public: [[nodiscard]] Data::CommunityInfo *communityInfo() const { return _communityInfo.get(); } + [[nodiscard]] not_null ensuredCommunityInfo(); [[nodiscard]] bool canManageLinkedPeers() const; [[nodiscard]] bool communityAnyoneCanAddPeers() const; diff --git a/Telegram/SourceFiles/data/data_community.cpp b/Telegram/SourceFiles/data/data_community.cpp index 0748f59b47..80c613fee2 100644 --- a/Telegram/SourceFiles/data/data_community.cpp +++ b/Telegram/SourceFiles/data/data_community.cpp @@ -7,14 +7,103 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "data/data_community.h" +#include "data/data_changes.h" #include "data/data_channel.h" #include "data/data_peer.h" #include "data/data_session.h" +#include "history/history.h" +#include "history/history_item.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "ui/text/text_options.h" +#include "ui/text/text_utilities.h" +#include "styles/style_dialogs.h" namespace Data { +namespace { + +constexpr auto kShowChatNamesCount = 8; + +[[nodiscard]] TextWithEntities ComposeCommunityListEntryText( + not_null info) { + const auto &list = info->lastHistories(); + if (list.empty()) { + return {}; + } + + const auto count = std::max( + int(list.size()), + int(info->histories().size())); + + const auto throwAwayLastName = (list.size() > 1) + && (count == list.size() + 1); + auto &&peers = ranges::views::all( + list + ) | ranges::views::take( + list.size() - (throwAwayLastName ? 1 : 0) + ); + const auto wrapName = [](not_null history) { + const auto name = history->peer->name(); + return st::wrap_rtl(TextWithEntities{ + .text = name, + .entities = (history->chatListBadgesState().unread + ? EntitiesInText{ + { + EntityType::Semibold, + 0, + int(name.size()), + QString(), + }, + { + EntityType::Colorized, + 0, + int(name.size()), + QString(), + }, + } + : EntitiesInText{}), + }); + }; + const auto shown = int(peers.size()); + const auto accumulated = [&] { + Expects(shown > 0); + + auto i = peers.begin(); + auto result = wrapName(*i); + for (++i; i != peers.end(); ++i) { + result = tr::lng_archived_last_list( + tr::now, + lt_accumulated, + result, + lt_chat, + wrapName(*i), + tr::marked); + } + return result; + }(); + return (shown < count) + ? tr::lng_archived_last( + tr::now, + lt_count, + (count - shown), + lt_chats, + accumulated, + tr::marked) + : accumulated; +} + +} // namespace CommunityInfo::CommunityInfo(not_null channel) : _channel(channel) { + _channel->session().changes().peerUpdates( + PeerUpdate::Flag::Name + ) | rpl::filter([=](const PeerUpdate &update) { + return ranges::contains(_lastHistories, update.peer, &History::peer); + }) | rpl::on_next([=] { + ++_chatListViewVersion; + repaintRow(); + }, _lifetime); } void CommunityInfo::applyLinkedPeers(const QVector &list) { @@ -32,10 +121,32 @@ void CommunityInfo::applyLinkedPeers(const QVector &list) { .visible = visible, }); } - if (_linkedPeers != now) { - _linkedPeers = std::move(now); - _linkedPeersChanges.fire({}); + if (_linkedPeers == now) { + return; } + _linkedPeers = std::move(now); + + const auto communityId = peerToChannel(_channel->id); + for (const auto &linked : _linkedPeers) { + if (const auto channel = linked.peer->asChannel()) { + channel->setLinkedCommunityId(communityId); + } + } + const auto stillLinked = [&](not_null history) { + return ranges::contains( + _linkedPeers, + history->peer, + &CommunityLinkedPeer::peer); + }; + for (const auto &history : base::duplicate(_histories)) { + if (!stillLinked(history)) { + const auto channel = history->peer->asChannel(); + if (channel && channel->linkedCommunityId() == communityId) { + channel->setLinkedCommunityId(ChannelId()); + } + } + } + _linkedPeersChanges.fire({}); } rpl::producer<> CommunityInfo::linkedPeersValue() const { @@ -44,4 +155,138 @@ rpl::producer<> CommunityInfo::linkedPeersValue() const { ) | rpl::then(_linkedPeersChanges.events()); } +bool CommunityInfo::collapsedInDialogs() const { + return _channel->flags() & ChannelDataFlag::CommunityCollapsed; +} + +void CommunityInfo::collapsedChanged() { + for (const auto &history : _histories) { + history->updateChatListSortPosition(); + history->updateChatListExistence(); + } + ensureRowInChatList(); + repaintRow(); +} + +void CommunityInfo::ensureRowInChatList() { + const auto history = _channel->owner().history(_channel); + if (!history->folderKnown()) { + history->clearFolder(); + } + history->requestChatListMessage(); + history->updateChatListSortPosition(); + history->updateChatListExistence(); +} + +void CommunityInfo::registerOne(not_null history) { + if (!_histories.emplace(history).second) { + return; + } + const auto date = history->chatListTimeId(); + if (date > _chatsListDate) { + _chatsListDate = date; + } + reorderLastHistories(); + if (collapsedInDialogs()) { + history->updateChatListExistence(); + } + ensureRowInChatList(); + updateRowSortPosition(); +} + +void CommunityInfo::unregisterOne(not_null history) { + if (!_histories.remove(history)) { + return; + } + if (history->chatListTimeId() >= _chatsListDate) { + recountChatsListDate(); + } + reorderLastHistories(); + updateRowSortPosition(); +} + +void CommunityInfo::oneChatsListDateChanged(TimeId was, TimeId now) { + if (now >= _chatsListDate) { + _chatsListDate = now; + updateRowSortPosition(); + } else if (was >= _chatsListDate) { + recountChatsListDate(); + updateRowSortPosition(); + } +} + +void CommunityInfo::oneListMessageChanged() { + reorderLastHistories(); +} + +void CommunityInfo::oneUnreadStateChanged() { + ++_chatListViewVersion; + repaintRow(); +} + +void CommunityInfo::recountChatsListDate() { + auto result = TimeId(0); + for (const auto &history : _histories) { + const auto channel = history->peer->asChannel(); + if (channel && !channel->amIn()) { + continue; + } + result = std::max(result, history->chatListTimeId()); + } + _chatsListDate = result; +} + +void CommunityInfo::reorderLastHistories() { + const auto pred = [](not_null a, not_null b) { + const auto aItem = a->chatListMessage(); + const auto bItem = b->chatListMessage(); + const auto aDate = aItem ? aItem->date() : TimeId(0); + const auto bDate = bItem ? bItem->date() : TimeId(0); + return aDate > bDate; + }; + _lastHistories.clear(); + _lastHistories.reserve( + std::min(int(_histories.size()), kShowChatNamesCount)); + for (const auto &history : _histories) { + const auto channel = history->peer->asChannel(); + if (channel && !channel->amIn()) { + continue; + } + const auto i = ranges::upper_bound(_lastHistories, history, pred); + if (int(_lastHistories.size()) < kShowChatNamesCount + || i != end(_lastHistories)) { + _lastHistories.insert(i, history); + } + if (int(_lastHistories.size()) > kShowChatNamesCount) { + _lastHistories.pop_back(); + } + } + ++_chatListViewVersion; + repaintRow(); +} + +void CommunityInfo::updateRowSortPosition() { + if (const auto history = _channel->owner().historyLoaded(_channel)) { + history->updateChatListSortPosition(); + } +} + +void CommunityInfo::repaintRow() { + if (const auto history = _channel->owner().historyLoaded(_channel)) { + history->updateChatListEntry(); + } +} + +void CommunityInfo::validateListEntryCache() { + if (_listEntryCacheVersion == _chatListViewVersion) { + return; + } + _listEntryCacheVersion = _chatListViewVersion; + _listEntryCache.setMarkedText( + st::dialogsTextStyle, + ComposeCommunityListEntryText(this), + // Use rich options as long as the entry text does not have user text. + Ui::ItemTextDefaultOptions()); +} + } // namespace Data diff --git a/Telegram/SourceFiles/data/data_community.h b/Telegram/SourceFiles/data/data_community.h index 7b66a21bfc..035096d44b 100644 --- a/Telegram/SourceFiles/data/data_community.h +++ b/Telegram/SourceFiles/data/data_community.h @@ -7,7 +7,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include "ui/text/text.h" + class ChannelData; +class History; class PeerData; namespace Data { @@ -36,11 +39,52 @@ public: } [[nodiscard]] rpl::producer<> linkedPeersValue() const; + [[nodiscard]] bool collapsedInDialogs() const; + void collapsedChanged(); + void ensureRowInChatList(); + + void registerOne(not_null history); + void unregisterOne(not_null history); + [[nodiscard]] auto histories() const + -> const base::flat_set> & { + return _histories; + } + + [[nodiscard]] TimeId chatsListDate() const { + return _chatsListDate; + } + void oneChatsListDateChanged(TimeId was, TimeId now); + void oneListMessageChanged(); + void oneUnreadStateChanged(); + + [[nodiscard]] auto lastHistories() const + -> const std::vector> & { + return _lastHistories; + } + void validateListEntryCache(); + [[nodiscard]] const Ui::Text::String &listEntryCache() const { + return _listEntryCache; + } + private: + void recountChatsListDate(); + void reorderLastHistories(); + void updateRowSortPosition(); + void repaintRow(); + const not_null _channel; std::vector _linkedPeers; rpl::event_stream<> _linkedPeersChanges; + base::flat_set> _histories; + std::vector> _lastHistories; + Ui::Text::String _listEntryCache; + int _listEntryCacheVersion = 0; + int _chatListViewVersion = 0; + TimeId _chatsListDate = 0; + + rpl::lifetime _lifetime; + }; } // namespace Data diff --git a/Telegram/SourceFiles/data/data_histories.cpp b/Telegram/SourceFiles/data/data_histories.cpp index 7968fbc0a1..1a27e34ed3 100644 --- a/Telegram/SourceFiles/data/data_histories.cpp +++ b/Telegram/SourceFiles/data/data_histories.cpp @@ -377,6 +377,11 @@ void Histories::requestDialogEntry(not_null folder) { void Histories::requestDialogEntry( not_null history, Fn callback) { + if (const auto channel = history->peer->asChannel()) { + if (channel->isCommunity()) { + return; + } + } const auto i = _dialogRequests.find(history); if (i != end(_dialogRequests)) { if (callback) { diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp index 2324896cad..c92bfa132c 100644 --- a/Telegram/SourceFiles/data/data_session.cpp +++ b/Telegram/SourceFiles/data/data_session.cpp @@ -1219,10 +1219,11 @@ not_null Session::processChat(const MTPChat &data) { channel->setPhoto(data.vphoto()); using Flag = ChannelDataFlag; - const auto flagsMask = Flag::Community - | Flag::CommunityCollapsed - | (!minimal ? (Flag::Left | Flag::Creator) : Flag()); const auto collapsed = data.vcollapsed_in_dialogs(); + const auto flagsMask = Flag::Community + | Flag::Forbidden + | (collapsed ? Flag::CommunityCollapsed : Flag()) + | (!minimal ? (Flag::Left | Flag::Creator) : Flag()); const auto flagsSet = Flag::Community | ((collapsed && mtpIsTrue(*collapsed)) ? Flag::CommunityCollapsed @@ -1236,7 +1237,9 @@ not_null Session::processChat(const MTPChat &data) { const auto channel = result->asChannel(); using Flag = ChannelDataFlag; - const auto flagsMask = Flag::Community | Flag::Forbidden; + const auto flagsMask = Flag::Community + | Flag::CommunityCollapsed + | Flag::Forbidden; const auto flagsSet = Flag::Community | Flag::Forbidden; channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); diff --git a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp index 2796d3a2c1..5c93c8e501 100644 --- a/Telegram/SourceFiles/dialogs/dialogs_entry.cpp +++ b/Telegram/SourceFiles/dialogs/dialogs_entry.cpp @@ -420,11 +420,16 @@ PositionChange Entry::adjustByPosInChatList( } void Entry::setChatListTimeId(TimeId date) { - _timeId = date; + const auto was = std::exchange(_timeId, date); updateChatListSortPosition(); if (const auto folder = this->folder()) { folder->updateChatListSortPosition(); } + if (was != date) { + if (const auto history = asHistory()) { + history->communityChatsListDateChanged(was); + } + } } int Entry::posInChatList(FilterId filterId) const { diff --git a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp index b94497266b..41ff68897c 100644 --- a/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp +++ b/Telegram/SourceFiles/dialogs/ui/dialogs_layout.cpp @@ -372,6 +372,45 @@ void PaintFolderEntryText( }); } +[[nodiscard]] Data::CommunityInfo *CommunityListInfo(History *history) { + const auto channel = history ? history->peer->asChannel() : nullptr; + const auto info = (channel && channel->isCommunity()) + ? channel->communityInfo() + : nullptr; + return (info && !info->lastHistories().empty()) ? info : nullptr; +} + +void PaintCommunityEntryText( + Painter &p, + not_null info, + const PaintContext &context, + QRect rect) { + if (rect.isEmpty()) { + return; + } + info->validateListEntryCache(); + p.setFont(st::dialogsTextFont); + p.setPen(context.active + ? st::dialogsTextFgActive + : context.selected + ? st::dialogsTextFgOver + : st::dialogsTextFg); + info->listEntryCache().draw(p, { + .position = rect.topLeft(), + .availableWidth = rect.width(), + .palette = &(context.active + ? st::dialogsTextPaletteArchiveActive + : context.selected + ? st::dialogsTextPaletteArchiveOver + : st::dialogsTextPaletteArchive), + .spoiler = Text::DefaultSpoilerCache(), + .now = context.now, + .pausedEmoji = context.paused || On(PowerSaving::kEmojiChat), + .pausedSpoiler = context.paused || On(PowerSaving::kChatSpoiler), + .elisionHeight = rect.height(), + }); +} + enum class Flag { SavedMessages = 0x008, RepliesMessages = 0x010, @@ -580,6 +619,20 @@ void PaintRow( availableWidth, st::dialogsTextFont->height); PaintFolderEntryText(p, folder, context, rect); + } else if (const auto info = CommunityListInfo(history)) { + const auto availableWidth = PaintWideCounter( + p, + context, + badgesState, + texttop, + namewidth, + false); + const auto rect = QRect( + nameleft, + texttop, + availableWidth, + st::dialogsTextFont->height); + PaintCommunityEntryText(p, info, context, rect); } else if (promoted && !history->topPromotionMessage().isEmpty()) { auto availableWidth = namewidth; p.setFont(st::dialogsTextFont); diff --git a/Telegram/SourceFiles/history/history.cpp b/Telegram/SourceFiles/history/history.cpp index 8735f49322..e5b0c54d28 100644 --- a/Telegram/SourceFiles/history/history.cpp +++ b/Telegram/SourceFiles/history/history.cpp @@ -173,6 +173,7 @@ History::History(not_null owner, PeerId peerId) _outboxReadBefore = std::numeric_limits::max(); } } + updateCommunityRegistration(); } History::~History() = default; @@ -2302,6 +2303,9 @@ void History::setUnreadCount(int newUnreadCount) { } else if (!_firstUnreadView && !_unreadBarView && loadedAtBottom()) { calculateFirstUnreadMessage(); } + if (_communityInfo) { + _communityInfo->oneUnreadStateChanged(); + } } void History::setUnreadMark(bool unread) { @@ -2314,6 +2318,9 @@ void History::setUnreadMark(bool unread) { const auto notifier = unreadStateChangeNotifier( useMyUnreadInParent() && !unreadCount()); Thread::setUnreadMarkFlag(unread); + if (_communityInfo) { + _communityInfo->oneUnreadStateChanged(); + } } void History::setFakeUnreadWhileOpened(bool enabled) { @@ -2452,6 +2459,32 @@ void History::setFolderPointer(Data::Folder *folder) { session().changes().historyUpdated(this, UpdateFlag::Folder); } +void History::updateCommunityRegistration() { + const auto channel = peer->asChannel(); + const auto communityId = channel + ? channel->linkedCommunityId() + : ChannelId(); + const auto info = communityId + ? owner().channel(communityId)->ensuredCommunityInfo().get() + : nullptr; + if (_communityInfo == info) { + return; + } + if (const auto was = base::take(_communityInfo)) { + was->unregisterOne(this); + } + _communityInfo = info; + if (info) { + info->registerOne(this); + } +} + +void History::communityChatsListDateChanged(TimeId wasDate) { + if (_communityInfo) { + _communityInfo->oneChatsListDateChanged(wasDate, chatListTimeId()); + } +} + int History::chatListNameVersion() const { return peer->nameVersion(); } @@ -2511,6 +2544,13 @@ void History::applyPinnedUpdate(const MTPDupdateDialogPinned &data) { TimeId History::adjustedChatListTimeId() const { const auto result = chatListTimeId(); + if (const auto channel = peer->asChannel()) { + if (channel->isCommunity()) { + if (const auto info = channel->communityInfo()) { + return std::max(result, info->chatsListDate()); + } + } + } if (const auto draft = cloudDraft(MsgId(), PeerId())) { if (!peer->forum() && !Data::DraftIsNull(draft) @@ -2674,7 +2714,20 @@ Dialogs::UnreadState History::chatListUnreadState() const { } Dialogs::BadgesState History::chatListBadgesState() const { - if (const auto forum = peer->forum()) { + const auto channel = peer->asChannel(); + if (channel && channel->isCommunity()) { + if (const auto info = channel->communityInfo()) { + auto state = Dialogs::UnreadState(); + for (const auto &history : info->histories()) { + state += history->chatListUnreadState(); + } + return Dialogs::BadgesForUnread( + state, + Dialogs::CountInBadge::Chats, + Dialogs::IncludeInBadge::All); + } + return computeBadgesState(); + } else if (const auto forum = peer->forum()) { return adjustBadgesStateByFolder( Dialogs::BadgesForUnread( forum->topicsList()->unreadState(), @@ -3025,6 +3078,9 @@ void History::setChatListMessage(HistoryItem *item) { if (const auto folder = this->folder()) { folder->oneListMessageChanged(was, item); } + if (_communityInfo) { + _communityInfo->oneListMessageChanged(); + } if (const auto to = peer->migrateTo()) { if (const auto history = owner().historyLoaded(to)) { if (!history->chatListMessageKnown()) { @@ -3103,6 +3159,18 @@ void History::setChatListMessageUnknown() { } void History::requestChatListMessage() { + const auto channel = peer->asChannel(); + if (channel && channel->isCommunity()) { + // Communities have no own messages, the chats list row shows + // a list of community chat names instead of a last message. + if (!lastMessageKnown()) { + setLastMessage(nullptr); + } + if (!chatListMessageKnown()) { + setChatListMessage(nullptr); + } + return; + } if (!lastMessageKnown()) { owner().histories().requestDialogEntry(this, [=] { requestChatListMessage(); @@ -3271,13 +3339,8 @@ bool History::shouldBeInChatList() const { if (channel->isCommunity()) { return !(channel->flags() & ChannelDataFlag::Forbidden) && !channel->haveLeft(); - } else if (const auto communityId = channel->linkedCommunityId()) { - const auto community = owner().channelLoaded(communityId); - if (community - && (community->flags() - & ChannelDataFlag::CommunityCollapsed)) { - return false; - } + } else if (_communityInfo && _communityInfo->collapsedInDialogs()) { + return false; } if (!channel->amIn()) { return isTopPromoted(); diff --git a/Telegram/SourceFiles/history/history.h b/Telegram/SourceFiles/history/history.h index d76df92b08..4573913b2e 100644 --- a/Telegram/SourceFiles/history/history.h +++ b/Telegram/SourceFiles/history/history.h @@ -28,6 +28,7 @@ struct LanguageId; namespace Data { struct Draft; +class CommunityInfo; class Forum; class Session; class Folder; @@ -449,6 +450,12 @@ public: HistoryItem *folderDialogItem = nullptr); void clearFolder(); + [[nodiscard]] Data::CommunityInfo *communityListInfo() const { + return _communityInfo; + } + void updateCommunityRegistration(); + void communityChatsListDateChanged(TimeId wasDate); + // Interface for Data::Histories. void setInboxReadTill(MsgId upTo); std::optional countStillUnreadLocal(MsgId readTillId) const; @@ -653,6 +660,7 @@ private: bool _loadedAtBottom = true; std::optional _folder; + Data::CommunityInfo *_communityInfo = nullptr; std::optional _inboxReadBefore; std::optional _outboxReadBefore;