Track stars-per-message for users and channels.

This commit is contained in:
John Preston 2025-02-13 14:12:19 +04:00
parent f2aa3afbbb
commit 45c7829cd8
10 changed files with 239 additions and 102 deletions

View file

@ -45,9 +45,9 @@ namespace {
constexpr auto kPremiumsRowId = PeerId(FakeChatId(BareId(1))).value;
constexpr auto kMiniAppsRowId = PeerId(FakeChatId(BareId(2))).value;
constexpr auto kGetPercent = 85;
constexpr auto kStarsMin = 10;
constexpr auto kStarsMin = 1;
constexpr auto kStarsMax = 9000;
constexpr auto kDefaultChargeStars = 200;
constexpr auto kDefaultChargeStars = 10;
using Exceptions = Api::UserPrivacy::Exceptions;
@ -934,7 +934,9 @@ void EditMessagesPrivacyBox(
Ui::AddSkip(inner, st::messagePrivacyTopSkip);
Ui::AddSubsectionTitle(inner, tr::lng_messages_privacy_subtitle());
const auto group = std::make_shared<Ui::RadiobuttonGroup>(
(privacy->newRequirePremiumCurrent()
(!allowed()
? kOptionAll
: privacy->newRequirePremiumCurrent()
? kOptionPremium
: privacy->newChargeStarsCurrent()
? kOptionCharge
@ -984,66 +986,17 @@ void EditMessagesPrivacyBox(
const auto chargeInner = chargeWrap->entity();
Ui::AddSkip(chargeInner);
Ui::AddSubsectionTitle(chargeInner, tr::lng_messages_privacy_price());
struct State {
rpl::variable<int> stars;
};
const auto state = std::make_shared<State>();
const auto savedValue = privacy->newChargeStarsCurrent();
const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars;
state->stars = chargeStars;
auto values = std::vector<int>();
if (chargeStars < kStarsMin) {
values.push_back(chargeStars);
}
for (auto i = kStarsMin; i < 100; ++i) {
values.push_back(i);
}
for (auto i = 100; i < 1000; i += 10) {
if (i < chargeStars + 10 && chargeStars < i) {
values.push_back(chargeStars);
}
values.push_back(i);
}
for (auto i = 1000; i < kStarsMax + 1; i += 100) {
if (i < chargeStars + 100 && chargeStars < i) {
values.push_back(chargeStars);
}
values.push_back(i);
}
const auto valuesCount = int(values.size());
const auto setStars = [=](int value) {
state->stars = value;
};
chargeInner->add(
MakeChargeStarsSlider(
chargeInner,
&st::settingsScale,
&st::settingsScaleLabel,
valuesCount,
[=](int index) { return values[index]; },
chargeStars,
setStars,
setStars),
st::boxRowPadding);
Ui::AddSkip(chargeInner);
auto dollars = state->stars.value() | rpl::map([=](int stars) {
const auto ratio = controller->session().appConfig().get<float64>(
u"stars_usd_withdraw_rate_x1000"_q,
1200);
const auto dollars = int(base::SafeRound(stars * (ratio / 1000.)));
return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q);
});
Ui::AddDividerText(
state->stars = SetupChargeSlider(
chargeInner,
tr::lng_messages_privacy_price_about(
lt_percent,
rpl::single(QString::number(kGetPercent) + '%'),
lt_amount,
std::move(dollars)));
controller->session().user(),
savedValue);
Ui::AddSkip(chargeInner);
Ui::AddSubsectionTitle(
@ -1137,3 +1090,77 @@ void EditMessagesPrivacyBox(
});
}
}
rpl::producer<int> SetupChargeSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
int savedValue) {
struct State {
rpl::variable<int> stars;
};
const auto group = !peer->isUser();
const auto state = container->lifetime().make_state<State>();
const auto chargeStars = savedValue ? savedValue : kDefaultChargeStars;
state->stars = chargeStars;
Ui::AddSubsectionTitle(container, group
? tr::lng_rights_charge_price()
: tr::lng_messages_privacy_price());
auto values = std::vector<int>();
if (chargeStars < kStarsMin) {
values.push_back(chargeStars);
}
for (auto i = kStarsMin; i < 100; ++i) {
values.push_back(i);
}
for (auto i = 100; i < 1000; i += 10) {
if (i < chargeStars + 10 && chargeStars < i) {
values.push_back(chargeStars);
}
values.push_back(i);
}
for (auto i = 1000; i < kStarsMax + 1; i += 100) {
if (i < chargeStars + 100 && chargeStars < i) {
values.push_back(chargeStars);
}
values.push_back(i);
}
const auto valuesCount = int(values.size());
const auto setStars = [=](int value) {
state->stars = value;
};
container->add(
MakeChargeStarsSlider(
container,
&st::settingsScale,
&st::settingsScaleLabel,
valuesCount,
[=](int index) { return values[index]; },
chargeStars,
setStars,
setStars),
st::boxRowPadding);
const auto skip = 2 * st::defaultVerticalListSkip;
Ui::AddSkip(container, skip);
auto dollars = state->stars.value() | rpl::map([=](int stars) {
const auto ratio = peer->session().appConfig().get<float64>(
u"stars_usd_withdraw_rate_x1000"_q,
1200);
const auto dollars = int(base::SafeRound(stars * (ratio / 1000.)));
return '~' + Ui::FillAmountAndCurrency(dollars, u"USD"_q);
});
Ui::AddDividerText(
container,
(group
? tr::lng_rights_charge_price_about
: tr::lng_messages_privacy_price_about)(
lt_percent,
rpl::single(QString::number(kGetPercent) + '%'),
lt_amount,
std::move(dollars)));
return state->stars.value();
}

View file

@ -169,3 +169,8 @@ private:
void EditMessagesPrivacyBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller);
[[nodiscard]] rpl::producer<int> SetupChargeSlider(
not_null<Ui::VerticalLayout*> container,
not_null<PeerData*> peer,
int savedValue);

View file

@ -219,6 +219,33 @@ void SaveSlowmodeSeconds(
api->registerModifyRequest(key, requestId);
}
void SaveStarsPerMessage(
not_null<ChannelData*> channel,
int starsPerMessage,
Fn<void()> done) {
const auto api = &channel->session().api();
const auto key = Api::RequestKey("stars_per_message", channel->id);
const auto requestId = api->request(MTPchannels_UpdatePaidMessagesPrice(
channel->inputChannel,
MTP_long(starsPerMessage)
)).done([=](const MTPUpdates &result) {
api->clearModifyRequest(key);
api->applyUpdates(result);
channel->setStarsPerMessage(starsPerMessage);
done();
}).fail([=](const MTP::Error &error) {
api->clearModifyRequest(key);
if (error.type() != u"CHAT_NOT_MODIFIED"_q) {
return;
}
channel->setStarsPerMessage(starsPerMessage);
done();
}).send();
api->registerModifyRequest(key, requestId);
}
void SaveBoostsUnrestrict(
not_null<ChannelData*> channel,
int boostsUnrestrict,
@ -271,6 +298,7 @@ void ShowEditPermissions(
channel,
result.boostsUnrestrict,
close);
SaveStarsPerMessage(channel, result.starsPerMessage, close);
}
};
auto done = [=](EditPeerPermissionsBoxResult result) {
@ -282,7 +310,9 @@ void ShowEditPermissions(
const auto saveFor = peer->migrateToOrMe();
const auto chat = saveFor->asChat();
if (!chat
|| (!result.slowmodeSeconds && !result.boostsUnrestrict)) {
|| (!result.slowmodeSeconds
&& !result.boostsUnrestrict
&& !result.starsPerMessage)) {
save(saveFor, result);
return;
}

View file

@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "info/profile/info_profile_values.h"
#include "boxes/peers/edit_participants_box.h"
#include "boxes/peers/edit_peer_info_box.h"
#include "boxes/edit_privacy_box.h"
#include "settings/settings_power_saving.h"
#include "window/window_session_controller.h"
#include "window/window_controller.h"
@ -940,9 +941,7 @@ rpl::producer<int> AddBoostsUnrestrictSlider(
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 });
Ui::AddSkip(container);
auto enabled = boostsUnrestrict->value(
) | rpl::map(_1 > 0);
@ -1006,19 +1005,20 @@ rpl::producer<int> AddBoostsUnrestrictWrapped(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
wrap->toggleOn(rpl::duplicate(shown), anim::type::normal);
wrap->toggleOn(std::move(shown), anim::type::normal);
wrap->finishAnimating();
auto result = AddBoostsUnrestrictSlider(wrap->entity(), peer);
const auto divider = container->add(
const auto inner = wrap->entity();
auto result = AddBoostsUnrestrictSlider(inner, peer);
const auto skip = st::defaultVerticalListSkip;
const auto divider = inner->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));
inner,
object_ptr<Ui::BoxContentDivider>(inner),
QMargins{ 0, skip, 0, skip }));
divider->toggleOn(rpl::duplicate(result) | rpl::map(!rpl::mappers::_1));
divider->finishAnimating();
return result;
@ -1157,7 +1157,38 @@ void ShowEditPeerPermissionsBox(
rpl::variable<int> slowmodeSeconds;
rpl::variable<int> boostsUnrestrict;
rpl::variable<bool> hasSendRestrictions;
rpl::variable<int> starsPerMessage;
};
const auto state = inner->lifetime().make_state<State>();
Ui::AddSkip(inner);
Ui::AddDivider(inner);
Ui::AddSkip(inner);
const auto starsPerMessage = peer->isChannel()
? peer->asChannel()->starsPerMessage()
: 0;
const auto charging = inner->add(object_ptr<Ui::SettingsButton>(
inner,
tr::lng_rights_charge_stars(),
st::settingsButtonNoIcon));
charging->toggleOn(rpl::single(starsPerMessage > 0));
Ui::AddSkip(inner);
Ui::AddDividerText(inner, tr::lng_rights_charge_stars_about());
const auto chargeWrap = inner->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
inner,
object_ptr<Ui::VerticalLayout>(inner)));
chargeWrap->toggleOn(charging->toggledValue());
chargeWrap->finishAnimating();
const auto chargeInner = chargeWrap->entity();
Ui::AddSkip(chargeInner);
state->starsPerMessage = SetupChargeSlider(
chargeInner,
peer,
starsPerMessage);
static constexpr auto kSendRestrictions = Flag::EmbedLinks
| Flag::SendGames
| Flag::SendGifs
@ -1171,7 +1202,6 @@ void ShowEditPeerPermissionsBox(
| 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(
@ -1212,10 +1242,14 @@ void ShowEditPeerPermissionsBox(
const auto boostsUnrestrict = hasRestrictions
? state->boostsUnrestrict.current()
: 0;
const auto starsPerMessage = charging->toggled()
? state->starsPerMessage.current()
: 0;
done({
restrictions,
slowmodeSeconds,
boostsUnrestrict,
starsPerMessage,
});
});
box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });

