Set custom reactions hard limit to max level.

This commit is contained in:
John Preston 2023-11-29 18:25:27 +04:00
parent 514ced1d8e
commit 2611899448
6 changed files with 54 additions and 9 deletions

View file

@ -1357,6 +1357,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_manage_peer_reactions_level#other" = "Your channel needs to reach level **{count}** to use **{same_count}** custom reactions."; "lng_manage_peer_reactions_level#other" = "Your channel needs to reach level **{count}** to use **{same_count}** custom reactions.";
"lng_manage_peer_reactions_boost" = "Boost your channel {link}."; "lng_manage_peer_reactions_boost" = "Boost your channel {link}.";
"lng_manage_peer_reactions_boost_link" = "here"; "lng_manage_peer_reactions_boost_link" = "here";
"lng_manage_peer_reactions_limit" = "Channels can't have more custom reactions.";
"lng_manage_peer_antispam" = "Aggressive Anti-Spam"; "lng_manage_peer_antispam" = "Aggressive Anti-Spam";
"lng_manage_peer_antispam_about" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report False Positives in Recent Actions."; "lng_manage_peer_antispam_about" = "Telegram will filter more spam but may occasionally affect ordinary messages. You can report False Positives in Recent Actions.";

View file

@ -36,6 +36,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_changes.h" #include "data/data_changes.h"
#include "data/data_message_reactions.h" #include "data/data_message_reactions.h"
#include "data/data_peer_values.h" #include "data/data_peer_values.h"
#include "data/data_premium_limits.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "history/admin_log/history_admin_log_section.h" #include "history/admin_log/history_admin_log_section.h"
#include "info/boosts/info_boosts_widget.h" #include "info/boosts/info_boosts_widget.h"
@ -1315,6 +1316,8 @@ void Controller::editReactions() {
EditAllowedReactionsArgs{ EditAllowedReactionsArgs{
.navigation = _navigation, .navigation = _navigation,
.allowedCustomReactions = counters.level, .allowedCustomReactions = counters.level,
.customReactionsHardLimit = Data::PremiumLimits(
&_peer->session()).maxBoostLevel(),
.list = _navigation->session().data().reactions().list( .list = _navigation->session().data().reactions().list(
Data::Reactions::Type::Active), Data::Reactions::Type::Active),
.allowed = Data::PeerAllowedReactions(_peer), .allowed = Data::PeerAllowedReactions(_peer),

View file

@ -42,6 +42,10 @@ constexpr auto kDisabledEmojiOpacity = 0.4;
struct UniqueCustomEmojiContext { struct UniqueCustomEmojiContext {
std::vector<DocumentId> ids; std::vector<DocumentId> ids;
Fn<bool(DocumentId)> applyHardLimit;
int hardLimit = 0;
int hardLimitChecked = 0;
bool hardLimitHit = false;
}; };
class MaybeDisabledEmoji final : public Ui::Text::CustomEmoji { class MaybeDisabledEmoji final : public Ui::Text::CustomEmoji {
@ -153,6 +157,7 @@ bool MaybeDisabledEmoji::readyInDefaultState() {
not_null<QTextDocument*> document, not_null<QTextDocument*> document,
UniqueCustomEmojiContext &context) { UniqueCustomEmojiContext &context) {
context.ids.clear(); context.ids.clear();
context.hardLimitChecked = 0;
auto removeFrom = 0; auto removeFrom = 0;
auto removeTill = 0; auto removeTill = 0;
auto block = document->begin(); auto block = document->begin();
@ -168,11 +173,20 @@ bool MaybeDisabledEmoji::readyInDefaultState() {
} }
const auto id = format.property(Ui::InputField::kCustomEmojiId); const auto id = format.property(Ui::InputField::kCustomEmojiId);
const auto documentId = id.toULongLong(); const auto documentId = id.toULongLong();
const auto applyHardLimit = context.applyHardLimit(documentId);
if (ranges::contains(context.ids, documentId)) { if (ranges::contains(context.ids, documentId)) {
removeTill += fragment.length(); removeTill += fragment.length();
break; break;
} else if (applyHardLimit
&& context.hardLimitChecked >= context.hardLimit) {
context.hardLimitHit = true;
removeTill += fragment.length();
break;
} }
context.ids.push_back(documentId); context.ids.push_back(documentId);
if (applyHardLimit) {
++context.hardLimitChecked;
}
} }
while (removeTill == removeFrom) { while (removeTill == removeFrom) {
block = block.next(); block = block.next();
@ -202,7 +216,9 @@ bool RemoveNonCustomEmoji(
void SetupOnlyCustomEmojiField( void SetupOnlyCustomEmojiField(
not_null<Ui::InputField*> field, not_null<Ui::InputField*> field,
Fn<void(std::vector<DocumentId>)> callback) { Fn<void(std::vector<DocumentId>, bool)> callback,
Fn<bool(DocumentId)> applyHardLimit,
int customHardLimit) {
field->setTagMimeProcessor(AllowOnlyCustomEmojiProcessor); field->setTagMimeProcessor(AllowOnlyCustomEmojiProcessor);
field->setMimeDataHook(AllowOnlyCustomEmojiMimeDataHook); field->setMimeDataHook(AllowOnlyCustomEmojiMimeDataHook);
@ -218,7 +234,10 @@ void SetupOnlyCustomEmojiField(
if (state->processing) { if (state->processing) {
return; return;
} }
auto context = UniqueCustomEmojiContext(); auto context = UniqueCustomEmojiContext{
.applyHardLimit = applyHardLimit,
.hardLimit = customHardLimit,
};
auto changed = false; auto changed = false;
state->processing = true; state->processing = true;
while (state->pending) { while (state->pending) {
@ -235,7 +254,7 @@ void SetupOnlyCustomEmojiField(
document->setPageSize(pageSize); document->setPageSize(pageSize);
} }
} }
callback(context.ids); callback(context.ids, context.hardLimitHit);
if (changed) { if (changed) {
field->forceProcessContentsChanges(); field->forceProcessContentsChanges();
} }
@ -289,9 +308,10 @@ struct ReactionsSelectorArgs {
rpl::producer<QString> title; rpl::producer<QString> title;
std::vector<Data::Reaction> list; std::vector<Data::Reaction> list;
std::vector<Data::ReactionId> selected; std::vector<Data::ReactionId> selected;
Fn<void(std::vector<Data::ReactionId>)> callback; Fn<void(std::vector<Data::ReactionId>, bool)> callback;
rpl::producer<ReactionsSelectorState> stateValue; rpl::producer<ReactionsSelectorState> stateValue;
int customAllowed = 0; int customAllowed = 0;
int customHardLimit = 0;
bool all = false; bool all = false;
}; };
@ -344,7 +364,12 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
}, std::move(customEmojiPaused)); }, std::move(customEmojiPaused));
const auto callback = args.callback; const auto callback = args.callback;
SetupOnlyCustomEmojiField(raw, [=](std::vector<DocumentId> ids) { const auto isCustom = [=](DocumentId id) {
return state->unifiedFactoryOwner->lookupReactionId(id).custom();
};
SetupOnlyCustomEmojiField(raw, [=](
std::vector<DocumentId> ids,
bool hardLimitHit) {
auto allowed = base::flat_set<DocumentId>(); auto allowed = base::flat_set<DocumentId>();
auto reactions = std::vector<Data::ReactionId>(); auto reactions = std::vector<Data::ReactionId>();
reactions.reserve(ids.size()); reactions.reserve(ids.size());
@ -361,8 +386,8 @@ object_ptr<Ui::RpWidget> AddReactionsSelector(
state->allowed = std::move(allowed); state->allowed = std::move(allowed);
raw->rawTextEdit()->update(); raw->rawTextEdit()->update();
} }
callback(std::move(reactions)); callback(std::move(reactions), hardLimitHit);
}); }, isCustom, args.customHardLimit);
raw->setTextWithTags(ComposeEmojiList(reactions, args.selected)); raw->setTextWithTags(ComposeEmojiList(reactions, args.selected));
using SelectorState = ReactionsSelectorState; using SelectorState = ReactionsSelectorState;
@ -667,13 +692,19 @@ void EditAllowedReactionsBox(
| ranges::views::transform(&Data::Reaction::id) | ranges::views::transform(&Data::Reaction::id)
| ranges::to_vector) | ranges::to_vector)
: allowed.some; : allowed.some;
const auto changed = [=](std::vector<Data::ReactionId> chosen) { const auto changed = [=](
std::vector<Data::ReactionId> chosen,
bool hardLimitHit) {
state->selected = std::move(chosen); state->selected = std::move(chosen);
state->customCount = ranges::count_if( state->customCount = ranges::count_if(
state->selected, state->selected,
&Data::ReactionId::custom); &Data::ReactionId::custom);
if (hardLimitHit) {
box->uiShow()->showToast(
tr::lng_manage_peer_reactions_limit(tr::now));
}
}; };
changed(selected.empty() ? DefaultSelected() : std::move(selected)); changed(selected.empty() ? DefaultSelected() : std::move(selected), {});
reactions->add(AddReactionsSelector(reactions, { reactions->add(AddReactionsSelector(reactions, {
.outer = box->getDelegate()->outerContainer(), .outer = box->getDelegate()->outerContainer(),
.controller = args.navigation->parentController(), .controller = args.navigation->parentController(),
@ -685,6 +716,7 @@ void EditAllowedReactionsBox(
.callback = changed, .callback = changed,
.stateValue = state->selectorState.value(), .stateValue = state->selectorState.value(),
.customAllowed = args.allowedCustomReactions, .customAllowed = args.allowedCustomReactions,
.customHardLimit = args.customReactionsHardLimit,
.all = !args.isGroup, .all = !args.isGroup,
}), st::boxRowPadding); }), st::boxRowPadding);

View file

@ -25,6 +25,7 @@ class SessionNavigation;
struct EditAllowedReactionsArgs { struct EditAllowedReactionsArgs {
not_null<Window::SessionNavigation*> navigation; not_null<Window::SessionNavigation*> navigation;
int allowedCustomReactions = 0; int allowedCustomReactions = 0;
int customReactionsHardLimit = 0;
bool isGroup = false; bool isGroup = false;
std::vector<Data::Reaction> list; std::vector<Data::Reaction> list;
Data::AllowedReactions allowed; Data::AllowedReactions allowed;

View file

@ -189,6 +189,12 @@ int PremiumLimits::aboutLengthCurrent() const {
: aboutLengthDefault(); : aboutLengthDefault();
} }
int PremiumLimits::maxBoostLevel() const {
return appConfigLimit(
u"boosts_channel_level_max"_q,
_session->isTestMode() ? 9 : 99);
}
int PremiumLimits::appConfigLimit( int PremiumLimits::appConfigLimit(
const QString &key, const QString &key,
int fallback) const { int fallback) const {

View file

@ -75,6 +75,8 @@ public:
[[nodiscard]] int aboutLengthPremium() const; [[nodiscard]] int aboutLengthPremium() const;
[[nodiscard]] int aboutLengthCurrent() const; [[nodiscard]] int aboutLengthCurrent() const;
[[nodiscard]] int maxBoostLevel() const;
private: private:
[[nodiscard]] int appConfigLimit( [[nodiscard]] int appConfigLimit(
const QString &key, const QString &key,