From 0ce83f64a2699e5f2d5b28a1d0b9870c7de273e6 Mon Sep 17 00:00:00 2001 From: John Preston Date: Fri, 12 Jun 2026 09:44:08 +0400 Subject: [PATCH] Add view community box with chats. --- Telegram/CMakeLists.txt | 2 + Telegram/Resources/langs/lang.strings | 6 + .../SourceFiles/boxes/peers/community_box.cpp | 348 ++++++++++++++++++ .../SourceFiles/boxes/peers/community_box.h | 22 ++ .../window/window_session_controller.cpp | 7 + 5 files changed, 385 insertions(+) create mode 100644 Telegram/SourceFiles/boxes/peers/community_box.cpp create mode 100644 Telegram/SourceFiles/boxes/peers/community_box.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index e0bf5de921..d0fe567e1b 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -230,6 +230,8 @@ PRIVATE boxes/peers/channel_ownership_transfer.h boxes/peers/choose_peer_box.cpp boxes/peers/choose_peer_box.h + boxes/peers/community_box.cpp + boxes/peers/community_box.h boxes/peers/create_managed_bot_box.cpp boxes/peers/create_managed_bot_box.h boxes/peers/edit_contact_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 992fcbf976..ce9548cf6c 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2234,6 +2234,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_community_admin_promote_title" = "Make Community Admin"; "lng_community_admin_promote_sure" = "{user} will be able to edit community info and use other admin tools."; "lng_community_admin_promote" = "Promote"; +"lng_community_show_as_one" = "Show as One Chat"; +"lng_community_show_as_one_about" = "Group all community chats into one item in the chat list."; +"lng_community_chats_joined" = "Chats you are in"; +"lng_community_chats_viewable" = "Chats you can view"; +"lng_community_chats_requestable" = "Chats you can request to join"; +"lng_community_add_chat" = "Add a Chat to Community"; "lng_manage_monoforum" = "Direct Messages"; "lng_manage_monoforum_off" = "Off"; diff --git a/Telegram/SourceFiles/boxes/peers/community_box.cpp b/Telegram/SourceFiles/boxes/peers/community_box.cpp new file mode 100644 index 0000000000..d65fc9eb16 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/community_box.cpp @@ -0,0 +1,348 @@ +/* +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 "boxes/peers/community_box.h" + +#include "api/api_communities.h" +#include "apiwrap.h" +#include "boxes/peer_list_box.h" +#include "boxes/peers/add_to_community_box.h" +#include "boxes/peers/manage_community_box.h" +#include "data/data_changes.h" +#include "data/data_channel.h" +#include "data/data_community.h" +#include "data/data_peer_values.h" +#include "data/data_session.h" +#include "info/profile/info_profile_values.h" +#include "lang/lang_keys.h" +#include "main/main_session.h" +#include "main/session/session_show.h" +#include "settings/settings_common.h" +#include "ui/layers/generic_box.h" +#include "ui/vertical_list.h" +#include "ui/widgets/buttons.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/wrap/vertical_layout.h" +#include "window/window_session_controller.h" +#include "styles/style_boxes.h" +#include "styles/style_info.h" +#include "styles/style_layers.h" +#include "styles/style_menu_icons.h" +#include "styles/style_settings.h" + +namespace { + +struct PartitionedChats { + std::vector> joined; + std::vector> viewable; + std::vector> requestable; +}; + +[[nodiscard]] PartitionedChats PartitionChats( + not_null info) { + auto result = PartitionedChats(); + for (const auto &linked : info->linkedPeers()) { + const auto channel = linked.peer->asChannel(); + if (channel && channel->amIn()) { + result.joined.push_back(linked.peer); + } else if (channel && channel->isPublic()) { + result.viewable.push_back(linked.peer); + } else { + result.requestable.push_back(linked.peer); + } + } + return result; +} + +class ChatsController final : public PeerListController { +public: + ChatsController( + not_null session, + rpl::producer>> chats, + Fn)> callback); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + + [[nodiscard]] rpl::producer countValue() const { + return _count.value(); + } + +private: + const not_null _session; + rpl::producer>> _chats; + Fn)> _callback; + rpl::variable _count = 0; + +}; + +ChatsController::ChatsController( + not_null session, + rpl::producer>> chats, + Fn)> callback) +: _session(session) +, _chats(std::move(chats)) +, _callback(std::move(callback)) { +} + +Main::Session &ChatsController::session() const { + return *_session; +} + +void ChatsController::prepare() { + std::move( + _chats + ) | rpl::on_next([=](const std::vector> &list) { + while (delegate()->peerListFullRowsCount() > 0) { + delegate()->peerListRemoveRow( + delegate()->peerListRowAt( + delegate()->peerListFullRowsCount() - 1)); + } + for (const auto &peer : list) { + auto row = std::make_unique(peer); + const auto channel = peer->asChannel(); + if (channel && channel->membersCountKnown()) { + row->setCustomStatus(tr::lng_chat_status_members( + tr::now, + lt_count_decimal, + channel->membersCount())); + } + delegate()->peerListAppendRow(std::move(row)); + } + delegate()->peerListRefreshRows(); + _count = int(list.size()); + }, lifetime()); +} + +void ChatsController::rowClicked(not_null row) { + _callback(row->peer()); +} + +void CommunityBox( + not_null box, + not_null navigation, + not_null community) { + box->setTitle(Info::Profile::NameValue(community)); + box->setWidth(st::boxWideWidth); + + const auto info = community->communityInfo(); + if (!info) { + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); + return; + } + const auto container = box->verticalLayout(); + + if (community->canEditInformation()) { + box->addTopButton(st::infoTopBarMenu, [=] { + ShowManageCommunityBox(navigation, community); + }); + } + + const auto toggle = Settings::AddButtonWithIcon( + container, + tr::lng_community_show_as_one(), + st::settingsButtonNoIcon); + toggle->toggleOn(Data::PeerFlagValue( + community.get(), + ChannelDataFlag::CommunityCollapsed)); + toggle->toggledChanges( + ) | rpl::filter([=](bool toggled) { + const auto flags = community->flags(); + return toggled != ((flags & ChannelDataFlag::CommunityCollapsed) + != 0); + }) | rpl::on_next([=](bool toggled) { + community->session().api().communities().toggleCollapsedInDialogs( + community, + toggled); + }, toggle->lifetime()); + Ui::AddSkip(container); + Ui::AddDividerText(container, tr::lng_community_show_as_one_about()); + + auto chats = info->linkedPeersValue( + ) | rpl::map([=] { + return PartitionChats(info); + }) | rpl::start_spawning(box->lifetime()); + + const auto openChat = [=](not_null peer) { + navigation->showPeerHistory( + peer, + Window::SectionShow::Way::ClearStack, + ShowAtUnreadMsgId); + }; + + const auto addSection = [&]( + rpl::producer title, + rpl::producer>> list) { + const auto wrap = container->add( + object_ptr>( + container, + object_ptr(container))); + const auto inner = wrap->entity(); + Ui::AddSkip(inner); + Ui::AddSubsectionTitle(inner, std::move(title)); + + class Delegate final : public PeerListContentDelegateSimple { + public: + explicit Delegate(std::shared_ptr show) + : _show(std::move(show)) { + } + + std::shared_ptr peerListUiShow() override { + return _show; + } + + private: + const std::shared_ptr _show; + + }; + const auto delegate = inner->lifetime().make_state( + Main::MakeSessionShow(box->uiShow(), &community->session())); + const auto controller = inner->lifetime().make_state< + ChatsController + >(&community->session(), std::move(list), openChat); + const auto content = inner->add(object_ptr( + inner, + controller)); + delegate->setContent(content); + controller->setDelegate(delegate); + + wrap->toggleOn( + controller->countValue() | rpl::map(rpl::mappers::_1 > 0), + anim::type::instant); + wrap->finishAnimating(); + }; + + addSection( + tr::lng_community_chats_joined(), + rpl::duplicate(chats) | rpl::map([](const PartitionedChats &c) { + return c.joined; + })); + addSection( + tr::lng_community_chats_viewable(), + rpl::duplicate(chats) | rpl::map([](const PartitionedChats &c) { + return c.viewable; + })); + addSection( + tr::lng_community_chats_requestable(), + rpl::duplicate(chats) | rpl::map([](const PartitionedChats &c) { + return c.requestable; + })); + + const auto canAdd = community->canManageLinkedPeers() + || community->communityAnyoneCanAddPeers(); + if (canAdd) { + box->addButton(tr::lng_community_add_chat(), [=] { + ShowChooseChatToAddBox(navigation, community); + }); + } else { + box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); + } + + if (!community->wasFullUpdated()) { + community->session().api().requestFullPeer(community); + } +} + +class ChooseChatController final : public PeerListController { +public: + ChooseChatController( + not_null session, + not_null community, + Fn)> callback); + + Main::Session &session() const override; + void prepare() override; + void rowClicked(not_null row) override; + +private: + const not_null _session; + const not_null _community; + Fn)> _callback; + +}; + +ChooseChatController::ChooseChatController( + not_null session, + not_null community, + Fn)> callback) +: _session(session) +, _community(community) +, _callback(std::move(callback)) { +} + +Main::Session &ChooseChatController::session() const { + return *_session; +} + +void ChooseChatController::prepare() { + delegate()->peerListSetTitle(tr::lng_community_add_chat()); + auto candidates = std::vector>(); + _session->data().enumerateGroups([&](not_null peer) { + const auto channel = peer->asChannel(); + if (channel + && channel->isMegagroup() + && channel->amCreator() + && !channel->isForbidden() + && !channel->isMonoforum() + && !channel->linkedCommunityId()) { + candidates.push_back(channel); + } + }); + ranges::sort(candidates, []( + not_null a, + not_null b) { + return a->name().compare(b->name(), Qt::CaseInsensitive) < 0; + }); + for (const auto &channel : candidates) { + if (delegate()->peerListFindRow(channel->id.value)) { + continue; + } + auto row = std::make_unique(channel); + if (channel->membersCountKnown()) { + row->setCustomStatus(tr::lng_chat_status_members( + tr::now, + lt_count_decimal, + channel->membersCount())); + } + delegate()->peerListAppendRow(std::move(row)); + } + delegate()->peerListRefreshRows(); +} + +void ChooseChatController::rowClicked(not_null row) { + if (const auto channel = row->peer()->asChannel()) { + _callback(channel); + } +} + +} // namespace + +void ShowCommunityBox( + not_null navigation, + not_null community) { + navigation->uiShow()->showBox( + Box(CommunityBox, navigation, community)); +} + +void ShowChooseChatToAddBox( + not_null navigation, + not_null community) { + const auto choose = [=](not_null group) { + ShowAddPeerToCommunity(navigation, community, group); + }; + auto controller = std::make_unique( + &community->session(), + community, + choose); + const auto init = [=](not_null box) { + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + }; + navigation->uiShow()->showBox(Box( + std::move(controller), + init)); +} diff --git a/Telegram/SourceFiles/boxes/peers/community_box.h b/Telegram/SourceFiles/boxes/peers/community_box.h new file mode 100644 index 0000000000..521dfc1d22 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/community_box.h @@ -0,0 +1,22 @@ +/* +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 +*/ +#pragma once + +class ChannelData; + +namespace Window { +class SessionNavigation; +} // namespace Window + +void ShowCommunityBox( + not_null navigation, + not_null community); + +void ShowChooseChatToAddBox( + not_null navigation, + not_null community); diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index c0ba7eb387..e62d4055eb 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -11,6 +11,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_cloud_password.h" #include "api/api_text_entities.h" #include "boxes/peers/add_bot_to_chat_box.h" +#include "boxes/peers/community_box.h" #include "boxes/peers/edit_peer_info_box.h" #include "boxes/peers/replace_boost_box.h" #include "boxes/add_contact_box.h" @@ -2702,6 +2703,12 @@ bool SessionController::canShowSeparateWindow(SeparateId id) const { } void SessionController::showPeer(not_null peer, MsgId msgId) { + if (const auto channel = peer->asChannel()) { + if (channel->isCommunity()) { + ShowCommunityBox(this, channel); + return; + } + } const auto currentPeer = activeChatCurrent().peer(); if (peer && peer->isChannel() && currentPeer != peer) { const auto clickedChannel = peer->asChannel();