View file

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

View file

@ -75,46 +75,47 @@ struct PeerUpdate {
BackgroundEmoji = (1ULL << 15),
StoriesState = (1ULL << 16),
VerifyInfo = (1ULL << 17),
StarsPerMessage = (1ULL << 18),
// For users
CanShareContact = (1ULL << 18),
IsContact = (1ULL << 19),
PhoneNumber = (1ULL << 20),
OnlineStatus = (1ULL << 21),
BotCommands = (1ULL << 22),
BotCanBeInvited = (1ULL << 23),
BotStartToken = (1ULL << 24),
CommonChats = (1ULL << 25),
PeerGifts = (1ULL << 26),
HasCalls = (1ULL << 27),
SupportInfo = (1ULL << 28),
IsBot = (1ULL << 29),
EmojiStatus = (1ULL << 30),
BusinessDetails = (1ULL << 31),
Birthday = (1ULL << 32),
PersonalChannel = (1ULL << 33),
StarRefProgram = (1ULL << 34),
CanShareContact = (1ULL << 19),
IsContact = (1ULL << 20),
PhoneNumber = (1ULL << 21),
OnlineStatus = (1ULL << 22),
BotCommands = (1ULL << 23),
BotCanBeInvited = (1ULL << 24),
BotStartToken = (1ULL << 25),
CommonChats = (1ULL << 26),
PeerGifts = (1ULL << 27),
HasCalls = (1ULL << 28),
SupportInfo = (1ULL << 29),
IsBot = (1ULL << 30),
EmojiStatus = (1ULL << 31),
BusinessDetails = (1ULL << 32),
Birthday = (1ULL << 33),
PersonalChannel = (1ULL << 34),
StarRefProgram = (1ULL << 35),
// For chats and channels
InviteLinks = (1ULL << 35),
Members = (1ULL << 36),
Admins = (1ULL << 37),
BannedUsers = (1ULL << 38),
Rights = (1ULL << 39),
PendingRequests = (1ULL << 40),
Reactions = (1ULL << 41),
InviteLinks = (1ULL << 36),
Members = (1ULL << 37),
Admins = (1ULL << 38),
BannedUsers = (1ULL << 39),
Rights = (1ULL << 40),
PendingRequests = (1ULL << 41),
Reactions = (1ULL << 42),
// For channels
ChannelAmIn = (1ULL << 42),
StickersSet = (1ULL << 43),
EmojiSet = (1ULL << 44),
ChannelLinkedChat = (1ULL << 45),
ChannelLocation = (1ULL << 46),
Slowmode = (1ULL << 47),
GroupCall = (1ULL << 48),
ChannelAmIn = (1ULL << 43),
StickersSet = (1ULL << 44),
EmojiSet = (1ULL << 45),
ChannelLinkedChat = (1ULL << 46),
ChannelLocation = (1ULL << 47),
Slowmode = (1ULL << 48),
GroupCall = (1ULL << 49),
// For iteration
LastUsedBit = (1ULL << 48),
LastUsedBit = (1ULL << 49),
};
using Flags = base::flags<Flag>;
friend inline constexpr auto is_flag_type(Flag) { return true; }

