diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index a179f9d1f..cff2ff063 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -184,6 +184,8 @@ PRIVATE boxes/peers/edit_forum_topic_box.h boxes/peers/edit_linked_chat_box.cpp boxes/peers/edit_linked_chat_box.h + boxes/peers/edit_members_visible.cpp + boxes/peers/edit_members_visible.h boxes/peers/edit_participant_box.cpp boxes/peers/edit_participant_box.h boxes/peers/edit_participants_box.cpp diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 3efc57af7..f876b188e 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -1141,6 +1141,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_profile_set_group_photo" = "Set Photo"; "lng_profile_add_participant" = "Add Members"; "lng_profile_add_via_link" = "Invite via Link"; +"lng_profile_hide_participants" = "Hide Members"; +"lng_profile_hide_participants_about" = "Switch this on to hide the list of members in this group. Admins will remain visible."; "lng_profile_view_channel" = "View Channel"; "lng_profile_view_discussion" = "View discussion"; "lng_profile_join_channel" = "Join Channel"; diff --git a/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp new file mode 100644 index 000000000..f881a3462 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_members_visible.cpp @@ -0,0 +1,61 @@ +/* +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/edit_members_visible.h" + +#include "boxes/peers/edit_peer_info_box.h" +#include "data/data_channel.h" +#include "ui/rp_widget.h" +#include "ui/wrap/vertical_layout.h" +#include "ui/wrap/slide_wrap.h" +#include "ui/widgets/buttons.h" +#include "settings/settings_common.h" +#include "main/main_session.h" +#include "apiwrap.h" +#include "lang/lang_keys.h" +#include "styles/style_info.h" + +[[nodiscard]] object_ptr CreateMembersVisibleButton( + not_null megagroup) { + auto result = object_ptr((QWidget*)nullptr); + const auto container = result.data(); + + struct State { + rpl::event_stream toggled; + }; + Settings::AddSkip(container); + const auto state = container->lifetime().make_state(); + const auto button = container->add( + EditPeerInfoBox::CreateButton( + container, + tr::lng_profile_hide_participants(), + rpl::single(QString()), + [] {}, + st::manageGroupTopicsButton, + { &st::infoRoundedIconAntiSpam, Settings::kIconPurple } + ))->toggleOn(rpl::single( + (megagroup->flags() & ChannelDataFlag::ParticipantsHidden) != 0 + ) | rpl::then(state->toggled.events())); + Settings::AddSkip(container); + Settings::AddDividerText( + container, + tr::lng_profile_hide_participants_about()); + + button->toggledValue( + ) | rpl::start_with_next([=](bool toggled) { + megagroup->session().api().request( + MTPchannels_ToggleParticipantsHidden( + megagroup->inputChannel, + MTP_bool(toggled) + ) + ).done([=](const MTPUpdates &result) { + megagroup->session().api().applyUpdates(result); + }).send(); + }, button->lifetime()); + + return result; +} diff --git a/Telegram/SourceFiles/boxes/peers/edit_members_visible.h b/Telegram/SourceFiles/boxes/peers/edit_members_visible.h new file mode 100644 index 000000000..f8b01dc79 --- /dev/null +++ b/Telegram/SourceFiles/boxes/peers/edit_members_visible.h @@ -0,0 +1,19 @@ +/* +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 + +#include "base/object_ptr.h" + +class ChannelData; + +namespace Ui { +class RpWidget; +} // namespace Ui + +[[nodiscard]] object_ptr CreateMembersVisibleButton( + not_null megagroup); diff --git a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp index a3b33450f..3c4b1715a 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_participants_box.cpp @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "boxes/peers/edit_participant_box.h" #include "boxes/peers/add_participants_box.h" #include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox +#include "boxes/peers/edit_members_visible.h" #include "ui/boxes/confirm_box.h" #include "boxes/max_invite_box.h" #include "boxes/add_contact_box.h" @@ -1188,11 +1189,14 @@ void ParticipantsBoxController::prepare() { Unexpected("Role in ParticipantsBoxController::prepare()"); }(); if (const auto megagroup = _peer->asMegagroup()) { - if ((_role == Role::Admins) + if ((_role == Role::Members) && megagroup->canBanMembers()) { + delegate()->peerListSetAboveWidget(CreateMembersVisibleButton( + megagroup)); + } else if ((_role == Role::Admins) && (megagroup->amCreator() || megagroup->hasAdminRights())) { const auto validator = AntiSpamMenu::AntiSpamValidator( _navigation->parentController(), - _peer->asChannel()); + megagroup); delegate()->peerListSetAboveWidget(validator.createButton()); } } diff --git a/Telegram/SourceFiles/data/data_channel.cpp b/Telegram/SourceFiles/data/data_channel.cpp index 3609ced1d..307801555 100644 --- a/Telegram/SourceFiles/data/data_channel.cpp +++ b/Telegram/SourceFiles/data/data_channel.cpp @@ -577,7 +577,10 @@ bool ChannelData::allowsForwarding() const { } bool ChannelData::canViewMembers() const { - return flags() & Flag::CanViewParticipants; + return (flags() & Flag::CanViewParticipants) + && (!(flags() & Flag::ParticipantsHidden) + || amCreator() + || hasAdminRights()); } bool ChannelData::canViewAdmins() const { @@ -944,14 +947,20 @@ void ApplyChannelUpdate( | Flag::CanSetStickers | Flag::PreHistoryHidden | Flag::AntiSpam - | Flag::Location; + | Flag::Location + | Flag::ParticipantsHidden; channel->setFlags((channel->flags() & ~mask) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) - | (update.is_can_view_participants() ? Flag::CanViewParticipants : Flag()) + | (update.is_can_view_participants() + ? Flag::CanViewParticipants + : Flag()) | (update.is_can_set_stickers() ? Flag::CanSetStickers : Flag()) | (update.is_hidden_prehistory() ? Flag::PreHistoryHidden : Flag()) | (update.is_antispam() ? Flag::AntiSpam : Flag()) - | (update.vlocation() ? Flag::Location : Flag())); + | (update.vlocation() ? Flag::Location : Flag()) + | (update.is_participants_hidden() + ? Flag::ParticipantsHidden + : Flag())); channel->setUserpicPhoto(update.vchat_photo()); if (const auto migratedFrom = update.vmigrated_from_chat_id()) { channel->addFlags(Flag::Megagroup); diff --git a/Telegram/SourceFiles/data/data_channel.h b/Telegram/SourceFiles/data/data_channel.h index d345e1d02..7c928ad8c 100644 --- a/Telegram/SourceFiles/data/data_channel.h +++ b/Telegram/SourceFiles/data/data_channel.h @@ -58,6 +58,7 @@ enum class ChannelDataFlag { RequestToJoin = (1 << 22), Forum = (1 << 23), AntiSpam = (1 << 24), + ParticipantsHidden = (1 << 25), }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; using ChannelDataFlags = base::flags; diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp index 83795cb23..3a79c08b9 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp @@ -929,9 +929,12 @@ void ActionsFiller::addBlockAction(not_null user) { void ActionsFiller::addLeaveChannelAction(not_null channel) { Expects(_controller->parentController()); + AddActionButton( _wrap, - tr::lng_profile_leave_channel(), + (channel->isMegagroup() + ? tr::lng_profile_leave_group() + : tr::lng_profile_leave_channel()), AmInChannelValue(channel), Window::DeleteAndLeaveHandler( _controller->parentController(), @@ -947,7 +950,9 @@ void ActionsFiller::addJoinChannelAction( | rpl::start_spawning(_wrap->lifetime()); AddActionButton( _wrap, - tr::lng_profile_join_channel(), + (channel->isMegagroup() + ? tr::lng_profile_join_group() + : tr::lng_profile_join_channel()), rpl::duplicate(joinVisible), [=] { channel->session().api().joinChannel(channel); }, &st::infoIconAddMember); @@ -998,7 +1003,14 @@ void ActionsFiller::fillChannelActions( } object_ptr ActionsFiller::fill() { - auto wrapResult = [=](auto &&callback) { + const auto wrapToggled = [=]( + object_ptr content, + rpl::producer shown) { + auto result = object_ptr>(_parent, std::move(content)); + result->setDuration(0)->toggleOn(std::move(shown)); + return result; + }; + const auto wrapResult = [=](auto &&callback) { _wrap = object_ptr(_parent); _wrap->add(CreateSkipWidget(_wrap)); callback(); @@ -1010,8 +1022,11 @@ object_ptr ActionsFiller::fill() { fillUserActions(user); }); } else if (auto channel = _peer->asChannel()) { - if (channel->isMegagroup()) { - return { nullptr }; + if (const auto megagroup = channel->asMegagroup()) { + using namespace rpl::mappers; + return wrapToggled(wrapResult([=] { + fillChannelActions(megagroup); + }), CanViewParticipantsValue(megagroup) | rpl::map(!_1)); } return wrapResult([=] { fillChannelActions(channel); diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp index cdb435046..993267235 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp @@ -359,12 +359,7 @@ void Cover::setupChildGeometry() { } Cover *Cover::setOnlineCount(rpl::producer &&count) { - std::move( - count - ) | rpl::start_with_next([this](int count) { - _onlineCount = count; - refreshStatusText(); - }, lifetime()); + _onlineCount = std::move(count); return this; } @@ -377,18 +372,21 @@ void Cover::initViewers(rpl::producer title) { refreshNameGeometry(width()); }, lifetime()); - _peer->session().changes().peerFlagsValue( - _peer, - Flag::OnlineStatus | Flag::Members - ) | rpl::start_with_next( - [=] { refreshStatusText(); }, - lifetime()); + rpl::combine( + _peer->session().changes().peerFlagsValue( + _peer, + Flag::OnlineStatus | Flag::Members), + _onlineCount.value() + ) | rpl::start_with_next([=] { + refreshStatusText(); + }, lifetime()); + _peer->session().changes().peerFlagsValue( _peer, (_peer->isUser() ? Flag::IsContact : Flag::Rights) - ) | rpl::start_with_next( - [=] { refreshUploadPhotoOverlay(); }, - lifetime()); + ) | rpl::start_with_next([=] { + refreshUploadPhotoOverlay(); + }, lifetime()); } void Cover::refreshUploadPhotoOverlay() { @@ -451,15 +449,17 @@ void Cover::refreshStatusText() { if (!chat->amIn()) { return tr::lng_chat_status_unaccessible({}, WithEntities); } - auto fullCount = std::max( + const auto onlineCount = _onlineCount.current(); + const auto fullCount = std::max( chat->count, int(chat->participants.size())); - return { .text = ChatStatusText(fullCount, _onlineCount, true) }; + return { .text = ChatStatusText(fullCount, onlineCount, true) }; } else if (auto channel = _peer->asChannel()) { - auto fullCount = qMax(channel->membersCount(), 1); + const auto onlineCount = _onlineCount.current(); + const auto fullCount = qMax(channel->membersCount(), 1); auto result = ChatStatusText( fullCount, - _onlineCount, + onlineCount, channel->isMegagroup()); return hasMembersLink ? PlainLink(result) diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.h b/Telegram/SourceFiles/info/profile/info_profile_cover.h index 8a0fc6174..0b64ab904 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_cover.h +++ b/Telegram/SourceFiles/info/profile/info_profile_cover.h @@ -131,7 +131,7 @@ private: const not_null _peer; const std::unique_ptr _emojiStatusPanel; const std::unique_ptr _badge; - int _onlineCount = 0; + rpl::variable _onlineCount; object_ptr _userpic; object_ptr _iconButton; diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp index d9f22fe6f..28c2b3574 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.cpp @@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "info/profile/info_profile_inner_widget.h" -#include -#include -#include #include "info/info_memento.h" #include "info/info_controller.h" #include "info/profile/info_profile_widget.h" @@ -31,8 +28,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "window/window_session_controller.h" #include "storage/storage_shared_media.h" #include "lang/lang_keys.h" -#include "styles/style_info.h" -#include "styles/style_boxes.h" #include "ui/widgets/buttons.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/scroll_area.h" @@ -40,7 +35,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/widgets/box_content_divider.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/vertical_layout.h" +#include "data/data_channel.h" #include "data/data_shared_media.h" +#include "styles/style_info.h" +#include "styles/style_boxes.h" namespace Info { namespace Profile { @@ -102,27 +100,42 @@ object_ptr InnerWidget::setupContent( result->add(std::move(actions)); } - if (_peer->isChat() || _peer->isMegagroup()) { - _members = result->add(object_ptr( - result, - _controller)); - _members->scrollToRequests( - ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { - auto min = (request.ymin < 0) - ? request.ymin - : MapFrom(this, _members, QPoint(0, request.ymin)).y(); - auto max = (request.ymin < 0) - ? MapFrom(this, _members, QPoint()).y() - : (request.ymax < 0) - ? request.ymax - : MapFrom(this, _members, QPoint(0, request.ymax)).y(); - _scrollToRequests.fire({ min, max }); - }, _members->lifetime()); - _cover->setOnlineCount(_members->onlineCountValue()); + if (_peer->isChat()) { + setupMembers(result.data()); + } else if (const auto megagroup = _peer->asMegagroup()) { + CanViewParticipantsValue( + megagroup + ) | rpl::start_with_next([=, raw = result.data()](bool can) { + if (can) { + setupMembers(raw); + } else { + _cover->setOnlineCount(rpl::single(0)); + delete base::take(_members); + } + }, lifetime()); } return result; } +void InnerWidget::setupMembers(not_null container) { + _members = container->add(object_ptr( + container, + _controller)); + _members->scrollToRequests( + ) | rpl::start_with_next([this](Ui::ScrollToRequest request) { + auto min = (request.ymin < 0) + ? request.ymin + : MapFrom(this, _members, QPoint(0, request.ymin)).y(); + auto max = (request.ymin < 0) + ? MapFrom(this, _members, QPoint()).y() + : (request.ymax < 0) + ? request.ymax + : MapFrom(this, _members, QPoint(0, request.ymax)).y(); + _scrollToRequests.fire({ min, max }); + }, _members->lifetime()); + _cover->setOnlineCount(_members->onlineCountValue()); +} + object_ptr InnerWidget::setupSharedMedia( not_null parent) { using namespace rpl::mappers; diff --git a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h index 7addfe5c5..2e50d2135 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h +++ b/Telegram/SourceFiles/info/profile/info_profile_inner_widget.h @@ -58,6 +58,7 @@ protected: private: object_ptr setupContent(not_null parent); object_ptr setupSharedMedia(not_null parent); + void setupMembers(not_null container); int countDesiredHeight() const; void updateDesiredHeight() { diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.cpp b/Telegram/SourceFiles/info/profile/info_profile_values.cpp index d936ef435..878366d15 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.cpp +++ b/Telegram/SourceFiles/info/profile/info_profile_values.cpp @@ -549,6 +549,20 @@ rpl::producer FullReactionsCountValue( }) | rpl::distinct_until_changed(); } +rpl::producer CanViewParticipantsValue( + not_null megagroup) { + if (megagroup->amCreator()) { + return rpl::single(true); + } + return rpl::combine( + megagroup->session().changes().peerFlagsValue( + megagroup, + UpdateFlag::Rights), + megagroup->flagsValue(), + [=] { return megagroup->canViewMembers(); } + ) | rpl::distinct_until_changed(); +} + template rpl::producer BadgeValueFromFlags(Peer peer) { return rpl::combine( diff --git a/Telegram/SourceFiles/info/profile/info_profile_values.h b/Telegram/SourceFiles/info/profile/info_profile_values.h index 3b4cc19e3..b3a110fe4 100644 --- a/Telegram/SourceFiles/info/profile/info_profile_values.h +++ b/Telegram/SourceFiles/info/profile/info_profile_values.h @@ -106,6 +106,8 @@ rpl::producer> MigratedOrMeValue( not_null peer); [[nodiscard]] rpl::producer FullReactionsCountValue( not_null peer); +[[nodiscard]] rpl::producer CanViewParticipantsValue( + not_null megagroup); enum class BadgeType; [[nodiscard]] rpl::producer BadgeValue(not_null peer);