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_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

View File

@ -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";

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/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());
}
}

View File

@ -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);

View File

@ -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<ChannelDataFlag>;

View File

@ -929,9 +929,12 @@ void ActionsFiller::addBlockAction(not_null<UserData*> user) {
void ActionsFiller::addLeaveChannelAction(not_null<ChannelData*> 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<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->add(CreateSkipWidget(_wrap));
callback();
@ -1010,8 +1022,11 @@ object_ptr<Ui::RpWidget> 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);

View File

@ -359,12 +359,7 @@ void Cover::setupChildGeometry() {
}
Cover *Cover::setOnlineCount(rpl::producer<int> &&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<QString> 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)

View File

@ -131,7 +131,7 @@ private:
const not_null<PeerData*> _peer;
const std::unique_ptr<EmojiStatusPanel> _emojiStatusPanel;
const std::unique_ptr<Badge> _badge;
int _onlineCount = 0;
rpl::variable<int> _onlineCount;
object_ptr<Ui::UserpicButton> _userpic;
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 <rpl/combine.h>
#include <rpl/combine_previous.h>
#include <rpl/flatten_latest.h>
#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<Ui::RpWidget> InnerWidget::setupContent(
result->add(std::move(actions));
}
if (_peer->isChat() || _peer->isMegagroup()) {
_members = result->add(object_ptr<Members>(
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<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(
not_null<RpWidget*> parent) {
using namespace rpl::mappers;

View File

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

View File

@ -549,6 +549,20 @@ rpl::producer<int> FullReactionsCountValue(
}) | 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>
rpl::producer<BadgeType> BadgeValueFromFlags(Peer peer) {
return rpl::combine(

View File

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