Allow hiding members list in groups.

This commit is contained in:
John Preston 2022-12-16 18:22:56 +04:00
parent b0a24238e8
commit af350e2daa
14 changed files with 196 additions and 53 deletions

View file

@ -184,6 +184,8 @@ PRIVATE
boxes/peers/edit_forum_topic_box.h boxes/peers/edit_forum_topic_box.h
boxes/peers/edit_linked_chat_box.cpp boxes/peers/edit_linked_chat_box.cpp
boxes/peers/edit_linked_chat_box.h 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.cpp
boxes/peers/edit_participant_box.h boxes/peers/edit_participant_box.h
boxes/peers/edit_participants_box.cpp boxes/peers/edit_participants_box.cpp

View file

@ -1141,6 +1141,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_profile_set_group_photo" = "Set Photo"; "lng_profile_set_group_photo" = "Set Photo";
"lng_profile_add_participant" = "Add Members"; "lng_profile_add_participant" = "Add Members";
"lng_profile_add_via_link" = "Invite via Link"; "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_channel" = "View Channel";
"lng_profile_view_discussion" = "View discussion"; "lng_profile_view_discussion" = "View discussion";
"lng_profile_join_channel" = "Join Channel"; "lng_profile_join_channel" = "Join Channel";

View file

@ -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<Ui::RpWidget> CreateMembersVisibleButton(
not_null<ChannelData*> megagroup) {
auto result = object_ptr<Ui::VerticalLayout>((QWidget*)nullptr);
const auto container = result.data();
struct State {
rpl::event_stream<bool> toggled;
};
Settings::AddSkip(container);
const auto state = container->lifetime().make_state<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;
}

View file

@ -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<Ui::RpWidget> CreateMembersVisibleButton(
not_null<ChannelData*> megagroup);

View file

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/peers/edit_participant_box.h" #include "boxes/peers/edit_participant_box.h"
#include "boxes/peers/add_participants_box.h" #include "boxes/peers/add_participants_box.h"
#include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox #include "boxes/peers/prepare_short_info_box.h" // PrepareShortInfoBox
#include "boxes/peers/edit_members_visible.h"
#include "ui/boxes/confirm_box.h" #include "ui/boxes/confirm_box.h"
#include "boxes/max_invite_box.h" #include "boxes/max_invite_box.h"
#include "boxes/add_contact_box.h" #include "boxes/add_contact_box.h"
@ -1188,11 +1189,14 @@ void ParticipantsBoxController::prepare() {
Unexpected("Role in ParticipantsBoxController::prepare()"); Unexpected("Role in ParticipantsBoxController::prepare()");
}(); }();
if (const auto megagroup = _peer->asMegagroup()) { 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())) { && (megagroup->amCreator() || megagroup->hasAdminRights())) {
const auto validator = AntiSpamMenu::AntiSpamValidator( const auto validator = AntiSpamMenu::AntiSpamValidator(
_navigation->parentController(), _navigation->parentController(),
_peer->asChannel()); megagroup);
delegate()->peerListSetAboveWidget(validator.createButton()); delegate()->peerListSetAboveWidget(validator.createButton());
} }
} }

View file