View file

@ -859,6 +859,21 @@ void ChannelData::growSlowmodeLastMessage(TimeId when) {
session().changes().peerUpdated(this, UpdateFlag::Slowmode);
}
int ChannelData::starsPerMessage() const {
if (const auto info = mgInfo.get()) {
return info->starsPerMessage;
}
return 0;
}
void ChannelData::setStarsPerMessage(int stars) {
if (!mgInfo || starsPerMessage() == stars) {
return;
}
mgInfo->starsPerMessage = stars;
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
}
int ChannelData::peerGiftsCount() const {
return _peerGiftsCount;
}
@ -1209,6 +1224,8 @@ void ApplyChannelUpdate(
} else {
channel->setLinkedChat(nullptr);
}
channel->setStarsPerMessage(
update.vsend_paid_messages_stars().value_or_empty());
if (const auto history = channel->owner().historyLoaded(channel)) {
if (const auto available = update.vavailable_min_id()) {
history->clearUpTill(available->v);

View file

@ -145,6 +145,8 @@ public:
int slowmodeSeconds = 0;
TimeId slowmodeLastMessage = 0;
int starsPerMessage = 0;
private:
ChatData *_migratedFrom = nullptr;
ChannelLocation _location;
@ -456,6 +458,9 @@ public:
[[nodiscard]] TimeId slowmodeLastMessage() const;
void growSlowmodeLastMessage(TimeId when);
[[nodiscard]] int starsPerMessage() const;
void setStarsPerMessage(int stars);
[[nodiscard]] int peerGiftsCount() const;
void setPeerGiftsCount(int count);

View file

@ -532,6 +532,17 @@ bool UserData::readDatesPrivate() const {
return (flags() & UserDataFlag::ReadDatesPrivate);
}
int UserData::starsPerMessage() const {
return _starsPerMessage;
}
void UserData::setStarsPerMessage(int stars) {
if (_starsPerMessage != stars) {
_starsPerMessage = stars;
session().changes().peerUpdated(this, UpdateFlag::StarsPerMessage);
}
}
bool UserData::canAddContact() const {
return canShareThisContact() && !isContact();
}
@ -722,6 +733,8 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
user->setTranslationDisabled(update.is_translations_disabled());
user->setPrivateForwardName(
update.vprivate_forward_name().value_or_empty());
user->setStarsPerMessage(
update.vsend_paid_messages_stars().value_or_empty());
if (const auto info = user->botInfo.get()) {
const auto group = update.vbot_group_admin_rights()

View file

@ -179,6 +179,9 @@ public:
[[nodiscard]] bool canSendIgnoreRequirePremium() const;
[[nodiscard]] bool readDatesPrivate() const;
[[nodiscard]] int starsPerMessage() const;
void setStarsPerMessage(int stars);
[[nodiscard]] bool canShareThisContact() const;
[[nodiscard]] bool canAddContact() const;
@ -268,6 +271,7 @@ private:
Data::Birthday _birthday;
int _commonChatsCount = 0;
int _peerGiftsCount = 0;
int _starsPerMessage = 0;
ContactStatus _contactStatus = ContactStatus::Unknown;
CallsStatus _callsStatus = CallsStatus::Unknown;