Check amounts of stars/TON.

This commit is contained in:
John Preston 2025-06-26 22:30:55 +04:00
parent 6f305c8974
commit 4840a9094b
24 changed files with 474 additions and 150 deletions

View file

@ -2911,6 +2911,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts."; "lng_credits_small_balance_star_gift" = "Buy **Stars** to send gifts to {user} and other contacts.";
"lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}."; "lng_credits_small_balance_for_message" = "Buy **Stars** to send messages to {user}.";
"lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages."; "lng_credits_small_balance_for_messages" = "Buy **Stars** to send messages.";
"lng_credits_small_balance_for_suggest" = "Buy **Stars** to suggest post to {channel}.";
"lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram."; "lng_credits_small_balance_fallback" = "Buy **Stars** to unlock content and services on Telegram.";
"lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars."; "lng_credits_purchase_blocked" = "Sorry, you can't purchase this item with Telegram Stars.";
"lng_credits_enough" = "You have enough stars at the moment. {link}"; "lng_credits_enough" = "You have enough stars at the moment. {link}";
@ -4478,6 +4479,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_suggest_warn_text_stars" = "You won't receive **Stars** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published."; "lng_suggest_warn_text_stars" = "You won't receive **Stars** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published.";
"lng_suggest_warn_text_ton" = "You won't receive **TON** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published."; "lng_suggest_warn_text_ton" = "You won't receive **TON** for the post if you delete it now. The post must remain visible for at least **24 hours** after it was published.";
"lng_suggest_warn_delete_anyway" = "Delete Anyway"; "lng_suggest_warn_delete_anyway" = "Delete Anyway";
"lng_suggest_low_ton_title" = "{amount} TON Needed";
"lng_suggest_low_ton_text" = "Buy **TON** to suggest message to {channel} and others.";
"lng_suggest_low_ton_fragment" = "Buy on Fragment";
"lng_suggest_low_ton_fragment_url" = "https://fragment.com/ads/topup";
"lng_reply_in_another_title" = "Reply in..."; "lng_reply_in_another_title" = "Reply in...";
"lng_reply_in_another_chat" = "Reply in Another Chat"; "lng_reply_in_another_chat" = "Reply in Another Chat";

View file