@ -577,7 +577,10 @@ bool ChannelData::allowsForwarding() const {
} }
bool ChannelData::canViewMembers() const { bool ChannelData::canViewMembers() const {
return flags() & Flag::CanViewParticipants; return (flags() & Flag::CanViewParticipants)
&& (!(flags() & Flag::ParticipantsHidden)
|| amCreator()
|| hasAdminRights());
} }
bool ChannelData::canViewAdmins() const { bool ChannelData::canViewAdmins() const {
@ -944,14 +947,20 @@ void ApplyChannelUpdate(
| Flag::CanSetStickers | Flag::CanSetStickers
| Flag::PreHistoryHidden | Flag::PreHistoryHidden
| Flag::AntiSpam | Flag::AntiSpam
| Flag::Location; | Flag::Location
| Flag::ParticipantsHidden;
channel->setFlags((channel->flags() & ~mask) channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (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_can_set_stickers() ? Flag::CanSetStickers : Flag())
| (update.is_hidden_prehistory() ? Flag::PreHistoryHidden : Flag()) | (update.is_hidden_prehistory() ? Flag::PreHistoryHidden : Flag())
| (update.is_antispam() ? Flag::AntiSpam : 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()); channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) { if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
channel->addFlags(Flag::Megagroup); channel->addFlags(Flag::Megagroup);

View file

@ -58,6 +58,7 @@ enum class ChannelDataFlag {
RequestToJoin = (1 << 22), RequestToJoin = (1 << 22),
Forum = (1 << 23), Forum = (1 << 23),
AntiSpam = (1 << 24), AntiSpam = (1 << 24),
ParticipantsHidden = (1 << 25),
}; };
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>; using ChannelDataFlags = base::flags<ChannelDataFlag>;

View file

@ -929,9 +929,12 @@ void ActionsFiller::addBlockAction(not_null<UserData*> user) {
void ActionsFiller::addLeaveChannelAction(not_null<ChannelData*> channel) { void ActionsFiller::addLeaveChannelAction(not_null<ChannelData*> channel) {
Expects(_controller->parentController()); Expects(_controller->parentController());
AddActionButton( AddActionButton(
_wrap, _wrap,
tr::lng_profile_leave_channel(), (channel->isMegagroup()
? tr::lng_profile_leave_group()
: tr::lng_profile_leave_channel()),
AmInChannelValue(channel), AmInChannelValue(channel),
Window::DeleteAndLeaveHandler( Window::DeleteAndLeaveHandler(
_controller->parentController(), _controller->parentController(),
@ -947,7 +950,9 @@ void ActionsFiller::addJoinChannelAction(
| rpl::start_spawning(_wrap->lifetime()); | rpl::start_spawning(_wrap->lifetime());
AddActionButton( AddActionButton(
_wrap, _wrap,
tr::lng_profile_join_channel(), (channel->isMegagroup()
? tr::lng_profile_join_group()
: tr::lng_profile_join_channel()),
rpl::duplicate(joinVisible), rpl::duplicate(joinVisible),
[=] { channel->session().api().joinChannel(channel); }, [=] { channel->session().api().joinChannel(channel); },
&st::infoIconAddMember); &st::infoIconAddMember);
@ -998,7 +1003,14 @@ void ActionsFiller::fillChannelActions(
} }
object_ptr<Ui::RpWidget> ActionsFiller::fill() { object_ptr<Ui::RpWidget> ActionsFiller::fill() {
auto wrapResult = [=](auto &&callback) { const auto wrapToggled = [=](
object_ptr<Ui::RpWidget> content,
rpl::producer<bool> shown) {
auto result = object_ptr<Ui::SlideWrap<>>(_parent, std::move(content));
result->setDuration(0)->toggleOn(std::move(shown));
return result;
};
const auto wrapResult = [=](auto &&callback) {
_wrap = object_ptr<Ui::VerticalLayout>(_parent); _wrap = object_ptr<Ui::VerticalLayout>(_parent);
_wrap->add(CreateSkipWidget(_wrap)); _wrap->add(CreateSkipWidget(_wrap));
callback(); callback();
@ -1010,8 +1022,11 @@ object_ptr<Ui::RpWidget> ActionsFiller::fill() {
fillUserActions(user); fillUserActions(user);
}); });
} else if (auto channel = _peer->asChannel()) { } else if (auto channel = _peer->asChannel()) {
if (channel->isMegagroup()) { if (const auto megagroup = channel->asMegagroup()) {
return { nullptr }; using namespace rpl::mappers;
return wrapToggled(wrapResult([=] {
fillChannelActions(megagroup);
}), CanViewParticipantsValue(megagroup) | rpl::map(!_1));
} }
return wrapResult([=] { return wrapResult([=] {
fillChannelActions(channel); fillChannelActions(channel);

View file

@ -359,12 +359,7 @@ void Cover::setupChildGeometry() {
} }
Cover *Cover::setOnlineCount(rpl::producer<int> &&count) { Cover *Cover::setOnlineCount(rpl::producer<int> &&count) {
std::move( _onlineCount = std::move(count);
count
) | rpl::start_with_next([this](int count) {
_onlineCount = count;
refreshStatusText();
}, lifetime());
return this; return this;
} }
@ -377,18 +372,21 @@ void Cover::initViewers(rpl::producer<QString> title) {
refreshNameGeometry(width()); refreshNameGeometry(width());
}, lifetime()); }, lifetime());
_peer->session().changes().peerFlagsValue( rpl::combine(
_peer, _peer->session().changes().peerFlagsValue(
Flag::OnlineStatus | Flag::Members _peer,
) | rpl::start_with_next( Flag::OnlineStatus | Flag::Members),
[=] { refreshStatusText(); }, _onlineCount.value()
lifetime()); ) | rpl::start_with_next([=] {
refreshStatusText();
}, lifetime());
_peer->session().changes().peerFlagsValue( _peer->session().changes().peerFlagsValue(
_peer, _peer,
(_peer->isUser() ? Flag::IsContact : Flag::Rights) (_peer->isUser() ? Flag::IsContact : Flag::Rights)
) | rpl::start_with_next( ) | rpl::start_with_next([=] {
[=] { refreshUploadPhotoOverlay(); }, refreshUploadPhotoOverlay();
lifetime()); }, lifetime());
} }
void Cover::refreshUploadPhotoOverlay() { void Cover::refreshUploadPhotoOverlay() {
@ -451,15 +449,17 @@ void Cover::refreshStatusText() {
if (!chat->amIn()) { if (!chat->amIn()) {
return tr::lng_chat_status_unaccessible({}, WithEntities); return tr::lng_chat_status_unaccessible({}, WithEntities);
} }
auto fullCount = std::max( const auto onlineCount = _onlineCount.current();
const auto fullCount = std::max(
chat->count, chat->count,
int(chat->participants.size())); int(chat->participants.size()));
return { .text = ChatStatusText(fullCount, _onlineCount, true) }; return { .text = ChatStatusText(fullCount, onlineCount, true) };
} else if (auto channel = _peer->asChannel()) { } 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( auto result = ChatStatusText(
fullCount, fullCount,
_onlineCount, onlineCount,
channel->isMegagroup()); channel->isMegagroup());
return hasMembersLink return hasMembersLink
? PlainLink(result) ? PlainLink(result)

View file

@ -131,7 +131,7 @@ private:
const not_null<PeerData*> _peer; const not_null<PeerData*> _peer;
const std::unique_ptr<EmojiStatusPanel> _emojiStatusPanel; const std::unique_ptr<EmojiStatusPanel> _emojiStatusPanel;
const std::unique_ptr<Badge> _badge; const std::unique_ptr<Badge> _badge;
int _onlineCount = 0; rpl::variable<int> _onlineCount;
object_ptr<Ui::UserpicButton> _userpic; object_ptr<Ui::UserpicButton> _userpic;
object_ptr<TopicIconButton> _iconButton; object_ptr<TopicIconButton> _iconButton;

View file

@ -7,9 +7,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "info/profile/info_profile_inner_widget.h" #include "info/profile/info_profile_inner_widget.h"
#include <rpl/combine.h>
#include <rpl/combine_previous.h>
#include <rpl/flatten_latest.h>
#include "info/info_memento.h" #include "info/info_memento.h"
#include "info/info_controller.h" #include "info/info_controller.h"
#include "info/profile/info_profile_widget.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 "window/window_session_controller.h"
#include "storage/storage_shared_media.h" #include "storage/storage_shared_media.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "styles/style_info.h"
#include "styles/style_boxes.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/widgets/checkbox.h" #include "ui/widgets/checkbox.h"
#include "ui/widgets/scroll_area.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/widgets/box_content_divider.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/wrap/vertical_layout.h" #include "ui/wrap/vertical_layout.h"
#include "data/data_channel.h"
#include "data/data_shared_media.h" #include "data/data_shared_media.h"
#include "styles/style_info.h"
#include "styles/style_boxes.h"
namespace Info { namespace Info {
namespace Profile { namespace Profile {
@ -102,27 +100,42 @@ object_ptr<Ui::RpWidget> InnerWidget::setupContent(
result->add(std::move(actions)); result->add(std::move(actions));
} }
if (_peer->isChat() || _peer->isMegagroup()) { if (_peer->isChat()) {
_members = result->add(object_ptr<Members>( setupMembers(result.data());
result, } else if (const auto megagroup = _peer->asMegagroup()) {
_controller)); CanViewParticipantsValue(
_members->scrollToRequests( megagroup
) | rpl::start_with_next([this](Ui::ScrollToRequest request) { ) | rpl::start_with_next([=, raw = result.data()](bool can) {
auto min = (request.ymin < 0) if (can) {
? request.ymin setupMembers(raw);
: MapFrom(this, _members, QPoint(0, request.ymin)).y(); } else {
auto max = (request.ymin < 0) _cover->setOnlineCount(rpl::single(0));
? MapFrom(this, _members, QPoint()).y() delete base::take(_members);
: (request.ymax < 0) }
? request.ymax }, lifetime());
: MapFrom(this, _members, QPoint(0, request.ymax)).y();
_scrollToRequests.fire({ min, max });
}, _members->lifetime());
_cover->setOnlineCount(_members->onlineCountValue());
} }
return result; return result;
} }
void InnerWidget::setupMembers(not_null<Ui::VerticalLayout*> container) {
_members = container->add(object_ptr<Members>(
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<Ui::RpWidget> InnerWidget::setupSharedMedia( object_ptr<Ui::RpWidget> InnerWidget::setupSharedMedia(
not_null<RpWidget*> parent) { not_null<RpWidget*> parent) {
using namespace rpl::mappers; using namespace rpl::mappers;

View file

@ -58,6 +58,7 @@ protected:
private: private:
object_ptr<RpWidget> setupContent(not_null<RpWidget*> parent); object_ptr<RpWidget> setupContent(not_null<RpWidget*> parent);
object_ptr<RpWidget> setupSharedMedia(not_null<RpWidget*> parent); object_ptr<RpWidget> setupSharedMedia(not_null<RpWidget*> parent);
void setupMembers(not_null<Ui::VerticalLayout*> container);
int countDesiredHeight() const; int countDesiredHeight() const;
void updateDesiredHeight() { void updateDesiredHeight() {

View file

@ -549,6 +549,20 @@ rpl::producer<int> FullReactionsCountValue(
}) | rpl::distinct_until_changed(); }) | rpl::distinct_until_changed();
} }
rpl::producer<bool> CanViewParticipantsValue(
not_null<ChannelData*> 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 <typename Flag, typename Peer> template <typename Flag, typename Peer>
rpl::producer<BadgeType> BadgeValueFromFlags(Peer peer) { rpl::producer<BadgeType> BadgeValueFromFlags(Peer peer) {
return rpl::combine( return rpl::combine(

View file

@ -106,6 +106,8 @@ rpl::producer<not_null<PeerData*>> MigratedOrMeValue(
not_null<PeerData*> peer); not_null<PeerData*> peer);
[[nodiscard]] rpl::producer<int> FullReactionsCountValue( [[nodiscard]] rpl::producer<int> FullReactionsCountValue(
not_null<Main::Session*> peer); not_null<Main::Session*> peer);
[[nodiscard]] rpl::producer<bool> CanViewParticipantsValue(
not_null<ChannelData*> megagroup);
enum class BadgeType; enum class BadgeType;
[[nodiscard]] rpl::producer<BadgeType> BadgeValue(not_null<PeerData*> peer); [[nodiscard]] rpl::producer<BadgeType> BadgeValue(not_null<PeerData*> peer);