1435 lines
42 KiB
C++
1435 lines
42 KiB
C++
/*
|
|
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_peer_permissions_box.h"
|
|
|
|
#include "lang/lang_keys.h"
|
|
#include "core/ui_integration.h"
|
|
#include "data/stickers/data_custom_emoji.h"
|
|
#include "data/data_channel.h"
|
|
#include "data/data_chat.h"
|
|
#include "data/data_session.h"
|
|
#include "ui/effects/toggle_arrow.h"
|
|
#include "ui/wrap/slide_wrap.h"
|
|
#include "ui/wrap/vertical_layout.h"
|
|
#include "ui/layers/generic_box.h"
|
|
#include "ui/painter.h"
|
|
#include "ui/widgets/labels.h"
|
|
#include "ui/widgets/checkbox.h"
|
|
#include "ui/widgets/buttons.h"
|
|
#include "ui/widgets/continuous_sliders.h"
|
|
#include "ui/widgets/box_content_divider.h"
|
|
#include "ui/text/text_utilities.h"
|
|
#include "ui/toast/toast.h"
|
|
#include "info/profile/info_profile_icon.h"
|
|
#include "info/profile/info_profile_values.h"
|
|
#include "boxes/peers/edit_participants_box.h"
|
|
#include "boxes/peers/edit_peer_info_box.h"
|
|
#include "settings/settings_power_saving.h"
|
|
#include "window/window_session_controller.h"
|
|
#include "window/window_controller.h"
|
|
#include "main/main_session.h"
|
|
#include "apiwrap.h"
|
|
#include "settings/settings_common.h"
|
|
#include "styles/style_layers.h"
|
|
#include "styles/style_boxes.h"
|
|
#include "styles/style_chat.h"
|
|
#include "styles/style_info.h"
|
|
#include "styles/style_menu_icons.h"
|
|
#include "styles/style_window.h"
|
|
#include "styles/style_settings.h"
|
|
|
|
namespace {
|
|
|
|
constexpr auto kSlowmodeValues = 7;
|
|
constexpr auto kBoostsUnrestrictValues = 5;
|
|
constexpr auto kSuggestGigagroupThreshold = 199000;
|
|
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
|
|
|
|
[[nodiscard]] auto Dependencies(PowerSaving::Flags)
|
|
-> std::vector<std::pair<PowerSaving::Flag, PowerSaving::Flag>> {
|
|
return {};
|
|
}
|
|
|
|
[[nodiscard]] auto NestedRestrictionLabelsList(
|
|
Data::RestrictionsSetOptions options)
|
|
-> std::vector<NestedEditFlagsLabels<ChatRestrictions>> {
|
|
using Flag = ChatRestriction;
|
|
|
|
auto first = std::vector<RestrictionLabel>{
|
|
{ Flag::SendOther, tr::lng_rights_chat_send_text(tr::now) },
|
|
};
|
|
auto media = std::vector<RestrictionLabel>{
|
|
{ Flag::SendPhotos, tr::lng_rights_chat_photos(tr::now) },
|
|
{ Flag::SendVideos, tr::lng_rights_chat_videos(tr::now) },
|
|
{ Flag::SendVideoMessages, tr::lng_rights_chat_video_messages(tr::now) },
|
|
{ Flag::SendMusic, tr::lng_rights_chat_music(tr::now) },
|
|
{ Flag::SendVoiceMessages, tr::lng_rights_chat_voice_messages(tr::now) },
|
|
{ Flag::SendFiles, tr::lng_rights_chat_files(tr::now) },
|
|
{ Flag::SendStickers
|
|
| Flag::SendGifs
|
|
| Flag::SendGames
|
|
| Flag::SendInline, tr::lng_rights_chat_stickers(tr::now) },
|
|
{ Flag::EmbedLinks, tr::lng_rights_chat_send_links(tr::now) },
|
|
{ Flag::SendPolls, tr::lng_rights_chat_send_polls(tr::now) },
|
|
};
|
|
auto second = std::vector<RestrictionLabel>{
|
|
{ Flag::AddParticipants, tr::lng_rights_chat_add_members(tr::now) },
|
|
{ Flag::CreateTopics, tr::lng_rights_group_add_topics(tr::now) },
|
|
{ Flag::PinMessages, tr::lng_rights_group_pin(tr::now) },
|
|
{ Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) },
|
|
};
|
|
if (!options.isForum) {
|
|
second.erase(
|
|
ranges::remove(
|
|
second,
|
|
Flag::CreateTopics | Flag(),
|
|
&RestrictionLabel::flags),
|
|
end(second));
|
|
}
|
|
return {
|
|
{ std::nullopt, std::move(first) },
|
|
{ tr::lng_rights_chat_send_media(), std::move(media) },
|
|
{ std::nullopt, std::move(second) },
|
|
};
|
|
}
|
|
|
|
[[nodiscard]] auto NestedAdminRightLabels(
|
|
Data::AdminRightsSetOptions options)
|
|
-> std::vector<NestedEditFlagsLabels<ChatAdminRights>> {
|
|
using Flag = ChatAdminRight;
|
|
|
|
if (options.isGroup) {
|
|
auto first = std::vector<AdminRightLabel>{
|
|
{ Flag::ChangeInfo, tr::lng_rights_group_info(tr::now) },
|
|
{ Flag::DeleteMessages, tr::lng_rights_group_delete(tr::now) },
|
|
{ Flag::BanUsers, tr::lng_rights_group_ban(tr::now) },
|
|
{ Flag::InviteByLinkOrAdd, options.anyoneCanAddMembers
|
|
? tr::lng_rights_group_invite_link(tr::now)
|
|
: tr::lng_rights_group_invite(tr::now) },
|
|
{ Flag::ManageTopics, tr::lng_rights_group_topics(tr::now) },
|
|
{ Flag::PinMessages, tr::lng_rights_group_pin(tr::now) },
|
|
};
|
|
auto stories = std::vector<AdminRightLabel>{
|
|
{ Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) },
|
|
{ Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) },
|
|
{ Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) },
|
|
};
|
|
auto second = std::vector<AdminRightLabel>{
|
|
{ Flag::ManageCall, tr::lng_rights_group_manage_calls(tr::now) },
|
|
{ Flag::Anonymous, tr::lng_rights_group_anonymous(tr::now) },
|
|
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
|
|
};
|
|
if (!options.isForum) {
|
|
first.erase(
|
|
ranges::remove(
|
|
first,
|
|
Flag::ManageTopics | Flag(),
|
|
&AdminRightLabel::flags),
|
|
end(first));
|
|
}
|
|
return {
|
|
{ std::nullopt, std::move(first) },
|
|
{ tr::lng_rights_channel_manage_stories(), std::move(stories) },
|
|
{ std::nullopt, std::move(second) },
|
|
};
|
|
}
|
|
auto first = std::vector<AdminRightLabel>{
|
|
{ Flag::ChangeInfo, tr::lng_rights_channel_info(tr::now) },
|
|
};
|
|
auto messages = std::vector<AdminRightLabel>{
|
|
{ Flag::PostMessages, tr::lng_rights_channel_post(tr::now) },
|
|
{ Flag::EditMessages, tr::lng_rights_channel_edit(tr::now) },
|
|
{ Flag::DeleteMessages, tr::lng_rights_channel_delete(tr::now) },
|
|
};
|
|
auto stories = std::vector<AdminRightLabel>{
|
|
{ Flag::PostStories, tr::lng_rights_channel_post_stories(tr::now) },
|
|
{ Flag::EditStories, tr::lng_rights_channel_edit_stories(tr::now) },
|
|
{ Flag::DeleteStories, tr::lng_rights_channel_delete_stories(tr::now) },
|
|
};
|
|
auto second = std::vector<AdminRightLabel>{
|
|
{ Flag::InviteByLinkOrAdd, tr::lng_rights_group_invite(tr::now) },
|
|
{ Flag::ManageCall, tr::lng_rights_channel_manage_calls(tr::now) },
|
|
{ Flag::AddAdmins, tr::lng_rights_add_admins(tr::now) },
|
|
};
|
|
return {
|
|
{ std::nullopt, std::move(first) },
|
|
{ tr::lng_rights_channel_manage(), std::move(messages) },
|
|
{ tr::lng_rights_channel_manage_stories(), std::move(stories) },
|
|
{ std::nullopt, std::move(second) },
|
|
};
|
|
}
|
|
|
|
int SlowmodeDelayByIndex(int index) {
|
|
Expects(index >= 0 && index < kSlowmodeValues);
|
|
|
|
switch (index) {
|
|
case 0: return 0;
|
|
case 1: return 10;
|
|
case 2: return 30;
|
|
case 3: return 60;
|
|
case 4: return 5 * 60;
|
|
case 5: return 15 * 60;
|
|
case 6: return 60 * 60;
|
|
}
|
|
Unexpected("Index in SlowmodeDelayByIndex.");
|
|
}
|
|
|
|
[[nodiscard]] int BoostsUnrestrictByIndex(int index) {
|
|
return index + 1;
|
|
}
|
|
|
|
template <typename CheckboxesMap, typename DependenciesMap>
|
|
void ApplyDependencies(
|
|
const CheckboxesMap &checkboxes,
|
|
const DependenciesMap &dependencies,
|
|
Ui::AbstractCheckView *changed) {
|
|
const auto checkAndApply = [&](
|
|
auto &¤t,
|
|
auto dependency,
|
|
bool isChecked) {
|
|
for (auto &&checkbox : checkboxes) {
|
|
if ((checkbox.first & dependency)
|
|
&& (checkbox.second->checked() == isChecked)) {
|
|
current->setChecked(isChecked, anim::type::normal);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
const auto applySomeDependency = [&] {
|
|
auto result = false;
|
|
for (auto &&entry : checkboxes) {
|
|
if (entry.second == changed) {
|
|
continue;
|
|
}
|
|
auto isChecked = entry.second->checked();
|
|
for (auto &&dependency : dependencies) {
|
|
const auto check = isChecked
|
|
? dependency.first
|
|
: dependency.second;
|
|
if (entry.first & check) {
|
|
if (checkAndApply(
|
|
entry.second,
|
|
(isChecked
|
|
? dependency.second
|
|
: dependency.first),
|
|
!isChecked)) {
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
|
|
const auto maxFixesCount = int(checkboxes.size());
|
|
for (auto i = 0; i != maxFixesCount; ++i) {
|
|
if (!applySomeDependency()) {
|
|
break;
|
|
}
|
|
};
|
|
}
|
|
|
|
auto Dependencies(ChatRestrictions)
|
|
-> std::vector<std::pair<ChatRestriction, ChatRestriction>> {
|
|
using Flag = ChatRestriction;
|
|
|
|
return {
|
|
// stickers <-> gifs
|
|
{ Flag::SendGifs, Flag::SendStickers },
|
|
{ Flag::SendStickers, Flag::SendGifs },
|
|
|
|
// stickers <-> games
|
|
{ Flag::SendGames, Flag::SendStickers },
|
|
{ Flag::SendStickers, Flag::SendGames },
|
|
|
|
// stickers <-> inline
|
|
{ Flag::SendInline, Flag::SendStickers },
|
|
{ Flag::SendStickers, Flag::SendInline },
|
|
|
|
// embed_links -> send_plain
|
|
{ Flag::EmbedLinks, Flag::SendOther },
|
|
|
|
// send_* -> view_messages
|
|
{ Flag::SendStickers, Flag::ViewMessages },
|
|
{ Flag::SendGifs, Flag::ViewMessages },
|
|
{ Flag::SendGames, Flag::ViewMessages },
|
|
{ Flag::SendInline, Flag::ViewMessages },
|
|
{ Flag::SendPolls, Flag::ViewMessages },
|
|
{ Flag::SendPhotos, Flag::ViewMessages },
|
|
{ Flag::SendVideos, Flag::ViewMessages },
|
|
{ Flag::SendVideoMessages, Flag::ViewMessages },
|
|
{ Flag::SendMusic, Flag::ViewMessages },
|
|
{ Flag::SendVoiceMessages, Flag::ViewMessages },
|
|
{ Flag::SendFiles, Flag::ViewMessages },
|
|
{ Flag::SendOther, Flag::ViewMessages },
|
|
};
|
|
}
|
|
|
|
ChatRestrictions NegateRestrictions(ChatRestrictions value) {
|
|
using Flag = ChatRestriction;
|
|
|
|
return (~value) & (Flag(0)
|
|
// view_messages is always allowed, so it is never in restrictions.
|
|
//| Flag::ViewMessages
|
|
| Flag::ChangeInfo
|
|
| Flag::EmbedLinks
|
|
| Flag::AddParticipants
|
|
| Flag::CreateTopics
|
|
| Flag::PinMessages
|
|
| Flag::SendGames
|
|
| Flag::SendGifs
|
|
| Flag::SendInline
|
|
| Flag::SendPolls
|
|
| Flag::SendStickers
|
|
| Flag::SendPhotos
|
|
| Flag::SendVideos
|
|
| Flag::SendVideoMessages
|
|
| Flag::SendMusic
|
|
| Flag::SendVoiceMessages
|
|
| Flag::SendFiles
|
|
| Flag::SendOther);
|
|
}
|
|
|
|
auto Dependencies(ChatAdminRights)
|
|
-> std::vector<std::pair<ChatAdminRight, ChatAdminRight>> {
|
|
return {};
|
|
}
|
|
|
|
auto ToPositiveNumberString() {
|
|
return rpl::map([](int count) {
|
|
return count ? QString::number(count) : QString();
|
|
});
|
|
}
|
|
|
|
ChatRestrictions DisabledByAdminRights(not_null<PeerData*> peer) {
|
|
using Flag = ChatRestriction;
|
|
using Admin = ChatAdminRight;
|
|
using Admins = ChatAdminRights;
|
|
|
|
const auto adminRights = [&] {
|
|
const auto full = ~Admins(0);
|
|
if (const auto chat = peer->asChat()) {
|
|
return chat->amCreator() ? full : chat->adminRights();
|
|
} else if (const auto channel = peer->asChannel()) {
|
|
return channel->amCreator() ? full : channel->adminRights();
|
|
}
|
|
Unexpected("User in DisabledByAdminRights.");
|
|
}();
|
|
return Flag(0)
|
|
| ((adminRights & Admin::ManageTopics)
|
|
? Flag(0)
|
|
: Flag::CreateTopics)
|
|
| ((adminRights & Admin::PinMessages)
|
|
? Flag(0)
|
|
: Flag::PinMessages)
|
|
| ((adminRights & Admin::InviteByLinkOrAdd)
|
|
? Flag(0)
|
|
: Flag::AddParticipants)
|
|
| ((adminRights & Admin::ChangeInfo)
|
|
? Flag(0)
|
|
: Flag::ChangeInfo);
|
|
}
|
|
|
|
not_null<Ui::RpWidget*> AddInnerToggle(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
const style::SettingsButton &st,
|
|
std::vector<not_null<Ui::AbstractCheckView*>> innerCheckViews,
|
|
not_null<Ui::SlideWrap<>*> wrap,
|
|
rpl::producer<QString> buttonLabel,
|
|
std::optional<QString> locked,
|
|
Settings::IconDescriptor &&icon) {
|
|
const auto button = container->add(object_ptr<Ui::SettingsButton>(
|
|
container,
|
|
nullptr,
|
|
st));
|
|
if (icon) {
|
|
Settings::AddButtonIcon(button, st, std::move(icon));
|
|
}
|
|
|
|
const auto toggleButton = Ui::CreateChild<Ui::SettingsButton>(
|
|
container.get(),
|
|
nullptr,
|
|
st);
|
|
|
|
struct State final {
|
|
State(const style::Toggle &st, Fn<void()> c)
|
|
: checkView(st, false, c) {
|
|
}
|
|
Ui::ToggleView checkView;
|
|
Ui::Animations::Simple animation;
|
|
rpl::event_stream<> anyChanges;
|
|
std::vector<not_null<Ui::AbstractCheckView*>> innerChecks;
|
|
};
|
|
const auto state = button->lifetime().make_state<State>(
|
|
st.toggle,
|
|
[=] { toggleButton->update(); });
|
|
state->innerChecks = std::move(innerCheckViews);
|
|
const auto countChecked = [=] {
|
|
return ranges::count_if(
|
|
state->innerChecks,
|
|
[](const auto &v) { return v->checked(); });
|
|
};
|
|
for (const auto &innerCheck : state->innerChecks) {
|
|
innerCheck->checkedChanges(
|
|
) | rpl::to_empty | rpl::start_to_stream(
|
|
state->anyChanges,
|
|
button->lifetime());
|
|
}
|
|
const auto checkView = &state->checkView;
|
|
{
|
|
const auto separator = Ui::CreateChild<Ui::RpWidget>(container.get());
|
|
separator->paintRequest(
|
|
) | rpl::start_with_next([=, bg = st.textBgOver] {
|
|
auto p = QPainter(separator);
|
|
p.fillRect(separator->rect(), bg);
|
|
}, separator->lifetime());
|
|
const auto separatorHeight = 2 * st.toggle.border
|
|
+ st.toggle.diameter;
|
|
button->geometryValue(
|
|
) | rpl::start_with_next([=](const QRect &r) {
|
|
const auto w = st::rightsButtonToggleWidth;
|
|
toggleButton->setGeometry(
|
|
r.x() + r.width() - w,
|
|
r.y(),
|
|
w,
|
|
r.height());
|
|
separator->setGeometry(
|
|
toggleButton->x() - st::lineWidth,
|
|
r.y() + (r.height() - separatorHeight) / 2,
|
|
st::lineWidth,
|
|
separatorHeight);
|
|
}, toggleButton->lifetime());
|
|
|
|
const auto checkWidget = Ui::CreateChild<Ui::RpWidget>(toggleButton);
|
|
checkWidget->resize(checkView->getSize());
|
|
checkWidget->paintRequest(
|
|
) | rpl::start_with_next([=] {
|
|
auto p = QPainter(checkWidget);
|
|
checkView->paint(p, 0, 0, checkWidget->width());
|
|
}, checkWidget->lifetime());
|
|
toggleButton->sizeValue(
|
|
) | rpl::start_with_next([=](const QSize &s) {
|
|
checkWidget->moveToRight(
|
|
st.toggleSkip,
|
|
(s.height() - checkWidget->height()) / 2);
|
|
}, toggleButton->lifetime());
|
|
}
|
|
state->anyChanges.events_starting_with(
|
|
rpl::empty_value()
|
|
) | rpl::map(countChecked) | rpl::start_with_next([=](int count) {
|
|
checkView->setChecked(count > 0, anim::type::normal);
|
|
}, toggleButton->lifetime());
|
|
checkView->setLocked(locked.has_value());
|
|
checkView->finishAnimating();
|
|
|
|
const auto totalInnerChecks = state->innerChecks.size();
|
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
|
button,
|
|
rpl::combine(
|
|
std::move(buttonLabel),
|
|
state->anyChanges.events_starting_with(
|
|
rpl::empty_value()
|
|
) | rpl::map(countChecked)
|
|
) | rpl::map([=](const QString &t, int checked) {
|
|
auto count = Ui::Text::Bold(" "
|
|
+ QString::number(checked)
|
|
+ '/'
|
|
+ QString::number(totalInnerChecks));
|
|
return TextWithEntities::Simple(t).append(std::move(count));
|
|
}));
|
|
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
const auto arrow = Ui::CreateChild<Ui::RpWidget>(button);
|
|
{
|
|
const auto &icon = st::permissionsExpandIcon;
|
|
arrow->resize(icon.size());
|
|
arrow->paintRequest(
|
|
) | rpl::start_with_next([=, &icon] {
|
|
auto p = QPainter(arrow);
|
|
const auto center = QPointF(
|
|
icon.width() / 2.,
|
|
icon.height() / 2.);
|
|
const auto progress = state->animation.value(
|
|
wrap->toggled() ? 1. : 0.);
|
|
auto hq = std::optional<PainterHighQualityEnabler>();
|
|
if (progress > 0.) {
|
|
hq.emplace(p);
|
|
p.translate(center);
|
|
p.rotate(progress * 180.);
|
|
p.translate(-center);
|
|
}
|
|
icon.paint(p, 0, 0, arrow->width());
|
|
}, arrow->lifetime());
|
|
}
|
|
button->sizeValue(
|
|
) | rpl::start_with_next([=, &st](const QSize &s) {
|
|
const auto labelLeft = st.padding.left();
|
|
const auto labelRight = s.width() - toggleButton->width();
|
|
|
|
label->resizeToWidth(labelRight - labelLeft - arrow->width());
|
|
label->moveToLeft(
|
|
labelLeft,
|
|
(s.height() - label->height()) / 2);
|
|
arrow->moveToLeft(
|
|
std::min(
|
|
labelLeft + label->textMaxWidth(),
|
|
labelRight - arrow->width()),
|
|
(s.height() - arrow->height()) / 2);
|
|
}, button->lifetime());
|
|
wrap->toggledValue(
|
|
) | rpl::skip(1) | rpl::start_with_next([=](bool toggled) {
|
|
state->animation.start(
|
|
[=] { arrow->update(); },
|
|
toggled ? 0. : 1.,
|
|
toggled ? 1. : 0.,
|
|
st::slideWrapDuration);
|
|
}, button->lifetime());
|
|
|
|
const auto handleLocked = [=] {
|
|
if (locked.has_value()) {
|
|
Ui::Toast::Show(container, *locked);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
button->clicks(
|
|
) | rpl::start_with_next([=] {
|
|
if (!handleLocked()) {
|
|
wrap->toggle(!wrap->toggled(), anim::type::normal);
|
|
}
|
|
}, button->lifetime());
|
|
|
|
toggleButton->clicks(
|
|
) | rpl::start_with_next([=] {
|
|
if (!handleLocked()) {
|
|
const auto checked = !checkView->checked();
|
|
for (const auto &innerCheck : state->innerChecks) {
|
|
innerCheck->setChecked(checked, anim::type::normal);
|
|
}
|
|
}
|
|
}, toggleButton->lifetime());
|
|
|
|
return button;
|
|
}
|
|
|
|
template <typename Flags>
|
|
[[nodiscard]] EditFlagsControl<Flags> CreateEditFlags(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
Flags checked,
|
|
EditFlagsDescriptor<Flags> &&descriptor) {
|
|
struct State final {
|
|
std::map<Flags, not_null<Ui::AbstractCheckView*>> checkViews;
|
|
rpl::event_stream<> anyChanges;
|
|
rpl::variable<QString> forceDisabledMessage;
|
|
rpl::variable<bool> forceDisabled;
|
|
base::flat_map<Flags, bool> realCheckedValues;
|
|
base::weak_ptr<Ui::Toast::Instance> toast;
|
|
};
|
|
const auto state = container->lifetime().make_state<State>();
|
|
if (descriptor.forceDisabledMessage) {
|
|
state->forceDisabledMessage = std::move(
|
|
descriptor.forceDisabledMessage);
|
|
state->forceDisabled = state->forceDisabledMessage.value(
|
|
) | rpl::map([=](const QString &message) {
|
|
return !message.isEmpty();
|
|
});
|
|
|
|
state->forceDisabled.value(
|
|
) | rpl::start_with_next([=](bool disabled) {
|
|
if (disabled) {
|
|
for (const auto &[flags, checkView] : state->checkViews) {
|
|
checkView->setChecked(false, anim::type::normal);
|
|
}
|
|
} else {
|
|
for (const auto &[flags, checkView] : state->checkViews) {
|
|
if (const auto i = state->realCheckedValues.find(flags)
|
|
; i != state->realCheckedValues.end()) {
|
|
checkView->setChecked(
|
|
i->second,
|
|
anim::type::normal);
|
|
}
|
|
}
|
|
}
|
|
}, container->lifetime());
|
|
}
|
|
|
|
const auto &st = descriptor.st ? *descriptor.st : st::rightsButton;
|
|
const auto value = [=] {
|
|
auto result = Flags(0);
|
|
for (const auto &[flags, checkView] : state->checkViews) {
|
|
if (checkView->checked()) {
|
|
result |= flags;
|
|
} else {
|
|
result &= ~flags;
|
|
}
|
|
}
|
|
return result;
|
|
};
|
|
const auto applyDependencies = [=](Ui::AbstractCheckView *view) {
|
|
static const auto dependencies = Dependencies(Flags());
|
|
ApplyDependencies(state->checkViews, dependencies, view);
|
|
};
|
|
|
|
if (descriptor.header) {
|
|
container->add(
|
|
object_ptr<Ui::FlatLabel>(
|
|
container,
|
|
std::move(descriptor.header),
|
|
st::rightsHeaderLabel),
|
|
st::rightsHeaderMargin);
|
|
}
|
|
const auto addCheckbox = [&](
|
|
not_null<Ui::VerticalLayout*> verticalLayout,
|
|
bool isInner,
|
|
const EditFlagsLabel<Flags> &entry) {
|
|
const auto flags = entry.flags;
|
|
const auto lockedIt = ranges::find_if(
|
|
descriptor.disabledMessages,
|
|
[&](const auto &pair) { return (pair.first & flags) != 0; });
|
|
const auto locked = (lockedIt != end(descriptor.disabledMessages))
|
|
? std::make_optional(lockedIt->second)
|
|
: std::nullopt;
|
|
const auto realChecked = (checked & flags) != 0;
|
|
state->realCheckedValues.emplace(flags, realChecked);
|
|
const auto toggled = realChecked && !state->forceDisabled.current();
|
|
|
|
const auto checkView = [&]() -> not_null<Ui::AbstractCheckView*> {
|
|
if (isInner) {
|
|
const auto checkbox = verticalLayout->add(
|
|
object_ptr<Ui::Checkbox>(
|
|
verticalLayout,
|
|
entry.label,
|
|
toggled,
|
|
st::settingsCheckbox),
|
|
st.padding);
|
|
const auto button = Ui::CreateChild<Ui::RippleButton>(
|
|
verticalLayout.get(),
|
|
st::defaultRippleAnimation);
|
|
button->stackUnder(checkbox);
|
|
rpl::combine(
|
|
verticalLayout->widthValue(),
|
|
checkbox->geometryValue()
|
|
) | rpl::start_with_next([=](int w, const QRect &r) {
|
|
button->setGeometry(0, r.y(), w, r.height());
|
|
}, button->lifetime());
|
|
checkbox->setAttribute(Qt::WA_TransparentForMouseEvents);
|
|
const auto checkView = checkbox->checkView();
|
|
button->setClickedCallback([=] {
|
|
checkView->setChecked(
|
|
!checkView->checked(),
|
|
anim::type::normal);
|
|
});
|
|
|
|
return checkView;
|
|
} else {
|
|
const auto button = Settings::AddButtonWithIcon(
|
|
verticalLayout,
|
|
rpl::single(entry.label),
|
|
st,
|
|
{ entry.icon });
|
|
const auto toggle = Ui::CreateChild<Ui::RpWidget>(
|
|
button.get());
|
|
|
|
// Looks like a bug in Clang, fails to compile with 'auto&' below.
|
|
rpl::lifetime &lifetime = toggle->lifetime();
|
|
|
|
const auto checkView = lifetime.make_state<Ui::ToggleView>(
|
|
st.toggle,
|
|
toggled,
|
|
[=] { toggle->update(); });
|
|
toggle->resize(checkView->getSize());
|
|
toggle->paintRequest(
|
|
) | rpl::start_with_next([=] {
|
|
auto p = QPainter(toggle);
|
|
checkView->paint(p, 0, 0, toggle->width());
|
|
}, toggle->lifetime());
|
|
button->sizeValue(
|
|
) | rpl::start_with_next([=](const QSize &s) {
|
|
toggle->moveToRight(
|
|
st.toggleSkip,
|
|
(s.height() - toggle->height()) / 2);
|
|
}, toggle->lifetime());
|
|
button->setClickedCallback([=] {
|
|
checkView->setChecked(
|
|
!checkView->checked(),
|
|
anim::type::normal);
|
|
});
|
|
checkView->setLocked(locked.has_value());
|
|
return checkView;
|
|
}
|
|
}();
|
|
state->checkViews.emplace(flags, checkView);
|
|
checkView->checkedChanges(
|
|
) | rpl::start_with_next([=](bool checked) {
|
|
if (checked && state->forceDisabled.current()) {
|
|
if (!state->toast) {
|
|
state->toast = Ui::Toast::Show(container, {
|
|
.text = { state->forceDisabledMessage.current() },
|
|
.duration = kForceDisableTooltipDuration,
|
|
});
|
|
}
|
|
checkView->setChecked(false, anim::type::instant);
|
|
} else if (locked.has_value()) {
|
|
if (checked != toggled) {
|
|
if (!state->toast) {
|
|
state->toast = Ui::Toast::Show(container, {
|
|
.text = { *locked },
|
|
.duration = kForceDisableTooltipDuration,
|
|
});
|
|
}
|
|
checkView->setChecked(toggled, anim::type::instant);
|
|
}
|
|
} else {
|
|
if (!state->forceDisabled.current()) {
|
|
state->realCheckedValues[flags] = checked;
|
|
}
|
|
InvokeQueued(container, [=] {
|
|
applyDependencies(checkView);
|
|
state->anyChanges.fire({});
|
|
});
|
|
}
|
|
}, verticalLayout->lifetime());
|
|
|
|
return checkView;
|
|
};
|
|
for (const auto &nestedWithLabel : descriptor.labels) {
|
|
Assert(!nestedWithLabel.nested.empty());
|
|
|
|
const auto isInner = nestedWithLabel.nestingLabel.has_value();
|
|
auto wrap = isInner
|
|
? object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
container,
|
|
object_ptr<Ui::VerticalLayout>(container))
|
|
: object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>{ nullptr };
|
|
const auto verticalLayout = wrap ? wrap->entity() : container.get();
|
|
auto innerChecks = std::vector<not_null<Ui::AbstractCheckView*>>();
|
|
for (const auto &entry : nestedWithLabel.nested) {
|
|
const auto c = addCheckbox(verticalLayout, isInner, entry);
|
|
if (isInner) {
|
|
innerChecks.push_back(c);
|
|
}
|
|
}
|
|
if (wrap) {
|
|
const auto raw = wrap.data();
|
|
raw->hide(anim::type::instant);
|
|
AddInnerToggle(
|
|
container,
|
|
st,
|
|
innerChecks,
|
|
raw,
|
|
*nestedWithLabel.nestingLabel,
|
|
std::nullopt,
|
|
{ nestedWithLabel.nested.front().icon });
|
|
container->add(std::move(wrap));
|
|
container->widthValue(
|
|
) | rpl::start_with_next([=](int w) {
|
|
raw->resizeToWidth(w);
|
|
}, raw->lifetime());
|
|
}
|
|
}
|
|
|
|
applyDependencies(nullptr);
|
|
for (const auto &[flags, checkView] : state->checkViews) {
|
|
checkView->finishAnimating();
|
|
}
|
|
|
|
return {
|
|
nullptr,
|
|
value,
|
|
state->anyChanges.events() | rpl::map(value)
|
|
};
|
|
}
|
|
|
|
void AddSlowmodeLabels(
|
|
not_null<Ui::VerticalLayout*> container) {
|
|
const auto labels = container->add(
|
|
object_ptr<Ui::FixedHeightWidget>(container, st::normalFont->height),
|
|
st::slowmodeLabelsMargin);
|
|
for (auto i = 0; i != kSlowmodeValues; ++i) {
|
|
const auto seconds = SlowmodeDelayByIndex(i);
|
|
const auto label = Ui::CreateChild<Ui::LabelSimple>(
|
|
labels,
|
|
st::slowmodeLabel,
|
|
(!seconds
|
|
? tr::lng_rights_slowmode_off(tr::now)
|
|
: (seconds < 60)
|
|
? tr::lng_seconds_tiny(tr::now, lt_count, seconds)
|
|
: (seconds < 3600)
|
|
? tr::lng_minutes_tiny(tr::now, lt_count, seconds / 60)
|
|
: tr::lng_hours_tiny(tr::now, lt_count,seconds / 3600)));
|
|
rpl::combine(
|
|
labels->widthValue(),
|
|
label->widthValue()
|
|
) | rpl::start_with_next([=](int outer, int inner) {
|
|
const auto skip = st::localStorageLimitMargin;
|
|
const auto size = st::localStorageLimitSlider.seekSize;
|
|
const auto available = outer
|
|
- skip.left()
|
|
- skip.right()
|
|
- size.width();
|
|
const auto shift = (i == 0)
|
|
? -(size.width() / 2)
|
|
: (i + 1 == kSlowmodeValues)
|
|
? (size.width() - (size.width() / 2) - inner)
|
|
: (-inner / 2);
|
|
const auto left = skip.left()
|
|
+ (size.width() / 2)
|
|
+ (i * available) / (kSlowmodeValues - 1)
|
|
+ shift;
|
|
label->moveToLeft(left, 0, outer);
|
|
}, label->lifetime());
|
|
}
|
|
}
|
|
|
|
rpl::producer<int> AddSlowmodeSlider(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<PeerData*> peer) {
|
|
using namespace rpl::mappers;
|
|
|
|
if (const auto chat = peer->asChat()) {
|
|
if (!chat->amCreator()) {
|
|
return rpl::single(0);
|
|
}
|
|
}
|
|
const auto channel = peer->asChannel();
|
|
auto &lifetime = container->lifetime();
|
|
const auto secondsCount = lifetime.make_state<rpl::variable<int>>(
|
|
channel ? channel->slowmodeSeconds() : 0);
|
|
|
|
container->add(
|
|
object_ptr<Ui::FlatLabel>(
|
|
container,
|
|
tr::lng_rights_slowmode_header(),
|
|
st::rightsHeaderLabel),
|
|
st::rightsHeaderMargin);
|
|
|
|
AddSlowmodeLabels(container);
|
|
|
|
const auto slider = container->add(
|
|
object_ptr<Ui::MediaSlider>(container, st::localStorageLimitSlider),
|
|
st::localStorageLimitMargin);
|
|
slider->resize(st::localStorageLimitSlider.seekSize);
|
|
slider->setPseudoDiscrete(
|
|
kSlowmodeValues,
|
|
SlowmodeDelayByIndex,
|
|
secondsCount->current(),
|
|
[=](int seconds) {
|
|
(*secondsCount) = seconds;
|
|
});
|
|
|
|
auto hasSlowMode = secondsCount->value(
|
|
) | rpl::map(
|
|
_1 != 0
|
|
) | rpl::distinct_until_changed();
|
|
|
|
auto useSeconds = secondsCount->value(
|
|
) | rpl::map(
|
|
_1 < 60
|
|
) | rpl::distinct_until_changed();
|
|
|
|
auto interval = rpl::combine(
|
|
std::move(useSeconds),
|
|
tr::lng_rights_slowmode_interval_seconds(
|
|
lt_count,
|
|
secondsCount->value() | tr::to_count()),
|
|
tr::lng_rights_slowmode_interval_minutes(
|
|
lt_count,
|
|
secondsCount->value() | rpl::map(_1 / 60.))
|
|
) | rpl::map([](
|
|
bool use,
|
|
const QString &seconds,
|
|
const QString &minutes) {
|
|
return use ? seconds : minutes;
|
|
});
|
|
|
|
auto aboutText = rpl::combine(
|
|
std::move(hasSlowMode),
|
|
tr::lng_rights_slowmode_about(),
|
|
tr::lng_rights_slowmode_about_interval(
|
|
lt_interval,
|
|
std::move(interval))
|
|
) | rpl::map([](
|
|
bool has,
|
|
const QString &about,
|
|
const QString &aboutInterval) {
|
|
return has ? aboutInterval : about;
|
|
});
|
|
|
|
container->add(
|
|
object_ptr<Ui::DividerLabel>(
|
|
container,
|
|
object_ptr<Ui::FlatLabel>(
|
|
container,
|
|
std::move(aboutText),
|
|
st::boxDividerLabel),
|
|
st::proxyAboutPadding),
|
|
style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
|
|
|
|
return secondsCount->value();
|
|
}
|
|
|
|
void AddBoostsUnrestrictLabels(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<Main::Session*> session) {
|
|
const auto labels = container->add(
|
|
object_ptr<Ui::FixedHeightWidget>(container, st::normalFont->height),
|
|
st::slowmodeLabelsMargin);
|
|
const auto manager = &session->data().customEmojiManager();
|
|
const auto one = Ui::Text::SingleCustomEmoji(
|
|
manager->registerInternalEmoji(
|
|
st::boostMessageIcon,
|
|
st::boostMessageIconPadding));
|
|
const auto many = Ui::Text::SingleCustomEmoji(
|
|
manager->registerInternalEmoji(
|
|
st::boostsMessageIcon,
|
|
st::boostsMessageIconPadding));
|
|
const auto context = Core::MarkedTextContext{
|
|
.session = session,
|
|
.customEmojiRepaint = [] {},
|
|
.customEmojiLoopLimit = 1,
|
|
};
|
|
for (auto i = 0; i != kBoostsUnrestrictValues; ++i) {
|
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
|
labels,
|
|
st::boostsUnrestrictLabel);
|
|
label->setMarkedText(
|
|
TextWithEntities(i ? many : one).append(QString::number(i + 1)),
|
|
context);
|
|
rpl::combine(
|
|
labels->widthValue(),
|
|
label->widthValue()
|
|
) | rpl::start_with_next([=](int outer, int inner) {
|
|
const auto skip = st::localStorageLimitMargin;
|
|
const auto size = st::localStorageLimitSlider.seekSize;
|
|
const auto available = outer
|
|
- skip.left()
|
|
- skip.right()
|
|
- size.width();
|
|
const auto shift = (i == 0)
|
|
? -(size.width() / 2)
|
|
: (i + 1 == kBoostsUnrestrictValues)
|
|
? (size.width() - (size.width() / 2) - inner)
|
|
: (-inner / 2);
|
|
const auto left = skip.left()
|
|
+ (size.width() / 2)
|
|
+ (i * available) / (kBoostsUnrestrictValues - 1)
|
|
+ shift;
|
|
label->moveToLeft(left, 0, outer);
|
|
}, label->lifetime());
|
|
}
|
|
}
|
|
|
|
rpl::producer<int> AddBoostsUnrestrictSlider(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<PeerData*> peer) {
|
|
using namespace rpl::mappers;
|
|
|
|
if (const auto chat = peer->asChat()) {
|
|
if (!chat->amCreator()) {
|
|
return rpl::single(0);
|
|
}
|
|
}
|
|
const auto channel = peer->asChannel();
|
|
auto &lifetime = container->lifetime();
|
|
const auto boostsUnrestrict = lifetime.make_state<rpl::variable<int>>(
|
|
channel ? channel->boostsUnrestrict() : 0);
|
|
|
|
container->add(
|
|
object_ptr<Ui::BoxContentDivider>(container),
|
|
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
|
|
|
|
auto enabled = boostsUnrestrict->value(
|
|
) | rpl::map(_1 > 0);
|
|
container->add(object_ptr<Ui::SettingsButton>(
|
|
container,
|
|
tr::lng_rights_boosts_no_restrict(),
|
|
st::defaultSettingsButton
|
|
))->toggleOn(rpl::duplicate(enabled))->toggledValue(
|
|
) | rpl::start_with_next([=](bool toggled) {
|
|
if (toggled && !boostsUnrestrict->current()) {
|
|
*boostsUnrestrict = 1;
|
|
} else if (!toggled && boostsUnrestrict->current()) {
|
|
*boostsUnrestrict = 0;
|
|
}
|
|
}, container->lifetime());
|
|
|
|
const auto outer = container->add(
|
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
container,
|
|
object_ptr<Ui::VerticalLayout>(container)));
|
|
outer->toggleOn(rpl::duplicate(enabled), anim::type::normal);
|
|
outer->finishAnimating();
|
|
|
|
const auto inner = outer->entity();
|
|
|
|
AddBoostsUnrestrictLabels(inner, &peer->session());
|
|
|
|
const auto slider = inner->add(
|
|
object_ptr<Ui::MediaSlider>(inner, st::localStorageLimitSlider),
|
|
st::localStorageLimitMargin);
|
|
slider->resize(st::localStorageLimitSlider.seekSize);
|
|
slider->setPseudoDiscrete(
|
|
kBoostsUnrestrictValues,
|
|
BoostsUnrestrictByIndex,
|
|
boostsUnrestrict->current(),
|
|
[=](int boosts) {
|
|
(*boostsUnrestrict) = boosts;
|
|
});
|
|
|
|
inner->add(
|
|
object_ptr<Ui::DividerLabel>(
|
|
inner,
|
|
object_ptr<Ui::FlatLabel>(
|
|
inner,
|
|
rpl::conditional(
|
|
boostsUnrestrict->value() | rpl::map(_1 > 0),
|
|
tr::lng_rights_boosts_about_on(),
|
|
tr::lng_rights_boosts_about()),
|
|
st::boxDividerLabel),
|
|
st::proxyAboutPadding),
|
|
style::margins(0, st::infoProfileSkip, 0, 0));
|
|
|
|
return boostsUnrestrict->value();
|
|
}
|
|
|
|
rpl::producer<int> AddBoostsUnrestrictWrapped(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<PeerData*> peer,
|
|
rpl::producer<bool> shown) {
|
|
const auto wrap = container->add(
|
|
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
|
|
container,
|
|
object_ptr<Ui::VerticalLayout>(container)));
|
|
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
|
|
wrap->finishAnimating();
|
|
|
|
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
|
|
const auto divider = container->add(
|
|
object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
|
|
container,
|
|
object_ptr<Ui::BoxContentDivider>(container),
|
|
QMargins{ 0, st::infoProfileSkip, 0, st::infoProfileSkip }));
|
|
divider->toggleOn(rpl::combine(
|
|
std::move(shown),
|
|
rpl::duplicate(result),
|
|
!rpl::mappers::_1 || !rpl::mappers::_2));
|
|
divider->finishAnimating();
|
|
|
|
return result;
|
|
}
|
|
|
|
void AddSuggestGigagroup(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
Fn<void()> callback) {
|
|
container->add(
|
|
object_ptr<Ui::FlatLabel>(
|
|
container,
|
|
tr::lng_rights_gigagroup_title(),
|
|
st::rightsHeaderLabel),
|
|
st::rightsHeaderMargin);
|
|
container->add(EditPeerInfoBox::CreateButton(
|
|
container,
|
|
tr::lng_rights_gigagroup_convert(),
|
|
rpl::single(QString()),
|
|
std::move(callback),
|
|
st::manageGroupTopicsButton,
|
|
{ &st::menuIconChatDiscuss }));
|
|
|
|
container->add(
|
|
object_ptr<Ui::DividerLabel>(
|
|
container,
|
|
object_ptr<Ui::FlatLabel>(
|
|
container,
|
|
tr::lng_rights_gigagroup_about(),
|
|
st::boxDividerLabel),
|
|
st::proxyAboutPadding),
|
|
style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
|
|
}
|
|
|
|
void AddBannedButtons(
|
|
not_null<Ui::VerticalLayout*> container,
|
|
not_null<Window::SessionNavigation*> navigation,
|
|
not_null<PeerData*> peer) {
|
|
if (const auto chat = peer->asChat()) {
|
|
if (!chat->amCreator()) {
|
|
return;
|
|
}
|
|
}
|
|
const auto channel = peer->asChannel();
|
|
container->add(EditPeerInfoBox::CreateButton(
|
|
container,
|
|
tr::lng_manage_peer_exceptions(),
|
|
(channel
|
|
? Info::Profile::RestrictedCountValue(channel)
|
|
: rpl::single(0)) | ToPositiveNumberString(),
|
|
[=] {
|
|
ParticipantsBoxController::Start(
|
|
navigation,
|
|
peer,
|
|
ParticipantsBoxController::Role::Restricted);
|
|
},
|
|
st::manageGroupTopicsButton,
|
|
{ &st::menuIconPermissions }));
|
|
if (channel) {
|
|
container->add(EditPeerInfoBox::CreateButton(
|
|
container,
|
|
tr::lng_manage_peer_removed_users(),
|
|
Info::Profile::KickedCountValue(channel)
|
|
| ToPositiveNumberString(),
|
|
[=] {
|
|
ParticipantsBoxController::Start(
|
|
navigation,
|
|
peer,
|
|
ParticipantsBoxController::Role::Kicked);
|
|
},
|
|
st::manageGroupTopicsButton,
|
|
{ &st::menuIconRemove }));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
void ShowEditPeerPermissionsBox(
|
|
not_null<Ui::GenericBox*> box,
|
|
not_null<Window::SessionNavigation*> navigation,
|
|
not_null<PeerData*> channelOrGroup,
|
|
Fn<void(EditPeerPermissionsBoxResult)> done) {
|
|
const auto peer = channelOrGroup->migrateToOrMe();
|
|
|
|
box->setTitle(tr::lng_manage_peer_permissions());
|
|
|
|
const auto inner = box->verticalLayout();
|
|
|
|
using Flag = ChatRestriction;
|
|
using Flags = ChatRestrictions;
|
|
|
|
const auto disabledByAdminRights = DisabledByAdminRights(peer);
|
|
const auto restrictions = FixDependentRestrictions([&] {
|
|
if (const auto chat = peer->asChat()) {
|
|
return chat->defaultRestrictions()
|
|
| disabledByAdminRights;
|
|
} else if (const auto channel = peer->asChannel()) {
|
|
return channel->defaultRestrictions()
|
|
| (channel->isPublic()
|
|
? (Flag::ChangeInfo | Flag::PinMessages)
|
|
: Flags(0))
|
|
| disabledByAdminRights;
|
|
}
|
|
Unexpected("User in EditPeerPermissionsBox.");
|
|
}());
|
|
const auto disabledMessages = [&] {
|
|
auto result = base::flat_map<Flags, QString>();
|
|
result.emplace(
|
|
disabledByAdminRights,
|
|
tr::lng_rights_permission_cant_edit(tr::now));
|
|
if (const auto channel = peer->asChannel()) {
|
|
if (channel->isPublic()
|
|
|| (channel->isMegagroup() && channel->linkedChat())) {
|
|
result.emplace(
|
|
Flag::ChangeInfo | Flag::PinMessages,
|
|
tr::lng_rights_permission_unavailable(tr::now));
|
|
}
|
|
}
|
|
return result;
|
|
}();
|
|
|
|
auto [checkboxes, getRestrictions, changes] = CreateEditRestrictions(
|
|
inner,
|
|
tr::lng_rights_default_restrictions_header(),
|
|
restrictions,
|
|
disabledMessages,
|
|
{ .isForum = peer->isForum() });
|
|
|
|
inner->add(std::move(checkboxes));
|
|
|
|
struct State {
|
|
rpl::variable<int> slowmodeSeconds;
|
|
rpl::variable<int> boostsUnrestrict;
|
|
rpl::variable<bool> hasSendRestrictions;
|
|
};
|
|
static constexpr auto kSendRestrictions = Flag::EmbedLinks
|
|
| Flag::SendGames
|
|
| Flag::SendGifs
|
|
| Flag::SendInline
|
|
| Flag::SendPolls
|
|
| Flag::SendStickers
|
|
| Flag::SendPhotos
|
|
| Flag::SendVideos
|
|
| Flag::SendVideoMessages
|
|
| Flag::SendMusic
|
|
| Flag::SendVoiceMessages
|
|
| Flag::SendFiles
|
|
| Flag::SendOther;
|
|
const auto state = inner->lifetime().make_state<State>();
|
|
state->hasSendRestrictions = ((restrictions & kSendRestrictions) != 0)
|
|
|| (peer->isChannel() && peer->asChannel()->slowmodeSeconds() > 0);
|
|
state->boostsUnrestrict = AddBoostsUnrestrictWrapped(
|
|
inner,
|
|
peer,
|
|
state->hasSendRestrictions.value());
|
|
state->slowmodeSeconds = AddSlowmodeSlider(inner, peer);
|
|
state->hasSendRestrictions = rpl::combine(
|
|
rpl::single(
|
|
restrictions
|
|
) | rpl::then(std::move(changes)),
|
|
state->slowmodeSeconds.value()
|
|
) | rpl::map([](ChatRestrictions restrictions, int slowmodeSeconds) {
|
|
return ((restrictions & kSendRestrictions) != 0) || slowmodeSeconds;
|
|
});
|
|
|
|
if (const auto channel = peer->asChannel()) {
|
|
if (channel->amCreator()
|
|
&& channel->membersCount() >= kSuggestGigagroupThreshold) {
|
|
AddSuggestGigagroup(
|
|
inner,
|
|
AboutGigagroupCallback(
|
|
peer->asChannel(),
|
|
navigation->parentController()));
|
|
}
|
|
}
|
|
|
|
AddBannedButtons(inner, navigation, peer);
|
|
|
|
box->addButton(tr::lng_settings_save(), [=, rights = getRestrictions] {
|
|
const auto restrictions = rights();
|
|
const auto slowmodeSeconds = state->slowmodeSeconds.current();
|
|
const auto hasRestrictions = (slowmodeSeconds > 0)
|
|
|| ((restrictions & kSendRestrictions) != 0);
|
|
const auto boostsUnrestrict = hasRestrictions
|
|
? state->boostsUnrestrict.current()
|
|
: 0;
|
|
done({
|
|
restrictions,
|
|
slowmodeSeconds,
|
|
boostsUnrestrict,
|
|
});
|
|
});
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
|
|
box->setWidth(st::boxWideWidth);
|
|
}
|
|
|
|
Fn<void()> AboutGigagroupCallback(
|
|
not_null<ChannelData*> channel,
|
|
not_null<Window::SessionController*> controller) {
|
|
const auto weak = base::make_weak(controller);
|
|
|
|
const auto converting = std::make_shared<bool>();
|
|
const auto convertSure = [=] {
|
|
if (*converting) {
|
|
return;
|
|
}
|
|
*converting = true;
|
|
channel->session().api().request(MTPchannels_ConvertToGigagroup(
|
|
channel->inputChannel
|
|
)).done([=](const MTPUpdates &result) {
|
|
channel->session().api().applyUpdates(result);
|
|
if (const auto strong = weak.get()) {
|
|
strong->window().hideSettingsAndLayer();
|
|
strong->showToast(tr::lng_gigagroup_done(tr::now));
|
|
}
|
|
}).fail([=] {
|
|
*converting = false;
|
|
}).send();
|
|
};
|
|
const auto convertWarn = [=] {
|
|
const auto strong = weak.get();
|
|
if (*converting || !strong) {
|
|
return;
|
|
}
|
|
strong->show(Box([=](not_null<Ui::GenericBox*> box) {
|
|
box->setTitle(tr::lng_gigagroup_warning_title());
|
|
box->addRow(
|
|
object_ptr<Ui::FlatLabel>(
|
|
box,
|
|
tr::lng_gigagroup_warning(
|
|
) | Ui::Text::ToRichLangValue(),
|
|
st::infoAboutGigagroup));
|
|
box->addButton(tr::lng_gigagroup_convert_sure(), convertSure);
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
}));
|
|
};
|
|
return [=] {
|
|
const auto strong = weak.get();
|
|
if (*converting || !strong) {
|
|
return;
|
|
}
|
|
strong->show(Box([=](not_null<Ui::GenericBox*> box) {
|
|
box->setTitle(tr::lng_gigagroup_convert_title());
|
|
const auto addFeature = [&](rpl::producer<QString> text) {
|
|
using namespace rpl::mappers;
|
|
const auto prefix = QString::fromUtf8("\xE2\x80\xA2 ");
|
|
box->addRow(
|
|
object_ptr<Ui::FlatLabel>(
|
|
box,
|
|
std::move(text) | rpl::map(prefix + _1),
|
|
st::infoAboutGigagroup),
|
|
style::margins(
|
|
st::boxRowPadding.left(),
|
|
st::boxLittleSkip,
|
|
st::boxRowPadding.right(),
|
|
st::boxLittleSkip));
|
|
};
|
|
addFeature(tr::lng_gigagroup_convert_feature1());
|
|
addFeature(tr::lng_gigagroup_convert_feature2());
|
|
addFeature(tr::lng_gigagroup_convert_feature3());
|
|
box->addButton(tr::lng_gigagroup_convert_sure(), convertWarn);
|
|
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
|
|
}));
|
|
};
|
|
}
|
|
|
|
std::vector<RestrictionLabel> RestrictionLabels(
|
|
Data::RestrictionsSetOptions options) {
|
|
auto result = std::vector<RestrictionLabel>();
|
|
for (const auto &[_, r] : NestedRestrictionLabelsList(options)) {
|
|
result.insert(result.end(), r.begin(), r.end());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
std::vector<AdminRightLabel> AdminRightLabels(
|
|
Data::AdminRightsSetOptions options) {
|
|
auto result = std::vector<AdminRightLabel>();
|
|
for (const auto &[_, r] : NestedAdminRightLabels(options)) {
|
|
result.insert(result.end(), r.begin(), r.end());
|
|
}
|
|
return result;
|
|
}
|
|
|
|
EditFlagsControl<ChatRestrictions> CreateEditRestrictions(
|
|
QWidget *parent,
|
|
rpl::producer<QString> header,
|
|
ChatRestrictions restrictions,
|
|
base::flat_map<ChatRestrictions, QString> disabledMessages,
|
|
Data::RestrictionsSetOptions options) {
|
|
auto widget = object_ptr<Ui::VerticalLayout>(parent);
|
|
auto result = CreateEditFlags(
|
|
widget.data(),
|
|
NegateRestrictions(restrictions),
|
|
{
|
|
.header = std::move(header),
|
|
.labels = NestedRestrictionLabelsList(options),
|
|
.disabledMessages = std::move(disabledMessages),
|
|
});
|
|
result.widget = std::move(widget);
|
|
result.value = [original = std::move(result.value)]{
|
|
return NegateRestrictions(original());
|
|
};
|
|
result.changes = std::move(
|
|
result.changes
|
|
) | rpl::map(NegateRestrictions);
|
|
|
|
return result;
|
|
}
|
|
|
|
EditFlagsControl<ChatAdminRights> CreateEditAdminRights(
|
|
QWidget *parent,
|
|
rpl::producer<QString> header,
|
|
ChatAdminRights rights,
|
|
base::flat_map<ChatAdminRights, QString> disabledMessages,
|
|
Data::AdminRightsSetOptions options) {
|
|
auto widget = object_ptr<Ui::VerticalLayout>(parent);
|
|
auto result = CreateEditFlags(
|
|
widget.data(),
|
|
rights,
|
|
{
|
|
.header = std::move(header),
|
|
.labels = NestedAdminRightLabels(options),
|
|
.disabledMessages = std::move(disabledMessages),
|
|
});
|
|
result.widget = std::move(widget);
|
|
|
|
return result;
|
|
}
|
|
|
|
ChatAdminRights DisabledByDefaultRestrictions(not_null<PeerData*> peer) {
|
|
using Flag = ChatAdminRight;
|
|
using Restriction = ChatRestriction;
|
|
|
|
const auto restrictions = FixDependentRestrictions([&] {
|
|
if (const auto chat = peer->asChat()) {
|
|
return chat->defaultRestrictions();
|
|
} else if (const auto channel = peer->asChannel()) {
|
|
return channel->defaultRestrictions();
|
|
}
|
|
Unexpected("User in DisabledByDefaultRestrictions.");
|
|
}());
|
|
return Flag(0)
|
|
| ((restrictions & Restriction::PinMessages)
|
|
? Flag(0)
|
|
: Flag::PinMessages)
|
|
//
|
|
// We allow to edit 'invite_users' admin right no matter what
|
|
// is chosen in default permissions for 'invite_users', because
|
|
// if everyone can 'invite_users' it handles invite link for admins.
|
|
//
|
|
//| ((restrictions & Restriction::AddParticipants)
|
|
// ? Flag(0)
|
|
// : Flag::InviteByLinkOrAdd)
|
|
//
|
|
| ((restrictions & Restriction::ChangeInfo)
|
|
? Flag(0)
|
|
: Flag::ChangeInfo);
|
|
}
|
|
|
|
ChatRestrictions FixDependentRestrictions(ChatRestrictions restrictions) {
|
|
const auto &dependencies = Dependencies(restrictions);
|
|
|
|
// Fix iOS bug of saving send_inline like embed_links.
|
|
// We copy send_stickers to send_inline.
|
|
if (restrictions & ChatRestriction::SendStickers) {
|
|
restrictions |= ChatRestriction::SendInline;
|
|
} else {
|
|
restrictions &= ~ChatRestriction::SendInline;
|
|
}
|
|
|
|
// Apply the strictest.
|
|
const auto fixOne = [&] {
|
|
for (const auto &[first, second] : dependencies) {
|
|
if ((restrictions & second) && !(restrictions & first)) {
|
|
restrictions |= first;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
while (fixOne()) {
|
|
}
|
|
return restrictions;
|
|
}
|
|
|
|
ChatAdminRights AdminRightsForOwnershipTransfer(
|
|
Data::AdminRightsSetOptions options) {
|
|
auto result = ChatAdminRights();
|
|
for (const auto &entry : AdminRightLabels(options)) {
|
|
if (!(entry.flags & ChatAdminRight::Anonymous)) {
|
|
result |= entry.flags;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
EditFlagsControl<PowerSaving::Flags> CreateEditPowerSaving(
|
|
QWidget *parent,
|
|
PowerSaving::Flags flags,
|
|
rpl::producer<QString> forceDisabledMessage) {
|
|
auto widget = object_ptr<Ui::VerticalLayout>(parent);
|
|
auto descriptor = Settings::PowerSavingLabels();
|
|
descriptor.forceDisabledMessage = std::move(forceDisabledMessage);
|
|
auto result = CreateEditFlags(
|
|
widget.data(),
|
|
flags,
|
|
std::move(descriptor));
|
|
result.widget = std::move(widget);
|
|
|
|
return result;
|
|
}
|