Allow saving boosts to lift restrictions.

This commit is contained in:
John Preston 2024-02-06 14:54:15 +04:00
parent f6a8c1e996
commit 180b14ea36
7 changed files with 317 additions and 23 deletions

View file

@ -2181,6 +2181,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_channel_reached_more_group#other" = "This group reached **Level {count}** and can now {post}.";
"lng_boost_channel_post_stories#one" = "post **{count} story** per day";
"lng_boost_channel_post_stories#other" = "post **{count} stories** per day";
"lng_boost_channel_features" = "Your boosts will help {channel} to unlock new features.";
"lng_boost_group_lift_restrictions" = "Boost the group to remove messaging restrictions.";
"lng_boost_group_lift_restrictions_many#one" = "Boost the group **{count} times** to remove messaging restrictions.";
"lng_boost_group_lift_restrictions_many#other" = "Boost the group **{count} times** to remove messaging restrictions.";
"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!";
"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels.";
"lng_boost_error_gifted_text_group" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost groups.";
@ -3566,6 +3570,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_rights_slowmode_interval_seconds#other" = "every {count} seconds";
"lng_rights_slowmode_interval_minutes#one" = "every {count} minute";
"lng_rights_slowmode_interval_minutes#other" = "every {count} minutes";
"lng_rights_boosts_no_restrict" = "Do not restrict boosters";
"lng_rights_boosts_about" = "Turn this on to always allow users who boosted your group to send messages and media.";
"lng_rights_boosts_about_on" = "Choose how many boosts a user must give to the group to bypass restrictions on sending messages.";
"lng_slowmode_enabled"= "Slow mode is enabled. You can send your next message in {left}.";
"lng_slowmode_no_many" = "Slow mode is enabled. You can't send more than one message at a time.";
@ -3661,6 +3668,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_restricted_send_gifs" = "The admins of this group restricted you from posting GIFs here.";
"lng_restricted_send_inline" = "The admins of this group restricted you from posting inline content here.";
"lng_restricted_send_polls" = "The admins of this group restricted you from posting polls here.";
"lng_restricted_boost_group" = "Boost this group to send messages";
"lng_restricted_send_message_until" = "The admins of this group restricted you from writing here until {date}, {time}.";
"lng_restricted_send_photos_until" = "The admins of this group restricted you from posting photos here until {date}, {time}.";

View file

