/* This file is part of Telegram Desktop, the official desktop application for the Telegram messaging service. For license and copyright information please follow this link: 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 = 20; [[nodiscard]] TextWithEntities ComposeCommunityListEntryText( not_null info) { const auto &list = info->lastHistories(); if (list.empty()) { return {}; } auto notInCount = 0; for (const auto &linked : info->linkedPeers()) { const auto channel = linked.peer->asChannel(); if (!channel || !channel->amIn()) { ++notInCount; } } auto &&peers = ranges::views::all(list); 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 notInCount ? tr::lng_archived_last( tr::now, lt_count, notInCount, lt_chats, accumulated, tr::marked) : accumulated; } } // namespace bool IsCommunityChatViewable(const CommunityLinkedPeer &linked) { return linked.canViewHistory; } CommunityInfo::CommunityInfo(not_null channel) : _channel(channel) , _chatsList( &channel->session(), FilterId(), channel->owner().maxPinnedChatsLimitValue( static_cast(nullptr))) { _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); } not_null CommunityInfo::chatsList() { return &_chatsList; } void CommunityInfo::applyLinkedPeers(const QVector &list) { auto now = std::vector(); now.reserve(list.size()); auto &owner = _channel->owner(); for (const auto &entry : list) { const auto &data = entry.data(); auto visible = std::optional(); if (const auto value = data.vvisible()) { visible = mtpIsTrue(*value); } now.push_back({ .peer = owner.peer(peerFromMTP(data.vpeer())), .visible = visible, .canViewHistory = data.is_can_view_history(), }); } if (_linkedPeers == now) { return; } _linkedPeers = std::move(now); _hiddenPeers.clear(); for (const auto &linked : _linkedPeers) { if (linked.visible.has_value() && !*linked.visible) { _hiddenPeers.emplace(linked.peer); } } 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); }; const auto unlinkStale = [&]( const base::flat_set> &set) { for (const auto &history : base::duplicate(set)) { if (!stillLinked(history)) { const auto channel = history->peer->asChannel(); if (channel && channel->linkedCommunityId() == communityId) { channel->setLinkedCommunityId(ChannelId()); } } } }; unlinkStale(_histories); unlinkStale(_otherHistories); ++_chatListViewVersion; repaintRow(); _linkedPeersChanges.fire({}); } rpl::producer<> CommunityInfo::linkedPeersValue() const { return rpl::single( rpl::empty ) | rpl::then(_linkedPeersChanges.events()); } rpl::producer<> CommunityInfo::refreshed() const { return _refreshed.events(); } bool CommunityInfo::isHidden(not_null peer) const { return _hiddenPeers.contains(peer); } bool CommunityInfo::collapsedInDialogs() const { return _channel->collapsedInDialogs(); } void CommunityInfo::moveHistory( not_null history, bool nowCollapsed) { if (!history->inChatList()) { history->updateChatListSortPosition(); history->updateChatListExistence(); return; } auto &owner = _channel->owner(); const auto wasList = nowCollapsed ? owner.chatsList(history->folder()) : chatsList(); history->removeFromChatList(0, wasList); history->updateChatListSortPosition(); } void CommunityInfo::collapsedChanged() { const auto nowCollapsed = collapsedInDialogs(); for (const auto &history : _histories) { moveHistory(history, nowCollapsed); } 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) { const auto channel = history->peer->asChannel(); if (channel && channel->amIn()) { if (!_histories.emplace(history).second) { return; } memberAdded(history); } else if (!_otherHistories.emplace(history).second) { return; } ensureRowInChatList(); updateRowSortPosition(); } void CommunityInfo::unregisterOne(not_null history) { if (!_histories.remove(history)) { _otherHistories.remove(history); return; } if (history->chatListTimeId() >= _chatsListDate) { recountChatsListDate(); } reorderLastHistories(); updateRowSortPosition(); } void CommunityInfo::refreshOneMembership(not_null history) { const auto channel = history->peer->asChannel(); if (channel && channel->amIn()) { // A non-member linked chat the user just joined moves into the // member aggregate. if (_histories.contains(history) || !_otherHistories.remove(history)) { return; } _histories.emplace(history); memberAdded(history); updateRowSortPosition(); } else { // A member chat the user just left moves out of the aggregate. if (!_histories.remove(history)) { return; } _otherHistories.emplace(history); if (history->chatListTimeId() >= _chatsListDate) { recountChatsListDate(); } reorderLastHistories(); updateRowSortPosition(); } } void CommunityInfo::memberAdded(not_null history) { const auto date = history->chatListTimeId(); if (date > _chatsListDate) { _chatsListDate = date; } reorderLastHistories(); if (collapsedInDialogs()) { history->updateChatListExistence(); } } 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) { 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 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(); } _refreshed.fire({}); } 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