@ -198,18 +198,8 @@ void SendSuggest(
SendSuggest(show, item, state, modify, done, stars); SendSuggest(show, item, state, modify, done, stars);
} }
}; };
const auto checked = state->sendPayment.check(
show,
item->history()->peer,
1,
starsApproved,
withPaymentApproved);
if (!checked) {
return;
}
const auto isForward = item->Get<HistoryMessageForwarded>(); const auto isForward = item->Get<HistoryMessageForwarded>();
auto action = SendAction(item->history()); auto action = SendAction(item->history());
action.options.suggest.exists = 1; action.options.suggest.exists = 1;
if (suggestion) { if (suggestion) {
action.options.suggest.date = suggestion->date; action.options.suggest.date = suggestion->date;
@ -218,12 +208,22 @@ void SendSuggest(
action.options.suggest.ton = suggestion->price.ton() ? 1 : 0; action.options.suggest.ton = suggestion->price.ton() ? 1 : 0;
} }
modify(action.options.suggest); modify(action.options.suggest);
action.options.starsApproved = starsApproved; action.options.starsApproved = starsApproved;
action.replyTo.monoforumPeerId = item->history()->amMonoforumAdmin() action.replyTo.monoforumPeerId = item->history()->amMonoforumAdmin()
? item->sublistPeerId() ? item->sublistPeerId()
: PeerId(); : PeerId();
action.replyTo.messageId = item->fullId(); action.replyTo.messageId = item->fullId();
const auto checked = state->sendPayment.check(
show,
item->history()->peer,
action.options,
1,
withPaymentApproved);
if (!checked) {
return;
}
show->session().api().sendAction(action); show->session().api().sendAction(action);
show->session().api().forwardMessages({ show->session().api().forwardMessages({
.items = { item }, .items = { item },
@ -302,7 +302,7 @@ void SuggestOfferForMessage(
}; };
using namespace HistoryView; using namespace HistoryView;
auto priceBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{ auto priceBox = Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.session = &show->session(), .peer = item->history()->peer,
.done = done, .done = done,
.value = values, .value = values,
.mode = mode, .mode = mode,

View file

@ -101,8 +101,10 @@ public:
return result; return result;
} }
friend inline auto operator<=>(CreditsAmount, CreditsAmount) = default; friend inline constexpr auto operator<=>(CreditsAmount, CreditsAmount)
friend inline bool operator==(CreditsAmount, CreditsAmount) = default; = default;
friend inline constexpr bool operator==(CreditsAmount, CreditsAmount)
= default;
[[nodiscard]] CreditsAmount abs() const { [[nodiscard]] CreditsAmount abs() const {
return (_whole < 0) ? CreditsAmount(-_whole, -_nano) : *this; return (_whole < 0) ? CreditsAmount(-_whole, -_nano) : *this;

View file

@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/ */
#include "data/components/credits.h" #include "data/components/credits.h"
#include "apiwrap.h"
#include "api/api_credits.h" #include "api/api_credits.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
@ -93,6 +94,54 @@ rpl::producer<CreditsAmount> Credits::balanceValue() const {
return _nonLockedBalance.value(); return _nonLockedBalance.value();
} }
void Credits::tonLoad(bool force) {
if (_tonRequestId
|| (!force
&& _tonLastLoaded
&& _tonLastLoaded + kReloadThreshold > crl::now())) {
return;
}
_tonRequestId = _session->api().request(MTPpayments_GetStarsStatus(
MTP_flags(MTPpayments_GetStarsStatus::Flag::f_ton),
MTP_inputPeerSelf()
)).done([=](const MTPpayments_StarsStatus &result) {
_tonRequestId = 0;
const auto amount = CreditsAmountFromTL(result.data().vbalance());
if (amount.ton()) {
apply(amount);
} else if (amount.empty()) {
apply(CreditsAmount(0, CreditsType::Ton));
} else {
LOG(("API Error: Got weird balance."));
}
}).fail([=](const MTP::Error &error) {
_tonRequestId = 0;
LOG(("API Error: Couldn't get TON balance, error: %1"
).arg(error.type()));
}).send();
}
bool Credits::tonLoaded() const {
return _tonLastLoaded != 0;
}
rpl::producer<bool> Credits::tonLoadedValue() const {
if (tonLoaded()) {
return rpl::single(true);
}
return rpl::single(
false
) | rpl::then(_tonLoadedChanges.events() | rpl::map_to(true));
}
CreditsAmount Credits::tonBalance() const {
return _tonBalance.current();
}
rpl::producer<CreditsAmount> Credits::tonBalanceValue() const {
return _tonBalance.value();
}
void Credits::updateNonLockedValue() { void Credits::updateNonLockedValue() {
_nonLockedBalance = (_balance >= _locked) _nonLockedBalance = (_balance >= _locked)
? (_balance - _locked) ? (_balance - _locked)
@ -133,7 +182,12 @@ void Credits::invalidate() {
void Credits::apply(CreditsAmount balance) { void Credits::apply(CreditsAmount balance) {
if (balance.ton()) { if (balance.ton()) {
_balanceTon = balance; _tonBalance = balance;
const auto was = std::exchange(_tonLastLoaded, crl::now());
if (!was) {
_tonLoadedChanges.fire({});
}
} else { } else {
_balance = balance; _balance = balance;
updateNonLockedValue(); updateNonLockedValue();

View file

@ -19,12 +19,8 @@ public:
~Credits(); ~Credits();
void load(bool force = false); void load(bool force = false);
void apply(CreditsAmount balance);
void apply(PeerId peerId, CreditsAmount balance);
[[nodiscard]] bool loaded() const; [[nodiscard]] bool loaded() const;
[[nodiscard]] rpl::producer<bool> loadedValue() const; [[nodiscard]] rpl::producer<bool> loadedValue() const;
[[nodiscard]] CreditsAmount balance() const; [[nodiscard]] CreditsAmount balance() const;
[[nodiscard]] CreditsAmount balance(PeerId peerId) const; [[nodiscard]] CreditsAmount balance(PeerId peerId) const;
[[nodiscard]] rpl::producer<CreditsAmount> balanceValue() const; [[nodiscard]] rpl::producer<CreditsAmount> balanceValue() const;
@ -33,6 +29,15 @@ public:
[[nodiscard]] rpl::producer<> refreshedByPeerId(PeerId peerId); [[nodiscard]] rpl::producer<> refreshedByPeerId(PeerId peerId);
void tonLoad(bool force = false);
[[nodiscard]] bool tonLoaded() const;
[[nodiscard]] rpl::producer<bool> tonLoadedValue() const;
[[nodiscard]] CreditsAmount tonBalance() const;
[[nodiscard]] rpl::producer<CreditsAmount> tonBalanceValue() const;
void apply(CreditsAmount balance);
void apply(PeerId peerId, CreditsAmount balance);
[[nodiscard]] bool statsEnabled() const; [[nodiscard]] bool statsEnabled() const;
void applyCurrency(PeerId peerId, uint64 balance); void applyCurrency(PeerId peerId, uint64 balance);
@ -56,13 +61,17 @@ private:
base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances; base::flat_map<PeerId, uint64> _cachedPeerCurrencyBalances;
CreditsAmount _balance; CreditsAmount _balance;
CreditsAmount _balanceTon;
CreditsAmount _locked; CreditsAmount _locked;
rpl::variable<CreditsAmount> _nonLockedBalance; rpl::variable<CreditsAmount> _nonLockedBalance;
rpl::event_stream<> _loadedChanges; rpl::event_stream<> _loadedChanges;
crl::time _lastLoaded = 0; crl::time _lastLoaded = 0;
float64 _rate = 0.; float64 _rate = 0.;
rpl::variable<CreditsAmount> _tonBalance;
rpl::event_stream<> _tonLoadedChanges;
crl::time _tonLastLoaded = false;
mtpRequestId _tonRequestId = 0;
bool _statsEnabled = false; bool _statsEnabled = false;
rpl::event_stream<PeerId> _refreshedByPeerId; rpl::event_stream<PeerId> _refreshedByPeerId;

View file

@ -1262,7 +1262,9 @@ void ApplyChannelUpdate(
| Flag::PaidMediaAllowed | Flag::PaidMediaAllowed
| Flag::CanViewCreditsRevenue | Flag::CanViewCreditsRevenue
| Flag::StargiftsAvailable | Flag::StargiftsAvailable
| Flag::PaidMessagesAvailable; | Flag::PaidMessagesAvailable
| Flag::HasStarsPerMessage
| Flag::StarsPerMessageKnown;
channel->setFlags((channel->flags() & ~mask) channel->setFlags((channel->flags() & ~mask)
| (update.is_can_set_username() ? Flag::CanSetUsername : Flag()) | (update.is_can_set_username() ? Flag::CanSetUsername : Flag())
| (update.is_can_view_participants() | (update.is_can_view_participants()
@ -1289,7 +1291,9 @@ void ApplyChannelUpdate(
: Flag()) : Flag())
| (update.is_paid_messages_available() | (update.is_paid_messages_available()
? Flag::PaidMessagesAvailable ? Flag::PaidMessagesAvailable
: Flag())); : Flag())
| (channel->starsPerMessage() ? Flag::HasStarsPerMessage : Flag())
| Flag::StarsPerMessageKnown);
channel->setUserpicPhoto(update.vchat_photo()); channel->setUserpicPhoto(update.vchat_photo());
if (const auto migratedFrom = update.vmigrated_from_chat_id()) { if (const auto migratedFrom = update.vmigrated_from_chat_id()) {
channel->addFlags(Flag::Megagroup); channel->addFlags(Flag::Megagroup);

View file

@ -83,6 +83,8 @@ enum class ChannelDataFlag : uint64 {
MonoforumAdmin = (1ULL << 40), MonoforumAdmin = (1ULL << 40),
MonoforumDisabled = (1ULL << 41), MonoforumDisabled = (1ULL << 41),
ForumTabs = (1ULL << 42), ForumTabs = (1ULL << 42),
HasStarsPerMessage = (1ULL << 43),
StarsPerMessageKnown = (1ULL << 44),
}; };
inline constexpr bool is_flag_type(ChannelDataFlag) { return true; }; inline constexpr bool is_flag_type(ChannelDataFlag) { return true; };
using ChannelDataFlags = base::flags<ChannelDataFlag>; using ChannelDataFlags = base::flags<ChannelDataFlag>;
@ -280,6 +282,12 @@ public:
[[nodiscard]] bool paidMessagesAvailable() const { [[nodiscard]] bool paidMessagesAvailable() const {
return flags() & Flag::PaidMessagesAvailable; return flags() & Flag::PaidMessagesAvailable;
} }
[[nodiscard]] bool hasStarsPerMessage() const {
return flags() & Flag::HasStarsPerMessage;
}
[[nodiscard]] bool starsPerMessageKnown() const {
return flags() & Flag::StarsPerMessageKnown;
}
[[nodiscard]] bool useSubsectionTabs() const; [[nodiscard]] bool useSubsectionTabs() const;
[[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights( [[nodiscard]] static ChatRestrictionsInfo KickedRestrictedRights(

View file

@ -992,7 +992,14 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
? Flag::StoriesHidden ? Flag::StoriesHidden
: Flag()) : Flag())
| Flag::AutoTranslation | Flag::AutoTranslation
| Flag::Monoforum; | Flag::Monoforum
| Flag::HasStarsPerMessage
| Flag::StarsPerMessageKnown;
const auto hasStarsPerMessage
= data.vsend_paid_messages_stars().has_value();
if (!hasStarsPerMessage) {
channel->setStarsPerMessage(0);
}
const auto storiesState = minimal const auto storiesState = minimal
? std::optional<Data::Stories::PeerSourceState>() ? std::optional<Data::Stories::PeerSourceState>()
: data.is_stories_unavailable() : data.is_stories_unavailable()
@ -1034,7 +1041,13 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
? Flag::StoriesHidden ? Flag::StoriesHidden
: Flag()) : Flag())
| (data.is_autotranslation() ? Flag::AutoTranslation : Flag()) | (data.is_autotranslation() ? Flag::AutoTranslation : Flag())
| (data.is_monoforum() ? Flag::Monoforum : Flag()); | (data.is_monoforum() ? Flag::Monoforum : Flag())
| (hasStarsPerMessage
? (Flag::HasStarsPerMessage
| (channel->starsPerMessageKnown()
? Flag::StarsPerMessageKnown
: Flag()))
: Flag::StarsPerMessageKnown);
channel->setFlags((channel->flags() & ~flagsMask) | flagsSet); channel->setFlags((channel->flags() & ~flagsMask) | flagsSet);
channel->setBotVerifyDetailsIcon( channel->setBotVerifyDetailsIcon(
data.vbot_verification_icon().value_or_empty()); data.vbot_verification_icon().value_or_empty());
@ -1047,12 +1060,6 @@ not_null<PeerData*> Session::processChat(const MTPChat &data) {
} }
channel->setPhoto(data.vphoto()); channel->setPhoto(data.vphoto());
const auto hasStarsPerMessage
= data.vsend_paid_messages_stars().has_value();
if (!hasStarsPerMessage) {
channel->setStarsPerMessage(0);
}
if (const auto monoforum = data.vlinked_monoforum_id()) { if (const auto monoforum = data.vlinked_monoforum_id()) {
if (const auto linked = channelLoaded(monoforum->v)) { if (const auto linked = channelLoaded(monoforum->v)) {
channel->setMonoforumLink(linked); channel->setMonoforumLink(linked);

View file

@ -719,6 +719,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
| Flag::CanPinMessages | Flag::CanPinMessages
| Flag::VoiceMessagesForbidden | Flag::VoiceMessagesForbidden
| Flag::ReadDatesPrivate | Flag::ReadDatesPrivate
| Flag::HasStarsPerMessage
| Flag::MessageMoneyRestrictionsKnown | Flag::MessageMoneyRestrictionsKnown
| Flag::RequiresPremiumToWrite; | Flag::RequiresPremiumToWrite;
user->setFlags((user->flags() & ~mask) user->setFlags((user->flags() & ~mask)
@ -732,6 +733,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
? Flag::VoiceMessagesForbidden ? Flag::VoiceMessagesForbidden
: Flag()) : Flag())
| (update.is_read_dates_private() ? Flag::ReadDatesPrivate : Flag()) | (update.is_read_dates_private() ? Flag::ReadDatesPrivate : Flag())
| (user->starsPerMessage() ? Flag::HasStarsPerMessage : Flag())
| Flag::MessageMoneyRestrictionsKnown | Flag::MessageMoneyRestrictionsKnown
| (update.is_contact_require_premium() | (update.is_contact_require_premium()
? Flag::RequiresPremiumToWrite ? Flag::RequiresPremiumToWrite

View file

@ -24,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "data/data_session.h" #include "data/data_session.h"
#include "data/data_stories.h" #include "data/data_stories.h"
#include "data/data_user.h" #include "data/data_user.h"
#include "history/view/controls/history_view_suggest_options.h"
#include "history/history.h" #include "history/history.h"
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "main/main_account.h" #include "main/main_account.h"
@ -189,20 +190,27 @@ Data::SendErrorWithThread GetErrorForSending(
std::optional<SendPaymentDetails> ComputePaymentDetails( std::optional<SendPaymentDetails> ComputePaymentDetails(
not_null<PeerData*> peer, not_null<PeerData*> peer,
int messagesCount) { int messagesCount) {
if (const auto user = peer->asUser()) { const auto user = peer->asUser();
if (user->hasStarsPerMessage() const auto channel = user ? nullptr : peer->asChannel();
&& !user->messageMoneyRestrictionsKnown()) { const auto has = (user && user->hasStarsPerMessage())
user->updateFull(); || (channel && channel->hasStarsPerMessage());
return {}; if (!has) {
return SendPaymentDetails();
} }
} else if (const auto channel = peer->asChannel()) {
if (!channel->isFullLoaded()) { const auto known1 = peer->session().credits().loaded();
channel->updateFull(); if (!known1) {
return {};
}
}
if (!peer->session().credits().loaded()) {
peer->session().credits().load(); peer->session().credits().load();
}
const auto known2 = user
? user->messageMoneyRestrictionsKnown()
: channel->starsPerMessageKnown();
if (!known2) {
peer->updateFull();
}
if (!known1 || !known2) {
return {}; return {};
} else if (const auto perMessage = peer->starsPerMessageChecked()) { } else if (const auto perMessage = peer->starsPerMessageChecked()) {
return SendPaymentDetails{ return SendPaymentDetails{
@ -213,6 +221,21 @@ std::optional<SendPaymentDetails> ComputePaymentDetails(
return SendPaymentDetails(); return SendPaymentDetails();
} }
bool SuggestPaymentDataReady(
not_null<PeerData*> peer,
SuggestPostOptions suggest) {
if (!suggest.exists || !suggest.price()) {
return true;
} else if (suggest.ton && !peer->session().credits().tonLoaded()) {
peer->session().credits().tonLoad();
return false;
} else if (!suggest.ton && !peer->session().credits().loaded()) {
peer->session().credits().load();
return false;
}
return true;
}
object_ptr<Ui::BoxContent> MakeSendErrorBox( object_ptr<Ui::BoxContent> MakeSendErrorBox(
const Data::SendErrorWithThread &error, const Data::SendErrorWithThread &error,
bool withTitle) { bool withTitle) {
@ -250,13 +273,15 @@ void ShowSendPaidConfirm(
not_null<PeerData*> peer, not_null<PeerData*> peer,
SendPaymentDetails details, SendPaymentDetails details,
Fn<void()> confirmed, Fn<void()> confirmed,
PaidConfirmStyles styles) { PaidConfirmStyles styles,
int suggestStarsPrice) {
return ShowSendPaidConfirm( return ShowSendPaidConfirm(
navigation->uiShow(), navigation->uiShow(),
peer, peer,
details, details,
confirmed, confirmed,
styles); styles,
suggestStarsPrice);
} }
void ShowSendPaidConfirm( void ShowSendPaidConfirm(
@ -264,13 +289,15 @@ void ShowSendPaidConfirm(
not_null<PeerData*> peer, not_null<PeerData*> peer,
SendPaymentDetails details, SendPaymentDetails details,
Fn<void()> confirmed, Fn<void()> confirmed,
PaidConfirmStyles styles) { PaidConfirmStyles styles,
int suggestStarsPrice) {
ShowSendPaidConfirm( ShowSendPaidConfirm(
std::move(show), std::move(show),
std::vector<not_null<PeerData*>>{ peer }, std::vector<not_null<PeerData*>>{ peer },
details, details,
confirmed, confirmed,
styles); styles,
suggestStarsPrice);
} }
void ShowSendPaidConfirm( void ShowSendPaidConfirm(
@ -278,7 +305,8 @@ void ShowSendPaidConfirm(
const std::vector<not_null<PeerData*>> &peers, const std::vector<not_null<PeerData*>> &peers,
SendPaymentDetails details, SendPaymentDetails details,
Fn<void()> confirmed, Fn<void()> confirmed,
PaidConfirmStyles styles) { PaidConfirmStyles styles,
int suggestStarsPrice) {
Expects(!peers.empty()); Expects(!peers.empty());
const auto singlePeer = (peers.size() > 1) const auto singlePeer = (peers.size() > 1)
@ -286,7 +314,7 @@ void ShowSendPaidConfirm(
: peers.front().get(); : peers.front().get();
const auto singlePeerId = singlePeer ? singlePeer->id : PeerId(); const auto singlePeerId = singlePeer ? singlePeer->id : PeerId();
const auto check = [=] { const auto check = [=] {
const auto required = details.stars; const auto required = details.stars + suggestStarsPrice;
if (!required) { if (!required) {
return; return;
} }
@ -296,10 +324,13 @@ void ShowSendPaidConfirm(
confirmed(); confirmed();
} }
}; };
Settings::MaybeRequestBalanceIncrease( using namespace Settings;
MaybeRequestBalanceIncrease(
show, show,
required, required,
Settings::SmallBalanceForMessage{ .recipientId = singlePeerId }, (suggestStarsPrice
? SmallBalanceSource(SmallBalanceForSuggest{ singlePeerId })
: SmallBalanceForMessage{ singlePeerId }),
done); done);
}; };
auto usersOnly = true; auto usersOnly = true;
@ -388,15 +419,15 @@ void ShowSendPaidConfirm(
bool SendPaymentHelper::check( bool SendPaymentHelper::check(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Api::SendOptions options,
int messagesCount, int messagesCount,
int starsApproved,
Fn<void(int)> resend, Fn<void(int)> resend,
PaidConfirmStyles styles) { PaidConfirmStyles styles) {
return check( return check(
navigation->uiShow(), navigation->uiShow(),
peer, peer,
options,
messagesCount, messagesCount,
starsApproved,
std::move(resend), std::move(resend),
styles); styles);
} }
@ -404,17 +435,27 @@ bool SendPaymentHelper::check(
bool SendPaymentHelper::check( bool SendPaymentHelper::check(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Api::SendOptions options,
int messagesCount, int messagesCount,
int starsApproved,
Fn<void(int)> resend, Fn<void(int)> resend,
PaidConfirmStyles styles) { PaidConfirmStyles styles) {
clear(); clear();
const auto suggest = options.suggest;
const auto starsApproved = options.starsApproved;
const auto suggestPriceStars = suggest.ton
? 0
: int(base::SafeRound(suggest.price().value()));
const auto suggestPriceTon = suggest.ton
? suggest.price()
: CreditsAmount();
const auto details = ComputePaymentDetails(peer, messagesCount); const auto details = ComputePaymentDetails(peer, messagesCount);
if (!details) { const auto suggestDetails = SuggestPaymentDataReady(peer, suggest);
if (!details || !suggestDetails) {
_resend = [=] { resend(starsApproved); }; _resend = [=] { resend(starsApproved); };
if (!peer->session().credits().loaded()) { if ((!details || !suggest.ton)
&& !peer->session().credits().loaded()) {
peer->session().credits().loadedValue( peer->session().credits().loadedValue(
) | rpl::filter( ) | rpl::filter(
rpl::mappers::_1 rpl::mappers::_1
@ -425,6 +466,18 @@ bool SendPaymentHelper::check(
}, _lifetime); }, _lifetime);
} }
if ((!suggestDetails && suggest.ton)
&& !peer->session().credits().tonLoaded()) {
peer->session().credits().tonLoadedValue(
) | rpl::filter(
rpl::mappers::_1
) | rpl::take(1) | rpl::start_with_next([=] {
if (const auto callback = base::take(_resend)) {
callback();
}
}, _lifetime);
}
peer->session().changes().peerUpdates( peer->session().changes().peerUpdates(
peer, peer,
Data::PeerUpdate::Flag::FullInfo Data::PeerUpdate::Flag::FullInfo
@ -438,7 +491,32 @@ bool SendPaymentHelper::check(
} else if (const auto stars = details->stars; stars > starsApproved) { } else if (const auto stars = details->stars; stars > starsApproved) {
ShowSendPaidConfirm(show, peer, *details, [=] { ShowSendPaidConfirm(show, peer, *details, [=] {
resend(stars); resend(stars);
}, styles); }, styles, suggestPriceStars);
return false;
} else if (suggestPriceStars
&& (CreditsAmount(details->stars + suggestPriceStars)
> peer->session().credits().balance())) {
const auto peerId = peer->id;
const auto forMessages = details->stars;
const auto required = forMessages + suggestPriceStars;
const auto done = [=](Settings::SmallBalanceResult result) {
if (result == Settings::SmallBalanceResult::Success
|| result == Settings::SmallBalanceResult::Already) {
resend(forMessages);
}
};
using namespace Settings;
MaybeRequestBalanceIncrease(
show,
required,
SmallBalanceForSuggest{ peerId },
done);
return false;
}
if (suggestPriceTon
&& suggestPriceTon > peer->session().credits().tonBalance()) {
show->show(
Box(HistoryView::InsufficientTonBox, peer, suggestPriceTon));
return false; return false;
} }
return true; return true;

View file

@ -149,6 +149,10 @@ struct SendPaymentDetails {
not_null<PeerData*> peer, not_null<PeerData*> peer,
int messagesCount); int messagesCount);
[[nodiscard]] bool SuggestPaymentDataReady(
not_null<PeerData*> peer,
SuggestPostOptions suggest);
struct PaidConfirmStyles { struct PaidConfirmStyles {
const style::FlatLabel *label = nullptr; const style::FlatLabel *label = nullptr;
const style::Checkbox *checkbox = nullptr; const style::Checkbox *checkbox = nullptr;
@ -158,34 +162,37 @@ void ShowSendPaidConfirm(
not_null<PeerData*> peer, not_null<PeerData*> peer,
SendPaymentDetails details, SendPaymentDetails details,
Fn<void()> confirmed, Fn<void()> confirmed,
PaidConfirmStyles styles = {}); PaidConfirmStyles styles = {},
int suggestStarsPrice = 0);
void ShowSendPaidConfirm( void ShowSendPaidConfirm(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
SendPaymentDetails details, SendPaymentDetails details,
Fn<void()> confirmed, Fn<void()> confirmed,
PaidConfirmStyles styles = {}); PaidConfirmStyles styles = {},
int suggestStarsPrice = 0);
void ShowSendPaidConfirm( void ShowSendPaidConfirm(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
const std::vector<not_null<PeerData*>> &peers, const std::vector<not_null<PeerData*>> &peers,
SendPaymentDetails details, SendPaymentDetails details,
Fn<void()> confirmed, Fn<void()> confirmed,
PaidConfirmStyles styles = {}); PaidConfirmStyles styles = {},
int suggestStarsPrice = 0);
class SendPaymentHelper final { class SendPaymentHelper final {
public: public:
[[nodiscard]] bool check( [[nodiscard]] bool check(
not_null<Window::SessionNavigation*> navigation, not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Api::SendOptions options,
int messagesCount, int messagesCount,
int starsApproved,
Fn<void(int)> resend, Fn<void(int)> resend,
PaidConfirmStyles styles = {}); PaidConfirmStyles styles = {});
[[nodiscard]] bool check( [[nodiscard]] bool check(
std::shared_ptr<Main::SessionShow> show, std::shared_ptr<Main::SessionShow> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
Api::SendOptions options,
int messagesCount, int messagesCount,
int starsApproved,
Fn<void(int)> resend, Fn<void(int)> resend,
PaidConfirmStyles styles = {}); PaidConfirmStyles styles = {});

View file

@ -4540,7 +4540,7 @@ void HistoryWidget::saveEditMessage(Api::SendOptions options) {
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1 + int(_forwardPanel->items().size()), 1 + int(_forwardPanel->items().size()),
options.starsApproved, options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -4629,15 +4629,15 @@ void HistoryWidget::sendVoice(const VoiceToSend &data) {
copy.options.starsApproved = approved; copy.options.starsApproved = approved;
sendVoice(copy); sendVoice(copy);
}; };
auto action = prepareSendAction(data.options);
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1 + int(_forwardPanel->items().size()), 1 + int(_forwardPanel->items().size()),
data.options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
} }
auto action = prepareSendAction(data.options);
session().api().sendVoiceMessage( session().api().sendVoiceMessage(
data.bytes, data.bytes,
data.waveform, data.waveform,
@ -4678,7 +4678,7 @@ void HistoryWidget::send(Api::SendOptions options) {
message.textWithTags, message.textWithTags,
ignoreSlowmodeCountdown, ignoreSlowmodeCountdown,
withPaymentApproved, withPaymentApproved,
options.starsApproved)) { message.action.options)) {
return; return;
} }
@ -5011,14 +5011,14 @@ FullMsgId HistoryWidget::cornerButtonsCurrentId() {
bool HistoryWidget::checkSendPayment( bool HistoryWidget::checkSendPayment(
int messagesCount, int messagesCount,
int starsApproved, Api::SendOptions options,
Fn<void(int)> withPaymentApproved) { Fn<void(int)> withPaymentApproved) {
return _peer return _peer
&& _sendPayment.check( && _sendPayment.check(
controller(), controller(),
_peer, _peer,
options,
messagesCount, messagesCount,
starsApproved,
std::move(withPaymentApproved)); std::move(withPaymentApproved));
} }
@ -5209,9 +5209,11 @@ void HistoryWidget::sendBotCommand(
copy.starsApproved = approved; copy.starsApproved = approved;
sendBotCommand(request, copy); sendBotCommand(request, copy);
}; };
const auto action = prepareSendAction(options);
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -5226,7 +5228,7 @@ void HistoryWidget::sendBotCommand(
? request.command ? request.command
: Bot::WrapCommandInChat(_peer, request.command, request.context); : Bot::WrapCommandInChat(_peer, request.command, request.context);
auto message = Api::MessageToSend(prepareSendAction(options)); auto message = Api::MessageToSend(action);
message.textWithTags = { toSend, TextWithTags::Tags() }; message.textWithTags = { toSend, TextWithTags::Tags() };
message.action.replyTo = request.replyTo message.action.replyTo = request.replyTo
? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/) ? ((!_peer->isUser()/* && (botStatus == 0 || botStatus == 2)*/)
@ -6233,7 +6235,7 @@ bool HistoryWidget::showSendMessageError(
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown, bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> withPaymentApproved, Fn<void(int starsApproved)> withPaymentApproved,
int starsApproved) { Api::SendOptions options) {
if (!_canSendMessages) { if (!_canSendMessages) {
return false; return false;
} }
@ -6254,7 +6256,7 @@ bool HistoryWidget::showSendMessageError(
return withPaymentApproved return withPaymentApproved
&& !checkSendPayment( && !checkSendPayment(
request.messagesCount, request.messagesCount,
starsApproved, options,
withPaymentApproved); withPaymentApproved);
} }
@ -6369,6 +6371,11 @@ void HistoryWidget::sendingFilesConfirmed(
void HistoryWidget::sendingFilesConfirmed( void HistoryWidget::sendingFilesConfirmed(
std::shared_ptr<Ui::PreparedBundle> bundle, std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options) { Api::SendOptions options) {
const auto compress = bundle->way.sendImagesAsPhotos();
const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
auto action = prepareSendAction(options);
action.clearDraft = false;
const auto withPaymentApproved = [=](int approved) { const auto withPaymentApproved = [=](int approved) {
auto copy = options; auto copy = options;
copy.starsApproved = approved; copy.starsApproved = approved;
@ -6376,16 +6383,12 @@ void HistoryWidget::sendingFilesConfirmed(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
bundle->totalCount, bundle->totalCount,
options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
} }
const auto compress = bundle->way.sendImagesAsPhotos();
const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
auto action = prepareSendAction(options);
action.clearDraft = false;
if (bundle->sendComment) { if (bundle->sendComment) {
auto message = Api::MessageToSend(action); auto message = Api::MessageToSend(action);
message.textWithTags = base::take(bundle->caption); message.textWithTags = base::take(bundle->caption);
@ -7715,9 +7718,13 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
copy.options.starsApproved = approved; copy.options.starsApproved = approved;
sendInlineResult(copy); sendInlineResult(copy);
}; };
auto action = prepareSendAction(result.options);
action.generateLocal = true;
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
result.options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -7725,9 +7732,6 @@ void HistoryWidget::sendInlineResult(InlineBots::ResultSelected result) {
controller()->sendingAnimation().appendSending( controller()->sendingAnimation().appendSending(
result.messageSendingFrom); result.messageSendingFrom);
auto action = prepareSendAction(result.options);
action.generateLocal = true;
session().api().sendInlineResult( session().api().sendInlineResult(
result.bot, result.bot,
result.result.get(), result.result.get(),
@ -8336,7 +8340,7 @@ bool HistoryWidget::sendExistingDocument(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
messageToSend.action.options.starsApproved, messageToSend.action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return false; return false;
@ -8375,6 +8379,7 @@ bool HistoryWidget::sendExistingPhoto(
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return false; return false;
} }
const auto action = prepareSendAction(options);
const auto withPaymentApproved = [=](int approved) { const auto withPaymentApproved = [=](int approved) {
auto copy = options; auto copy = options;
@ -8383,15 +8388,13 @@ bool HistoryWidget::sendExistingPhoto(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return false; return false;
} }
Api::SendExistingPhoto( Api::SendExistingPhoto(Api::MessageToSend(action), photo);
Api::MessageToSend(prepareSendAction(options)),
photo);
hideSelectorControlsAnimated(); hideSelectorControlsAnimated();

View file

@ -371,7 +371,7 @@ private:
[[nodiscard]] bool checkSendPayment( [[nodiscard]] bool checkSendPayment(
int messagesCount, int messagesCount,
int starsApproved, Api::SendOptions options,
Fn<void(int)> withPaymentApproved); Fn<void(int)> withPaymentApproved);
void checkSuggestToGigagroup(); void checkSuggestToGigagroup();
@ -489,7 +489,7 @@ private:
const TextWithTags &textWithTags, const TextWithTags &textWithTags,
bool ignoreSlowmodeCountdown, bool ignoreSlowmodeCountdown,
Fn<void(int starsApproved)> withPaymentApproved = nullptr, Fn<void(int starsApproved)> withPaymentApproved = nullptr,
int starsApproved = 0); Api::SendOptions options = {});
void sendingFilesConfirmed( void sendingFilesConfirmed(
Ui::PreparedList &&list, Ui::PreparedList &&list,

View file

@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "base/unixtime.h" #include "base/unixtime.h"
#include "chat_helpers/compose/compose_show.h" #include "chat_helpers/compose/compose_show.h"
#include "core/ui_integration.h" #include "core/ui_integration.h"
#include "data/components/credits.h"
#include "data/stickers/data_custom_emoji.h" #include "data/stickers/data_custom_emoji.h"
#include "data/data_channel.h" #include "data/data_channel.h"
#include "data/data_media_types.h" #include "data/data_media_types.h"
@ -19,9 +20,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "history/history_item_components.h" #include "history/history_item_components.h"
#include "info/channel_statistics/earn/earn_icons.h" #include "info/channel_statistics/earn/earn_icons.h"
#include "lang/lang_keys.h" #include "lang/lang_keys.h"
#include "lottie/lottie_icon.h"
#include "main/main_app_config.h" #include "main/main_app_config.h"
#include "main/main_session.h" #include "main/main_session.h"
#include "settings/settings_common.h" #include "settings/settings_common.h"
#include "settings/settings_credits_graphics.h"
#include "ui/layers/generic_box.h" #include "ui/layers/generic_box.h"
#include "ui/text/text_utilities.h" #include "ui/text/text_utilities.h"
#include "ui/boxes/choose_date_time.h" #include "ui/boxes/choose_date_time.h"
@ -30,8 +33,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/widgets/fields/input_field.h" #include "ui/widgets/fields/input_field.h"
#include "ui/widgets/buttons.h" #include "ui/widgets/buttons.h"
#include "ui/wrap/slide_wrap.h" #include "ui/wrap/slide_wrap.h"
#include "ui/basic_click_handlers.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/rect.h"
#include "ui/vertical_list.h" #include "ui/vertical_list.h"
#include "styles/style_boxes.h"
#include "styles/style_channel_earn.h" #include "styles/style_channel_earn.h"
#include "styles/style_chat.h" #include "styles/style_chat.h"
#include "styles/style_chat_helpers.h" #include "styles/style_chat_helpers.h"
@ -81,13 +87,19 @@ void ChooseSuggestPriceBox(
std::vector<Button> buttons; std::vector<Button> buttons;
rpl::variable<TimeId> date; rpl::variable<TimeId> date;
rpl::variable<bool> ton; rpl::variable<bool> ton;
Fn<void()> save;
bool savePending = false;
bool inButton = false; bool inButton = false;
}; };
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->date = args.value.date; state->date = args.value.date;
state->ton = (args.value.ton != 0); state->ton = (args.value.ton != 0);
const auto limit = args.session->appConfig().suggestedPostStarsMax(); const auto peer = args.peer;
const auto session = &peer->session();
session->credits().load();
session->credits().tonLoad();
const auto limit = session->appConfig().suggestedPostStarsMax();
box->setTitle((args.mode == SuggestMode::New) box->setTitle((args.mode == SuggestMode::New)
? tr::lng_suggest_options_title() ? tr::lng_suggest_options_title()
@ -196,7 +208,7 @@ void ChooseSuggestPriceBox(
Ui::AddSkip(container); Ui::AddSkip(container);
const auto added = st::boxRowPadding - st::defaultSubsectionTitlePadding; const auto added = st::boxRowPadding - st::defaultSubsectionTitlePadding;
const auto manager = &args.session->data().customEmojiManager(); const auto manager = &session->data().customEmojiManager();
const auto makeIcon = [&]( const auto makeIcon = [&](
not_null<QWidget*> parent, not_null<QWidget*> parent,
TextWithEntities text) { TextWithEntities text) {
@ -205,7 +217,7 @@ void ChooseSuggestPriceBox(
rpl::single(text), rpl::single(text),
st::defaultFlatLabel, st::defaultFlatLabel,
st::defaultPopupMenu, st::defaultPopupMenu,
Core::TextContext({ .session = args.session })); Core::TextContext({ .session = session }));
}; };
const auto starsWrap = container->add( const auto starsWrap = container->add(
@ -333,7 +345,7 @@ void ChooseSuggestPriceBox(
} }
}; };
auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{ auto dateBox = Box(ChooseSuggestTimeBox, SuggestTimeBoxArgs{
.session = args.session, .session = session,
.done = done, .done = done,
.value = state->date.current(), .value = state->date.current(),
.mode = args.mode, .mode = args.mode,
@ -344,8 +356,8 @@ void ChooseSuggestPriceBox(
Ui::AddSkip(container); Ui::AddSkip(container);
Ui::AddDividerText(container, tr::lng_suggest_options_date_about()); Ui::AddDividerText(container, tr::lng_suggest_options_date_about());
AssertIsDebug()//tr::lng_suggest_options_offer AssertIsDebug();//tr::lng_suggest_options_offer
const auto save = [=] { state->save = [=] {
auto nanos = int64(); auto nanos = int64();
if (state->ton.current()) { if (state->ton.current()) {
const auto now = Ui::ParseTonAmountString( const auto now = Ui::ParseTonAmountString(
@ -363,22 +375,74 @@ void ChooseSuggestPriceBox(
} }
nanos = now * Ui::kNanosInOne; nanos = now * Ui::kNanosInOne;
} }
const auto ton = uint32(state->ton.current() ? 1 : 0);
const auto value = CreditsAmount( const auto value = CreditsAmount(
nanos / Ui::kNanosInOne, nanos / Ui::kNanosInOne,
nanos % Ui::kNanosInOne); nanos % Ui::kNanosInOne,
ton ? CreditsType::Ton : CreditsType::Stars);
const auto credits = &session->credits();
if (ton) {
if (!credits->tonLoaded()) {
state->savePending = true;
return;
} else if (credits->tonBalance() < value) {
box->uiShow()->show(
Box(InsufficientTonBox, peer, value));
return;
}
} else {
if (!credits->loaded()) {
state->savePending = true;
return;
}
using namespace Settings;
const auto required = peer->starsPerMessageChecked()
+ int(base::SafeRound(value.value()));
const auto done = [=](SmallBalanceResult result) {
if (result == SmallBalanceResult::Success
|| result == SmallBalanceResult::Already) {
state->save();
}
};
MaybeRequestBalanceIncrease(
Main::MakeSessionShow(box->uiShow(), session),
required,
SmallBalanceForSuggest{ peer->id },
done);
return;
}
state->save = nullptr;
args.done({ args.done({
.exists = true, .exists = true,
.priceWhole = uint32(value.whole()), .priceWhole = uint32(value.whole()),
.priceNano = uint32(value.nano()), .priceNano = uint32(value.nano()),
.ton = uint32(state->ton.current() ? 1 : 0), .ton = ton,
.date = state->date.current(), .date = state->date.current(),
}); });
}; };
QObject::connect(starsField, &Ui::NumberInput::submitted, box, save); const auto credits = &session->credits();
tonField->submits() | rpl::start_with_next(save, tonField->lifetime()); rpl::combine(
credits->tonBalanceValue(),
credits->balanceValue()
) | rpl::filter([=] {
return state->savePending;
}) | rpl::start_with_next([=] {
state->savePending = false;
if (const auto onstack = state->save) {
onstack();
}
}, box->lifetime());
box->addButton(tr::lng_settings_save(), save); QObject::connect(
starsField,
&Ui::NumberInput::submitted,
box,
state->save);
tonField->submits(
) | rpl::start_with_next(state->save, tonField->lifetime());
box->addButton(tr::lng_settings_save(), state->save);
box->addButton(tr::lng_cancel(), [=] { box->addButton(tr::lng_cancel(), [=] {
box->closeBox(); box->closeBox();
}); });
@ -402,6 +466,52 @@ bool CanAddOfferToMessage(not_null<HistoryItem*> item) {
history->owner().history(broadcast)).has_value(); history->owner().history(broadcast)).has_value();
} }
void InsufficientTonBox(
not_null<Ui::GenericBox*> box,
not_null<PeerData*> peer,
CreditsAmount required) {
auto icon = Settings::CreateLottieIcon(
box->verticalLayout(),
{
.name = u"diamond"_q,
.sizeOverride = Size(st::changePhoneIconSize),
},
{});
box->setShowFinishedCallback([animate = std::move(icon.animate)] {
animate(anim::repeat::loop);
});
box->addRow(std::move(icon.widget), st::lowTonIconPadding);
const auto add = required - peer->session().credits().tonBalance();
const auto nano = add.whole() * Ui::kNanosInOne + add.nano();
const auto amount = Ui::FormatTonAmount(nano).full;
box->addRow(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_suggest_low_ton_title(tr::now, lt_amount, amount),
st::boxTitle)),
st::boxRowPadding + st::lowTonTitlePadding);
const auto label = box->addRow(
object_ptr<Ui::FlatLabel>(
box,
tr::lng_suggest_low_ton_text(
lt_channel,
rpl::single(Ui::Text::Bold(peer->name())),
Ui::Text::RichLangValue),
st::lowTonText),
st::boxRowPadding + st::lowTonTextPadding);
label->setTryMakeSimilarLines(true);
label->resizeToWidth(
st::boxWidth - st::boxRowPadding.left() - st::boxRowPadding.right());
box->addButton(tr::lng_suggest_low_ton_fragment(), [=] {
UrlClickHandler::Open(tr::lng_suggest_low_ton_fragment_url(tr::now));
});
box->addButton(tr::lng_cancel(), [=] {
box->closeBox();
});
}
SuggestOptions::SuggestOptions( SuggestOptions::SuggestOptions(
std::shared_ptr<ChatHelpers::Show> show, std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> peer, not_null<PeerData*> peer,
@ -458,7 +568,7 @@ void SuggestOptions::edit() {
} }
}; };
*weak = _show->show(Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{ *weak = _show->show(Box(ChooseSuggestPriceBox, SuggestPriceBoxArgs{
.session = &_peer->session(), .peer = _peer,
.done = apply, .done = apply,
.value = _values, .value = _values,
})); }));

View file

@ -44,7 +44,7 @@ void ChooseSuggestTimeBox(
SuggestTimeBoxArgs &&args); SuggestTimeBoxArgs &&args);
struct SuggestPriceBoxArgs { struct SuggestPriceBoxArgs {
not_null<Main::Session*> session; not_null<PeerData*> peer;
bool updating = false; bool updating = false;
Fn<void(SuggestPostOptions)> done; Fn<void(SuggestPostOptions)> done;
SuggestPostOptions value; SuggestPostOptions value;
@ -58,6 +58,11 @@ void ChooseSuggestPriceBox(
[[nodiscard]] bool CanAddOfferToMessage(not_null<HistoryItem*> item); [[nodiscard]] bool CanAddOfferToMessage(not_null<HistoryItem*> item);
void InsufficientTonBox(
not_null<Ui::GenericBox*> box,
not_null<PeerData*> peer,
CreditsAmount required);
class SuggestOptions final { class SuggestOptions final {
public: public:
SuggestOptions( SuggestOptions(

View file

@ -1195,13 +1195,13 @@ void ChatWidget::sendingFilesConfirmed(
bool ChatWidget::checkSendPayment( bool ChatWidget::checkSendPayment(
int messagesCount, int messagesCount,
int starsApproved, Api::SendOptions options,
Fn<void(int)> withPaymentApproved) { Fn<void(int)> withPaymentApproved) {
return _sendPayment.check( return _sendPayment.check(
controller(), controller(),
_peer, _peer,
options,
messagesCount, messagesCount,
starsApproved,
std::move(withPaymentApproved)); std::move(withPaymentApproved));
} }
@ -1215,7 +1215,7 @@ void ChatWidget::sendingFilesConfirmed(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
bundle->totalCount, bundle->totalCount,
options.starsApproved, options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -1386,7 +1386,7 @@ void ChatWidget::sendVoice(const ComposeControls::VoiceToSend &data) {
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
data.options.starsApproved, data.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -1438,7 +1438,7 @@ void ChatWidget::send(Api::SendOptions options) {
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
request.messagesCount, request.messagesCount,
options.starsApproved, options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -1669,7 +1669,7 @@ bool ChatWidget::sendExistingDocument(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
messageToSend.action.options.starsApproved, messageToSend.action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return false; return false;
@ -1709,7 +1709,7 @@ bool ChatWidget::sendExistingPhoto(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
options.starsApproved, options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return false; return false;
@ -1752,7 +1752,7 @@ void ChatWidget::sendInlineResult(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
options.starsApproved, options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -3158,7 +3158,7 @@ void ChatWidget::sendBotCommandWithOptions(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
options.starsApproved, options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;

View file

@ -227,7 +227,7 @@ private:
[[nodiscard]] bool checkSendPayment( [[nodiscard]] bool checkSendPayment(
int messagesCount, int messagesCount,
int starsApproved, Api::SendOptions options,
Fn<void(int)> withPaymentApproved); Fn<void(int)> withPaymentApproved);
void markLoaded(); void markLoaded();

View file

@ -1910,8 +1910,8 @@ void WebViewInstance::botSendPreparedMessage(
const auto checked = state->sendPayment.check( const auto checked = state->sendPayment.check(
uiShow(), uiShow(),
strong->peer(), strong->peer(),
options,
1, 1,
options.starsApproved,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -2545,8 +2545,8 @@ void ChooseAndSendLocation(
const auto checked = state->sendPayment.check( const auto checked = state->sendPayment.check(
strong, strong,
action.history->peer, action.history->peer,
action.options,
1, 1,
action.options.starsApproved,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;

View file

@ -257,7 +257,7 @@ bool ReplyArea::send(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
request.messagesCount, request.messagesCount,
message.action.options.starsApproved, message.action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return false; return false;
@ -273,7 +273,7 @@ bool ReplyArea::send(
bool ReplyArea::checkSendPayment( bool ReplyArea::checkSendPayment(
int messagesCount, int messagesCount,
int starsApproved, Api::SendOptions options,
Fn<void(int)> withPaymentApproved) { Fn<void(int)> withPaymentApproved) {
const auto st1 = ::Settings::DarkCreditsEntryBoxStyle(); const auto st1 = ::Settings::DarkCreditsEntryBoxStyle();
const auto st2 = st1.shareBox.get(); const auto st2 = st1.shareBox.get();
@ -282,8 +282,8 @@ bool ReplyArea::checkSendPayment(
&& _sendPayment.check( && _sendPayment.check(
_controller->uiShow(), _controller->uiShow(),
_data.peer, _data.peer,
options,
messagesCount, messagesCount,
starsApproved,
std::move(withPaymentApproved), std::move(withPaymentApproved),
{ {
.label = st3 ? st3->chooseDateTimeArgs.labelStyle : nullptr, .label = st3 ? st3->chooseDateTimeArgs.labelStyle : nullptr,
@ -292,6 +292,8 @@ bool ReplyArea::checkSendPayment(
} }
void ReplyArea::sendVoice(const VoiceToSend &data) { void ReplyArea::sendVoice(const VoiceToSend &data) {
auto action = prepareSendAction(data.options);
const auto withPaymentApproved = [=](int approved) { const auto withPaymentApproved = [=](int approved) {
auto copy = data; auto copy = data;
copy.options.starsApproved = approved; copy.options.starsApproved = approved;
@ -299,13 +301,12 @@ void ReplyArea::sendVoice(const VoiceToSend &data) {
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
data.options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
} }
auto action = prepareSendAction(data.options);
session().api().sendVoiceMessage( session().api().sendVoiceMessage(
data.bytes, data.bytes,
data.waveform, data.waveform,
@ -341,7 +342,7 @@ bool ReplyArea::sendExistingDocument(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
messageToSend.action.options.starsApproved, messageToSend.action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return false; return false;
@ -373,6 +374,8 @@ bool ReplyArea::sendExistingPhoto(
} else if (showSlowmodeError()) { } else if (showSlowmodeError()) {
return false; return false;
} }
const auto action = prepareSendAction(options);
const auto withPaymentApproved = [=](int approved) { const auto withPaymentApproved = [=](int approved) {
auto copy = options; auto copy = options;
copy.starsApproved = approved; copy.starsApproved = approved;
@ -380,15 +383,13 @@ bool ReplyArea::sendExistingPhoto(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return false; return false;
} }
Api::SendExistingPhoto( Api::SendExistingPhoto(Api::MessageToSend(action), photo);
Api::MessageToSend(prepareSendAction(options)),
photo);
_controls->cancelReplyMessage(); _controls->cancelReplyMessage();
finishSending(); finishSending();
@ -411,6 +412,9 @@ void ReplyArea::sendInlineResult(
not_null<UserData*> bot, not_null<UserData*> bot,
Api::SendOptions options, Api::SendOptions options,
std::optional<MsgId> localMessageId) { std::optional<MsgId> localMessageId) {
auto action = prepareSendAction(options);
action.generateLocal = true;
const auto withPaymentApproved = [=](int approved) { const auto withPaymentApproved = [=](int approved) {
auto copy = options; auto copy = options;
copy.starsApproved = approved; copy.starsApproved = approved;
@ -418,14 +422,12 @@ void ReplyArea::sendInlineResult(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
1, 1,
options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
} }
auto action = prepareSendAction(options);
action.generateLocal = true;
session().api().sendInlineResult( session().api().sendInlineResult(
bot, bot,
result.get(), result.get(),
@ -677,6 +679,11 @@ void ReplyArea::sendingFilesConfirmed(
void ReplyArea::sendingFilesConfirmed( void ReplyArea::sendingFilesConfirmed(
std::shared_ptr<Ui::PreparedBundle> bundle, std::shared_ptr<Ui::PreparedBundle> bundle,
Api::SendOptions options) { Api::SendOptions options) {
const auto compress = bundle->way.sendImagesAsPhotos();
const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
auto action = prepareSendAction(options);
action.clearDraft = false;
const auto withPaymentApproved = [=](int approved) { const auto withPaymentApproved = [=](int approved) {
auto copy = options; auto copy = options;
copy.starsApproved = approved; copy.starsApproved = approved;
@ -684,16 +691,12 @@ void ReplyArea::sendingFilesConfirmed(
}; };
const auto checked = checkSendPayment( const auto checked = checkSendPayment(
bundle->totalCount, bundle->totalCount,
options.starsApproved, action.options,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
} }
const auto compress = bundle->way.sendImagesAsPhotos();
const auto type = compress ? SendMediaType::Photo : SendMediaType::File;
auto action = prepareSendAction(options);
action.clearDraft = false;
if (bundle->sendComment) { if (bundle->sendComment) {
auto message = Api::MessageToSend(action); auto message = Api::MessageToSend(action);
message.textWithTags = base::take(bundle->caption); message.textWithTags = base::take(bundle->caption);

View file

@ -96,7 +96,7 @@ private:
[[nodiscard]] bool checkSendPayment( [[nodiscard]] bool checkSendPayment(
int messagesCount, int messagesCount,
int starsApproved, Api::SendOptions options,
Fn<void(int)> withPaymentApproved); Fn<void(int)> withPaymentApproved);
void uploadFile(const QByteArray &fileContent, SendMediaType type); void uploadFile(const QByteArray &fileContent, SendMediaType type);

View file

@ -2396,6 +2396,10 @@ void SmallBalanceBox(
return value.recipientId return value.recipientId
? owner->peer(value.recipientId)->shortName() ? owner->peer(value.recipientId)->shortName()
: QString(); : QString();
}, [&](SmallBalanceForSuggest value) {
return value.recipientId
? owner->peer(value.recipientId)->shortName()
: QString();
}); });
auto needed = show->session().credits().balanceValue( auto needed = show->session().credits().balanceValue(
@ -2442,6 +2446,11 @@ void SmallBalanceBox(
lt_user, lt_user,
rpl::single(Ui::Text::Bold(name)), rpl::single(Ui::Text::Bold(name)),
Ui::Text::RichLangValue)) Ui::Text::RichLangValue))
: v::is<SmallBalanceForSuggest>(source)
? tr::lng_credits_small_balance_for_suggest(
lt_channel,
rpl::single(Ui::Text::Bold(name)),
Ui::Text::RichLangValue)
: name.isEmpty() : name.isEmpty()
? tr::lng_credits_small_balance_fallback( ? tr::lng_credits_small_balance_fallback(
Ui::Text::RichLangValue) Ui::Text::RichLangValue)

View file

@ -233,13 +233,17 @@ struct SmallBalanceStarGift {
struct SmallBalanceForMessage { struct SmallBalanceForMessage {
PeerId recipientId; PeerId recipientId;
}; };
struct SmallBalanceForSuggest {
PeerId recipientId;
};
struct SmallBalanceSource : std::variant< struct SmallBalanceSource : std::variant<
SmallBalanceBot, SmallBalanceBot,
SmallBalanceReaction, SmallBalanceReaction,
SmallBalanceSubscription, SmallBalanceSubscription,
SmallBalanceDeepLink, SmallBalanceDeepLink,
SmallBalanceStarGift, SmallBalanceStarGift,
SmallBalanceForMessage> { SmallBalanceForMessage,
SmallBalanceForSuggest> {
using variant::variant; using variant::variant;
}; };

View file

@ -1370,3 +1370,11 @@ tonInput: InputField(defaultInputField) {
starsFieldIconPosition: point(0px, 10px); starsFieldIconPosition: point(0px, 10px);
tonFieldIconSize: 16px; tonFieldIconSize: 16px;
tonFieldIconPosition: point(2px, 9px); tonFieldIconPosition: point(2px, 9px);
lowTonIconPadding: margins(12px, 20px, 12px, 0px);
lowTonTitlePadding: margins(0px, 12px, 0px, 12px);
lowTonTextPadding: margins(0px, 0px, 0px, 8px);
lowTonText: FlatLabel(defaultFlatLabel) {
minWidth: 100px;
align: align(top);
}

View file

@ -1798,6 +1798,10 @@ void PeerMenuShareContactBox(
state->share = nullptr; state->share = nullptr;
return; return;
} }
auto action = Api::SendAction(strong, options);
action.clearDraft = false;
const auto withPaymentApproved = [=](int stars) { const auto withPaymentApproved = [=](int stars) {
if (const auto onstack = state->share) { if (const auto onstack = state->share) {
auto copy = options; auto copy = options;
@ -1808,8 +1812,8 @@ void PeerMenuShareContactBox(
const auto checked = state->sendPayment.check( const auto checked = state->sendPayment.check(
navigation, navigation,
peer, peer,
action.options,
1, 1,
options.starsApproved,
withPaymentApproved); withPaymentApproved);
if (!checked) { if (!checked) {
return; return;
@ -1818,8 +1822,6 @@ void PeerMenuShareContactBox(
strong, strong,
ShowAtTheEndMsgId, ShowAtTheEndMsgId,
Window::SectionShow::Way::ClearStack); Window::SectionShow::Way::ClearStack);
auto action = Api::SendAction(strong, options);
action.clearDraft = false;
strong->session().api().shareContact(user, action); strong->session().api().shareContact(user, action);
state->share = nullptr; state->share = nullptr;
}; };
@ -1887,6 +1889,12 @@ void PeerMenuCreatePoll(
const auto weak = QPointer<CreatePollBox>(box); const auto weak = QPointer<CreatePollBox>(box);
const auto state = box->lifetime().make_state<State>(); const auto state = box->lifetime().make_state<State>();
state->create = [=](const CreatePollBox::Result &result) { state->create = [=](const CreatePollBox::Result &result) {
auto action = Api::SendAction(
peer->owner().history(peer),
result.options);
action.replyTo = replyTo;
action.options.suggest = suggest;
const auto withPaymentApproved = crl::guard(weak, [=](int stars) { const auto withPaymentApproved = crl::guard(weak, [=](int stars) {
if (const auto onstack = state->create) { if (const auto onstack = state->create) {
auto copy = result; auto copy = result;
@ -1897,17 +1905,13 @@ void PeerMenuCreatePoll(
const auto checked = state->sendPayment.check( const auto checked = state->sendPayment.check(
controller, controller,
peer, peer,
action.options,
1, 1,
result.options.starsApproved,
withPaymentApproved); withPaymentApproved);
if (!checked || std::exchange(state->lock, true)) { if (!checked || std::exchange(state->lock, true)) {
return; return;
} }
auto action = Api::SendAction(
peer->owner().history(peer),
result.options);
action.replyTo = replyTo;
action.options.suggest = suggest;
const auto local = action.history->localDraft( const auto local = action.history->localDraft(
replyTo.topicRootId, replyTo.topicRootId,
replyTo.monoforumPeerId); replyTo.monoforumPeerId);
@ -2002,20 +2006,22 @@ void PeerMenuCreateTodoList(
onstack(copy); onstack(copy);
} }
}); });
const auto checked = state->sendPayment.check(
controller,
peer,
1,
result.options.starsApproved,
withPaymentApproved);
if (!checked || std::exchange(state->lock, true)) {
return;
}
auto action = Api::SendAction( auto action = Api::SendAction(
peer->owner().history(peer), peer->owner().history(peer),
result.options); result.options);
action.replyTo = replyTo; action.replyTo = replyTo;
action.options.suggest = suggest; action.options.suggest = suggest;
const auto checked = state->sendPayment.check(
controller,
peer,
action.options,
1,
withPaymentApproved);
if (!checked || std::exchange(state->lock, true)) {
return;
}
const auto local = action.history->localDraft( const auto local = action.history->localDraft(
replyTo.topicRootId, replyTo.topicRootId,
replyTo.monoforumPeerId); replyTo.monoforumPeerId);