@ -791,6 +791,9 @@ slowmodeLabelsMargin: margins(0px, 5px, 0px, 0px);
slowmodeLabel: LabelSimple(defaultLabelSimple) {
textFg: windowSubTextFg;
}
boostsUnrestrictLabel: FlatLabel(defaultFlatLabel) {
textFg: windowSubTextFg;
}
customBadgeField: InputField(defaultInputField) {
textMargins: margins(2px, 7px, 2px, 0px);

View file

@ -198,6 +198,37 @@ void SaveSlowmodeSeconds(
api->registerModifyRequest(key, requestId);
}
void SaveBoostsUnrestrict(
not_null<ChannelData*> channel,
int boostsUnrestrict,
Fn<void()> done) {
const auto api = &channel->session().api();
const auto key = Api::RequestKey("boosts_unrestrict", channel->id);
const auto requestId = api->request(
MTPchannels_SetBoostsToUnblockRestrictions(
channel->inputChannel,
MTP_int(boostsUnrestrict))
).done([=](const MTPUpdates &result) {
api->clearModifyRequest(key);
api->applyUpdates(result);
channel->setBoostsUnrestrict(
channel->boostsApplied(),
boostsUnrestrict);
done();
}).fail([=](const MTP::Error &error) {
api->clearModifyRequest(key);
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
return;
}
channel->setBoostsUnrestrict(
channel->boostsApplied(),
boostsUnrestrict);
done();
}).send();
api->registerModifyRequest(key, requestId);
}
void ShowEditPermissions(
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer) {
@ -215,6 +246,10 @@ void ShowEditPermissions(
close);
if (const auto channel = peer->asChannel()) {
SaveSlowmodeSeconds(channel, result.slowmodeSeconds, close);
SaveBoostsUnrestrict(
channel,
result.boostsUnrestrict,
close);
}
};
auto done = [=](EditPeerPermissionsBoxResult result) {
@ -225,7 +260,8 @@ void ShowEditPermissions(
const auto saveFor = peer->migrateToOrMe();
const auto chat = saveFor->asChat();
if (!result.slowmodeSeconds || !chat) {
if (!chat
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
save(saveFor, result);
return;
}

View file

@ -8,8 +8,11 @@ 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"
@ -34,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#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"
@ -42,6 +46,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace {
constexpr auto kSlowmodeValues = 7;
constexpr auto kBoostsUnrestrictValues = 5;
constexpr auto kSuggestGigagroupThreshold = 199000;
constexpr auto kForceDisableTooltipDuration = 3 * crl::time(1000);
@ -163,6 +168,10 @@ int SlowmodeDelayByIndex(int index) {
Unexpected("Index in SlowmodeDelayByIndex.");
}
[[nodiscard]] int BoostsUnrestrictByIndex(int index) {
return index + 1;
}
template <typename CheckboxesMap, typename DependenciesMap>
void ApplyDependencies(
const CheckboxesMap &checkboxes,
@ -768,14 +777,14 @@ void AddSlowmodeLabels(
}
}
Fn<int()> AddSlowmodeSlider(
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 [] { return 0; };
return rpl::single(0);
}
}
const auto channel = peer->asChannel();
@ -783,10 +792,6 @@ Fn<int()> AddSlowmodeSlider(
const auto secondsCount = lifetime.make_state<rpl::variable<int>>(
channel ? channel->slowmodeSeconds() : 0);
container->add(
object_ptr<Ui::BoxContentDivider>(container),
{ 0, st::infoProfileSkip, 0, st::infoProfileSkip });
container->add(
object_ptr<Ui::FlatLabel>(
container,
@ -856,7 +861,157 @@ Fn<int()> AddSlowmodeSlider(
st::proxyAboutPadding),
style::margins(0, st::infoProfileSkip, 0, st::infoProfileSkip));
return [=] { return secondsCount->current(); };
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 ? one : many).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(
@ -983,7 +1138,40 @@ void ShowEditPeerPermissionsBox(
inner->add(std::move(checkboxes));
const auto getSlowmodeSeconds = AddSlowmodeSlider(inner, peer);
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()
@ -999,7 +1187,18 @@ void ShowEditPeerPermissionsBox(
AddBannedButtons(inner, navigation, peer);
box->addButton(tr::lng_settings_save(), [=, rights = getRestrictions] {
done({ rights(), getSlowmodeSeconds() });
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(); });

View file

@ -37,6 +37,7 @@ class SessionNavigation;
struct EditPeerPermissionsBoxResult final {
ChatRestrictions rights;
int slowmodeSeconds = 0;
int boostsUnrestrict = 0;
};
void ShowEditPeerPermissionsBox(

View file

@ -772,34 +772,74 @@ void ChannelData::setMigrateFromChat(ChatData *chat) {
}
int ChannelData::slowmodeSeconds() const {
return _slowmodeSeconds;
if (const auto info = mgInfo.get()) {
return info->slowmodeSeconds;
}
return 0;
}
void ChannelData::setSlowmodeSeconds(int seconds) {
if (_slowmodeSeconds == seconds) {
if (!mgInfo || slowmodeSeconds() == seconds) {
return;
}
_slowmodeSeconds = seconds;
mgInfo->slowmodeSeconds = seconds;
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
}
TimeId ChannelData::slowmodeLastMessage() const {
return (hasAdminRights() || amCreator()) ? 0 : _slowmodeLastMessage;
return (hasAdminRights() || amCreator() || !mgInfo)
? 0
: mgInfo->slowmodeLastMessage;
}
void ChannelData::growSlowmodeLastMessage(TimeId when) {
const auto info = mgInfo.get();
const auto now = base::unixtime::now();
accumulate_min(when, now);
if (_slowmodeLastMessage > now) {
_slowmodeLastMessage = when;
} else if (_slowmodeLastMessage >= when) {
if (!info) {
return;
} else if (info->slowmodeLastMessage > now) {
info->slowmodeLastMessage = when;
} else if (info->slowmodeLastMessage >= when) {
return;
} else {
_slowmodeLastMessage = when;
info->slowmodeLastMessage = when;
}
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
}
int ChannelData::boostsApplied() const {
if (const auto info = mgInfo.get()) {
return info->boostsApplied;
}
return 0;
}
int ChannelData::boostsUnrestrict() const {
if (const auto info = mgInfo.get()) {
return info->boostsUnrestrict;
}
return 0;
}
void ChannelData::setBoostsUnrestrict(int applied, int unrestrict) {
if (const auto info = mgInfo.get()) {
if (info->boostsApplied == applied
&& info->boostsUnrestrict == unrestrict) {
return;
}
const auto wasUnrestricted = (info->boostsApplied > 0)
&& (info->boostsApplied >= info->boostsUnrestrict);
const auto nowUnrestricted = (applied > 0)
&& (applied >= unrestrict);
info->boostsApplied = applied;
info->boostsUnrestrict = unrestrict;
if (wasUnrestricted != nowUnrestricted) {
session().changes().peerUpdated(this, UpdateFlag::Rights);
}
}
}
void ChannelData::setInvitePeek(const QString &hash, TimeId expires) {
if (!_invitePeek) {
_invitePeek = std::make_unique<InvitePeek>();
@ -1118,8 +1158,9 @@ void ApplyChannelUpdate(
if (stickersChanged) {
session->changes().peerUpdated(channel, UpdateFlag::StickersSet);
}
channel->mgInfo->boostsApplied = update.vboosts_applied().value_or_empty();
channel->setBoostsUnrestrict(
update.vboosts_applied().value_or_empty(),
update.vboosts_unrestrict().value_or_empty());
}
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
channel->setTranslationDisabled(update.is_translations_disabled());

View file

@ -133,6 +133,10 @@ public:
mutable int lastParticipantsStatus = LastParticipantsUpToDate;
int lastParticipantsCount = 0;
int boostsApplied = 0;
int boostsUnrestrict = 0;
int slowmodeSeconds = 0;
TimeId slowmodeLastMessage = 0;
private:
ChatData *_migratedFrom = nullptr;
@ -433,6 +437,11 @@ public:
[[nodiscard]] TimeId slowmodeLastMessage() const;
void growSlowmodeLastMessage(TimeId when);
[[nodiscard]] int boostsApplied() const;
[[nodiscard]] int boostsUnrestrict() const;
[[nodiscard]] bool unrestrictedByBoosts() const;
void setBoostsUnrestrict(int applied, int unrestrict);
void setInvitePeek(const QString &hash, TimeId expires);
void clearInvitePeek();
[[nodiscard]] TimeId invitePeekExpires() const;
@ -520,9 +529,6 @@ private:
std::unique_ptr<Data::GroupCall> _call;
PeerId _callDefaultJoinAs = 0;
int _slowmodeSeconds = 0;
TimeId _slowmodeLastMessage = 0;
};
namespace Data {