Allow placing bid with message / anonymous.

This commit is contained in:
John Preston
2025-11-14 18:57:56 +04:00
parent ae6b0dd753
commit 1320e7ac2f
7 changed files with 567 additions and 423 deletions
+7
View File
@@ -4018,6 +4018,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_auction_bought#one" = "{count} {emoji} item bought {arrow}";
"lng_auction_bought#other" = "{count} {emoji} items bought {arrow}";
"lng_auction_join_button" = "Join Auction";
"lng_auction_join_bid" = "Place a Bid";
"lng_auction_join_time_left" = "{time} left";
"lng_auction_join_time_medium" = "{hours} h {minutes} m";
"lng_auction_join_time_small" = "{minutes} m";
@@ -4070,6 +4071,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_auction_bought_bid" = "Accepted Bid";
"lng_auction_bought_round" = "Round #{n}";
"lng_auction_bought_top" = "Top {n}";
"lng_auction_change_title" = "Change Recipient";
"lng_auction_change_button" = "Change";
"lng_auction_change_already" = "You've already placed a bid on this gift for {name}.";
"lng_auction_change_to" = "Do you want to raise your bid and change the recipient to {name}?";
"lng_auction_change_already_me" = "You've already placed a bid on this gift for yourself.";
"lng_auction_change_to_me" = "Do you want to raise your bid and change the recipient to yourself?";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected account.";
@@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#include "boxes/star_gift_auction_box.h"
#include "api/api_text_entities.h"
#include "base/unixtime.h"
#include "boxes/send_credits_box.h" // CreditsEmojiSmall
#include "boxes/star_gift_box.h"
@@ -26,6 +27,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "payments/payments_checkout_process.h"
#include "settings/settings_credits_graphics.h"
#include "storage/storage_account.h"
#include "ui/boxes/confirm_box.h"
#include "ui/controls/button_labels.h"
#include "ui/controls/feature_list.h"
#include "ui/controls/table_rows.h"
@@ -71,7 +73,7 @@ struct BidRowData {
BidType type = BidType::Setting;
friend inline bool operator==(
const BidRowData &,
const BidRowData &,
const BidRowData &) = default;
};
@@ -207,8 +209,8 @@ struct BidRowData {
crl::on_main(was, [=] { delete was; });
}
state->userpic = std::make_unique<UserpicButton>(
raw,
state->user,
raw,
state->user,
st::auctionBidUserpic);
state->userpic->show();
state->userpic->moveToLeft(userpicLeft, 0);
@@ -241,31 +243,34 @@ struct BidRowData {
return result;
}
struct AuctionBidBoxArgs {
not_null<PeerData*> peer;
std::shared_ptr<ChatHelpers::Show> show;
rpl::producer<Data::GiftAuctionState> state;
};
void PlaceAuctionBid(
std::shared_ptr<ChatHelpers::Show> show,
not_null<PeerData*> to,
int64 amount,
const Data::GiftAuctionState &state,
std::unique_ptr<Info::PeerGifts::GiftSendDetails> details,
Fn<void(Payments::CheckoutResult)> done) {
auto paymentDone = [=](
Payments::CheckoutResult result,
const MTPUpdates *updates) {
done(result);
};
const auto passDetails = (details || !state.my.bid);
const auto hideName = passDetails && details && details->anonymous;
const auto text = details ? details->text : TextWithEntities();
using Flag = MTPDinputInvoiceStarGiftAuctionBid::Flag;
const auto invoice = MTP_inputInvoiceStarGiftAuctionBid(
MTP_flags((state.my.bid ? Flag::f_update_bid : Flag())
| (state.my.bid ? Flag() : Flag::f_peer)),
state.my.bid ? MTP_inputPeerEmpty() : to->input,
| (passDetails ? Flag::f_peer : Flag())
| (passDetails ? Flag::f_message : Flag())
| (hideName ? Flag::f_hide_name : Flag())),
passDetails ? to->input : MTP_inputPeerEmpty(),
MTP_long(state.gift->id),
MTP_long(amount),
MTPTextWithEntities());
MTP_textWithEntities(
MTP_string(text.text),
Api::EntitiesToMTP(&to->session(), text.entities)));
RequestOurForm(show, invoice, [=](
uint64 formId,
CreditsAmount price,
@@ -334,7 +339,7 @@ object_ptr<RpWidget> MakeAuctionInfoBlocks(
{
.title = std::move(bidTitle),
.subtext = tr::lng_auction_bid_minimal(
lt_count,
lt_count,
std::move(minimal)),
},
{
@@ -349,9 +354,9 @@ object_ptr<RpWidget> MakeAuctionInfoBlocks(
}
void AddBidPlaces(
not_null<GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<Data::GiftAuctionState> value,
not_null<GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
rpl::producer<Data::GiftAuctionState> value,
rpl::producer<int> chosen) {
struct My {
BidType type;
@@ -408,8 +413,8 @@ void AddBidPlaces(
for (auto i = begin(levels), e = end(levels); i != e; ++i) {
if (i->amount < chosen
|| (!setting
&& i->amount == chosen
|| (!setting
&& i->amount == chosen
&& i->date > value.my.date)) {
top.push_back({ show->session().user(), chosen });
for (auto j = i; j != e; ++j) {
@@ -446,7 +451,7 @@ void AddBidPlaces(
return BidRowData{ user, stars, place, my.type };
});
box->addRow(MakeBidRow(box, show, std::move(bid)));
AddSubsectionTitle(
box->verticalLayout(),
tr::lng_auction_bid_winners_title(),
@@ -468,6 +473,7 @@ void AddBidPlaces(
void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
const auto weak = base::make_weak(box);
using namespace Info::PeerGifts;
struct State {
State(rpl::producer<Data::GiftAuctionState> value)
: value(std::move(value)) {
@@ -492,6 +498,9 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
state->chosen = chosen;
const auto show = args.show;
const auto details = args.details
? *args.details
: std::optional<GiftSendDetails>();
const auto save = [=, peer = args.peer](int amount) {
const auto &current = state->value.current();
if (amount > current.my.bid) {
@@ -513,7 +522,10 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
});
}
};
PlaceAuctionBid(show, peer, amount, now, done);
auto owned = details
? std::make_unique<GiftSendDetails>(*details)
: nullptr;
PlaceAuctionBid(show, peer, amount, now, std::move(owned), done);
}
if (const auto strong = weak.get()) {
strong->closeBox();
@@ -562,7 +574,7 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
box->addRow(
MakeAuctionInfoBlocks(box, &show->session(), state->value.value()),
st::boxRowPadding + QMargins(0, skip / 2, 0, skip));
AddBidPlaces(box, show, state->value.value(), state->chosen.value());
AddSkip(content);
@@ -592,16 +604,11 @@ void AuctionBidBox(not_null<GenericBox*> box, AuctionBidBoxArgs &&args) {
}) | rpl::flatten_latest());
AddStarSelectBalance(
box,
&show->session(),
box,
&show->session(),
show->session().credits().balanceValue());
}
[[nodiscard]] object_ptr<BoxContent> MakeAuctionBidBox(
AuctionBidBoxArgs &&args) {
return Box(AuctionBidBox, std::move(args));
}
[[nodiscard]] object_ptr<RpWidget> MakeAveragePriceValue(
not_null<TableLayout*> table,
std::shared_ptr<TableRowTooltipData> tooltip,
@@ -740,8 +747,8 @@ void AuctionAboutBox(
});
box->addRow(
Calls::Group::MakeRoundActiveLogo(
box,
st::auctionAboutLogo,
box,
st::auctionAboutLogo,
st::auctionAboutLogoPadding),
st::boxRowPadding + st::confcallLinkHeaderIconPadding);
@@ -765,8 +772,8 @@ void AuctionAboutBox(
{
st::menuIconRatingGifts,
tr::lng_auction_about_top_title(
tr::now,
lt_count,
tr::now,
lt_count,
giftsPerRound),
tr::lng_auction_about_top_about(
tr::now,
@@ -774,14 +781,14 @@ void AuctionAboutBox(
giftsPerRound,
lt_rounds,
tr::lng_auction_about_top_rounds(
tr::now,
lt_count,
tr::now,
lt_count,
rounds,
tr::rich),
lt_bidders,
tr::lng_auction_about_top_bidders(
tr::now,
lt_count,
tr::now,
lt_count,
giftsPerRound,
tr::rich),
tr::rich),
@@ -811,7 +818,7 @@ void AuctionAboutBox(
}
});
box->addButton(
rpl::single(QString()),
rpl::single(QString()),
understood ? [=] { understood(close); } : close
)->setText(rpl::single(Text::IconEmoji(
&st::infoStarsUnderstood
@@ -819,8 +826,8 @@ void AuctionAboutBox(
}
void AuctionGotGiftsBox(
not_null<GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
const Data::StarGift &gift,
std::vector<Data::GiftAcquired> list) {
Expects(!list.empty());
@@ -830,7 +837,7 @@ void AuctionGotGiftsBox(
tr::lng_auction_bought_title(lt_count, rpl::single(count * 1.)));
box->setWidth(st::boxWideWidth);
box->setMaxHeight(st::boxWideWidth * 2);
auto helper = Text::CustomEmojiHelper(Core::TextContext({
.session = &show->session(),
}));
@@ -858,7 +865,7 @@ void AuctionGotGiftsBox(
// Title "Round #n"
addFullWidth(tr::lng_auction_bought_round(
lt_n,
lt_n,
rpl::single(tr::marked(QString::number(entry.round))),
tr::bold
) | rpl::map([=](const TextWithEntities &text) {
@@ -888,9 +895,9 @@ void AuctionGotGiftsBox(
helper.paletteDependent(
Text::CustomEmojiTextBadge(
tr::lng_auction_bought_top(
tr::now,
lt_n,
QString::number(entry.position)).toUpper(),
tr::now,
lt_n,
QString::number(entry.position)).toUpper(),
st::defaultTableSmallButton)));
AddTableRow(
table,
@@ -907,7 +914,7 @@ void AuctionGotGiftsBox(
void AuctionInfoBox(
not_null<GenericBox*> box,
std::shared_ptr<ChatHelpers::Show> show,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
rpl::producer<Data::GiftAuctionState> value) {
using namespace Info::PeerGifts;
@@ -924,6 +931,7 @@ void AuctionInfoBox(
std::vector<Data::GiftAcquired> acquired;
bool acquiredRequested = false;
};
const auto show = window->uiShow();
const auto state = box->lifetime().make_state<State>(&show->session());
state->value = std::move(value);
state->minutesLeft = MinutesLeftTillValue(
@@ -1027,15 +1035,16 @@ void AuctionInfoBox(
return false;
} else if (state->acquired.size() == value.my.gotCount) {
show->show(Box(
AuctionGotGiftsBox,
show,
AuctionGotGiftsBox,
show,
gift,
state->acquired));
} else if (!state->acquiredRequested) {
state->acquiredRequested = true;
show->session().giftAuctions().requestAcquired(
value.gift->id,
crl::guard(box, [=](std::vector<Data::GiftAcquired> result) {
crl::guard(box, [=](
std::vector<Data::GiftAcquired> result) {
state->acquiredRequested = false;
state->acquired = std::move(result);
if (!state->acquired.empty()) {
@@ -1057,61 +1066,23 @@ void AuctionInfoBox(
box->closeBox();
return;
}
const auto bidBox = show->show(MakeAuctionBidBox({
.peer = peer,
.show = show,
.state = state->value.value(),
}));
bidBox->boxClosing(
const auto sendBox = show->show(Box(
SendGiftBox,
window,
peer,
nullptr,
GiftTypeStars{ .info = *state->value.current().gift },
state->value.value()));
sendBox->boxClosing(
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
});
auto buttonTitle = rpl::combine(
state->value.value(),
state->minutesLeft.value()
) | rpl::map([=](const Data::GiftAuctionState &state, int minutes) {
return (state.finished() || minutes <= 0)
? tr::lng_box_ok(tr::marked)
: tr::lng_auction_join_button(tr::marked);
}) | rpl::flatten_latest();
auto buttonSubtitle = rpl::combine(
state->value.value(),
state->minutesLeft.value()
) | rpl::map([=](const Data::GiftAuctionState &state, int minutes) {
if (state.finished() || minutes <= 0) {
return rpl::single(TextWithEntities());
}
const auto hours = (minutes / 60);
minutes -= (hours * 60);
auto value = [](int count) {
return rpl::single(tr::marked(QString::number(count)));
};
return tr::lng_auction_join_time_left(
lt_time,
(hours
? tr::lng_auction_join_time_medium(
lt_hours,
value(hours),
lt_minutes,
value(minutes),
tr::marked)
: tr::lng_auction_join_time_small(
lt_minutes,
value(minutes),
tr::marked)),
tr::marked);
}) | rpl::flatten_latest();
SetButtonTwoLabels(
SetAuctionButtonCountdownText(
button,
std::move(buttonTitle),
std::move(buttonSubtitle),
st::resaleButtonTitle,
st::resaleButtonSubtitle);
AuctionButtonCountdownType::Join,
state->value.value());
rpl::combine(
state->value.value(),
@@ -1135,7 +1106,7 @@ void AuctionInfoBox(
}
base::weak_qptr<BoxContent> ChooseAndShowAuctionBox(
std::shared_ptr<ChatHelpers::Show> show,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
std::shared_ptr<rpl::variable<Data::GiftAuctionState>> state,
Fn<void()> boxClosed) {
@@ -1143,28 +1114,65 @@ base::weak_qptr<BoxContent> ChooseAndShowAuctionBox(
const auto &now = state->current();
const auto finished = now.finished()
|| (now.endDate <= base::unixtime::now());
const auto showBidBox = now.my.bid && !finished;
const auto showBidBox = now.my.bid
&& !finished
&& (!now.my.to || now.my.to == peer);
const auto showChangeRecipient = !showBidBox && now.my.bid && !finished;
const auto showInfoBox = !showBidBox
&& !showChangeRecipient
&& (local->readPref<bool>(kAuctionAboutShownPref) || finished);
auto box = base::weak_qptr<BoxContent>();
if (showBidBox) {
box = show->show(MakeAuctionBidBox({
box = window->show(MakeAuctionBidBox({
.peer = peer,
.show = show,
.show = window->uiShow(),
.state = state->value(),
}));
} else if (showChangeRecipient) {
const auto change = [=](Fn<void()> close) {
const auto sendBox = window->show(Box(
SendGiftBox,
window,
peer,
nullptr,
Info::PeerGifts::GiftTypeStars{
.info = *now.gift,
},
state->value()));
sendBox->boxClosing(
) | rpl::start_with_next(close, sendBox->lifetime());
};
const auto text = (now.my.to->isSelf()
? tr::lng_auction_change_already_me(tr::now, tr::rich)
: tr::lng_auction_change_already(
tr::now,
lt_name,
tr::bold(now.my.to->name()),
tr::rich)).append(' ').append(peer->isSelf()
? tr::lng_auction_change_to_me(tr::now, tr::rich)
: tr::lng_auction_change_to(
tr::now,
lt_name,
tr::bold(peer->name()),
tr::rich));
box = window->show(MakeConfirmBox({
.text = text,
.confirmed = change,
.confirmText = tr::lng_auction_change_button(),
.title = tr::lng_auction_change_title(),
}));
} else if (showInfoBox) {
box = show->show(Box(
box = window->show(Box(
AuctionInfoBox,
show,
window,
peer,
state->value()));
} else {
local->writePref<bool>(kAuctionAboutShownPref, true);
const auto understood = [=](Fn<void()> close) {
ChooseAndShowAuctionBox(show, peer, state, close);
ChooseAndShowAuctionBox(window, peer, state, close);
};
box = show->show(Box(
box = window->show(Box(
AuctionAboutBox,
now.totalRounds,
now.gift->auctionGiftsPerRound,
@@ -1205,17 +1213,16 @@ rpl::lifetime ShowStarGiftAuction(
state->value = std::move(value);
if (initial) {
if (const auto strong = weak.get()) {
const auto show = strong->uiShow();
const auto to = peer
? peer
: already
? already
: show->session().user();
? peer
: already
? already
: strong->session().user();
state->box = ChooseAndShowAuctionBox(
show,
to,
strong,
to,
std::shared_ptr<rpl::variable<Data::GiftAuctionState>>(
state,
state,
&state->value),
boxClosed);
} else {
@@ -1231,4 +1238,69 @@ rpl::lifetime ShowStarGiftAuction(
return result;
}
object_ptr<BoxContent> MakeAuctionBidBox(AuctionBidBoxArgs &&args) {
return Box(AuctionBidBox, std::move(args));
}
void SetAuctionButtonCountdownText(
not_null<RoundButton*> button,
AuctionButtonCountdownType type,
rpl::producer<Data::GiftAuctionState> value) {
struct State {
rpl::variable<Data::GiftAuctionState> value;
rpl::variable<int> minutesLeft;
};
const auto state = button->lifetime().make_state<State>();
state->value = std::move(value);
state->minutesLeft = MinutesLeftTillValue(
state->value.current().endDate);
auto buttonTitle = rpl::combine(
state->value.value(),
state->minutesLeft.value()
) | rpl::map([=](const Data::GiftAuctionState &state, int minutes) {
return (state.finished() || minutes <= 0)
? tr::lng_box_ok(tr::marked)
: (type == AuctionButtonCountdownType::Join)
? tr::lng_auction_join_button(tr::marked)
: tr::lng_auction_join_bid(tr::marked);
}) | rpl::flatten_latest();
auto buttonSubtitle = rpl::combine(
state->value.value(),
state->minutesLeft.value()
) | rpl::map([=](const Data::GiftAuctionState &state, int minutes) {
if (state.finished() || minutes <= 0) {
return rpl::single(TextWithEntities());
}
const auto hours = (minutes / 60);
minutes -= (hours * 60);
auto value = [](int count) {
return rpl::single(tr::marked(QString::number(count)));
};
return tr::lng_auction_join_time_left(
lt_time,
(hours
? tr::lng_auction_join_time_medium(
lt_hours,
value(hours),
lt_minutes,
value(minutes),
tr::marked)
: tr::lng_auction_join_time_small(
lt_minutes,
value(minutes),
tr::marked)),
tr::marked);
}) | rpl::flatten_latest();
SetButtonTwoLabels(
button,
std::move(buttonTitle),
std::move(buttonSubtitle),
st::resaleButtonTitle,
st::resaleButtonSubtitle);
}
} // namespace Ui
@@ -7,12 +7,27 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
*/
#pragma once
namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
struct GiftAuctionState;
} // namespace Data
namespace Info::PeerGifts {
struct GiftSendDetails;
} // namespace Info::PeerGifts
namespace Window {
class SessionController;
} // namespace Window
namespace Ui {
class BoxContent;
class RoundButton;
[[nodiscard]] rpl::lifetime ShowStarGiftAuction(
not_null<Window::SessionController*> controller,
PeerData *peer,
@@ -20,4 +35,22 @@ namespace Ui {
Fn<void()> finishRequesting,
Fn<void()> boxClosed);
struct AuctionBidBoxArgs {
not_null<PeerData*> peer;
std::shared_ptr<ChatHelpers::Show> show;
rpl::producer<Data::GiftAuctionState> state;
std::unique_ptr<Info::PeerGifts::GiftSendDetails> details;
};
[[nodiscard]] object_ptr<BoxContent> MakeAuctionBidBox(
AuctionBidBoxArgs &&args);
enum class AuctionButtonCountdownType {
Join,
Place,
};
void SetAuctionButtonCountdownText(
not_null<RoundButton*> button,
AuctionButtonCountdownType type,
rpl::producer<Data::GiftAuctionState> value);
} // namespace Ui
+323 -310
View File
@@ -35,6 +35,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "chat_helpers/tabbed_selector.h"
#include "core/application.h"
#include "core/ui_integration.h"
#include "data/components/gift_auctions.h"
#include "data/components/promo_suggestions.h"
#include "data/data_birthday.h"
#include "data/data_changes.h"
@@ -159,15 +160,6 @@ struct PremiumGiftsDescriptor {
std::shared_ptr<Api::PremiumGiftCodeOptions> api;
};
struct GiftDetails {
GiftDescriptor descriptor;
TextWithEntities text;
uint64 randomId = 0;
bool anonymous = false;
bool upgraded = false;
bool byStars = false;
};
struct SessionResalePrices {
explicit SessionResalePrices(not_null<Main::Session*> session)
: api(std::make_unique<Api::PremiumGiftCodeOptions>(session->user())) {
@@ -291,14 +283,14 @@ public:
PreviewWrap(
not_null<QWidget*> parent,
not_null<PeerData*> recipient,
rpl::producer<GiftDetails> details);
rpl::producer<GiftSendDetails> details);
~PreviewWrap();
private:
void paintEvent(QPaintEvent *e) override;
void resizeTo(int width);
void prepare(rpl::producer<GiftDetails> details);
void prepare(rpl::producer<GiftSendDetails> details);
const not_null<History*> _history;
const not_null<PeerData*> _recipient;
@@ -437,7 +429,7 @@ auto GenerateGiftMedia(
not_null<Element*> parent,
Element *replacing,
not_null<PeerData*> recipient,
const GiftDetails &data)
const GiftSendDetails &data)
-> Fn<void(
not_null<MediaGeneric*>,
Fn<void(std::unique_ptr<MediaGenericPart>)>)> {
@@ -571,7 +563,7 @@ auto GenerateGiftMedia(
PreviewWrap::PreviewWrap(
not_null<QWidget*> parent,
not_null<PeerData*> recipient,
rpl::producer<GiftDetails> details)
rpl::producer<GiftSendDetails> details)
: RpWidget(parent)
, _history(recipient->owner().history(recipient->session().userPeerId()))
, _recipient(recipient)
@@ -603,7 +595,7 @@ PreviewWrap::PreviewWrap(
void ShowSentToast(
not_null<Window::SessionController*> window,
const GiftDescriptor &descriptor,
const GiftDetails &details) {
const GiftSendDetails &details) {
const auto &st = st::historyPremiumToast;
const auto skip = st.padding.top();
const auto size = st.style.font->height * 2;
@@ -686,8 +678,8 @@ PreviewWrap::~PreviewWrap() {
_item = {};
}
void PreviewWrap::prepare(rpl::producer<GiftDetails> details) {
std::move(details) | rpl::start_with_next([=](GiftDetails details) {
void PreviewWrap::prepare(rpl::producer<GiftSendDetails> details) {
std::move(details) | rpl::start_with_next([=](GiftSendDetails details) {
const auto &descriptor = details.descriptor;
const auto cost = v::match(descriptor, [&](GiftTypePremium data) {
const auto stars = (details.byStars && data.stars)
@@ -1234,7 +1226,7 @@ void SendGift(
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
const GiftDetails &details,
const GiftSendDetails &details,
Fn<void(Payments::CheckoutResult)> done) {
const auto processNonPanelPaymentFormFactory
= Payments::ProcessNonPanelPaymentFormFactory(window, done);
@@ -1618,292 +1610,6 @@ void CheckMaybeGiftLocked(
})).send();
}
void SendGiftBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
const GiftDescriptor &descriptor) {
const auto stars = std::get_if<GiftTypeStars>(&descriptor);
const auto limited = stars
&& (stars->info.limitedCount > stars->info.limitedLeft)
&& (stars->info.limitedLeft > 0);
const auto costToUpgrade = stars ? stars->info.starsToUpgrade : 0;
const auto user = peer->asUser();
const auto disallowed = user
? user->disallowedGiftTypes()
: Api::DisallowedGiftTypes();
const auto disallowLimited = !peer->isSelf()
&& (disallowed & Api::DisallowedGiftType::Limited);
box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
box->setWidth(st::boxWideWidth);
box->setTitle(tr::lng_gift_send_title());
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
const auto session = &window->session();
struct State {
rpl::variable<GiftDetails> details;
rpl::variable<bool> messageAllowed;
std::shared_ptr<Data::DocumentMedia> media;
bool submitting = false;
};
const auto state = box->lifetime().make_state<State>();
state->details = GiftDetails{
.descriptor = descriptor,
.randomId = base::RandomValue<uint64>(),
.upgraded = disallowLimited && (costToUpgrade > 0),
};
peer->updateFull();
state->messageAllowed = peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::StarsPerMessage
) | rpl::map([=] {
return peer->starsPerMessageChecked() == 0;
});
auto cost = state->details.value(
) | rpl::map([](const GiftDetails &details) {
return v::match(details.descriptor, [&](const GiftTypePremium &data) {
const auto stars = (details.byStars && data.stars)
? data.stars
: (data.currency == kCreditsCurrency)
? data.cost
: 0;
if (stars) {
return CreditsEmojiSmall().append(
Lang::FormatCountDecimal(std::abs(stars)));
}
return TextWithEntities{
FillAmountAndCurrency(data.cost, data.currency),
};
}, [&](const GiftTypeStars &data) {
const auto amount = std::abs(data.info.stars)
+ (details.upgraded ? data.info.starsToUpgrade : 0);
return CreditsEmojiSmall().append(
Lang::FormatCountDecimal(amount));
});
});
const auto document = LookupGiftSticker(session, descriptor);
if ((state->media = document ? document->createMediaView() : nullptr)) {
state->media->checkStickerLarge();
}
const auto container = box->verticalLayout();
container->add(object_ptr<PreviewWrap>(
container,
peer,
state->details.value()));
const auto messageWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
messageWrap->toggleOn(state->messageAllowed.value());
messageWrap->finishAnimating();
const auto messageInner = messageWrap->entity();
const auto limit = StarGiftMessageLimit(session);
const auto text = AddPartInput(
window,
messageInner,
box->getDelegate()->outerContainer(),
tr::lng_gift_send_message(),
QString(),
limit);
text->changes() | rpl::start_with_next([=] {
auto now = state->details.current();
auto textWithTags = text->getTextWithAppliedMarkdown();
now.text = TextWithEntities{
std::move(textWithTags.text),
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
};
state->details = std::move(now);
}, text->lifetime());
box->setFocusCallback([=] {
text->setFocusFast();
});
const auto allow = [=](not_null<DocumentData*> emoji) {
return true;
};
InitMessageFieldHandlers({
.session = session,
.show = window->uiShow(),
.field = text,
.customEmojiPaused = [=] {
using namespace Window;
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
},
.allowPremiumEmoji = allow,
.allowMarkdownTags = {
InputField::kTagBold,
InputField::kTagItalic,
InputField::kTagUnderline,
InputField::kTagStrikeOut,
InputField::kTagSpoiler,
}
});
Emoji::SuggestionsController::Init(
box->getDelegate()->outerContainer(),
text,
session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (stars) {
if (costToUpgrade > 0 && !peer->isSelf() && !disallowLimited) {
const auto id = stars->info.id;
const auto showing = std::make_shared<bool>();
AddDivider(container);
AddSkip(container);
AddUpgradeButton(container, costToUpgrade, peer, [=](bool on) {
auto now = state->details.current();
now.upgraded = on;
state->details = std::move(now);
}, [=] {
if (*showing) {
return;
}
*showing = true;
ShowStarGiftUpgradeBox({
.controller = window,
.stargiftId = id,
.ready = [=](bool) { *showing = false; },
.peer = peer,
.cost = int(costToUpgrade),
});
});
} else {
AddDivider(container);
}
AddSkip(container);
container->add(
object_ptr<SettingsButton>(
container,
tr::lng_gift_send_anonymous(),
st::settingsButtonNoIcon)
)->toggleOn(rpl::single(peer->isSelf()))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
auto now = state->details.current();
now.anonymous = toggled;
state->details = std::move(now);
}, container->lifetime());
AddSkip(container);
}
v::match(descriptor, [&](const GiftTypePremium &data) {
AddDividerText(messageInner, tr::lng_gift_send_premium_about(
lt_user,
rpl::single(peer->shortName())));
if (const auto byStars = data.stars) {
const auto star = Ui::Text::IconEmoji(&st::starIconEmojiColored);
AddSkip(container);
container->add(
object_ptr<SettingsButton>(
container,
tr::lng_gift_send_pay_with_stars(
lt_amount,
rpl::single(base::duplicate(star).append(Lang::FormatCountDecimal(byStars))),
Ui::Text::WithEntities),
st::settingsButtonNoIcon)
)->toggleOn(rpl::single(false))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
auto now = state->details.current();
now.byStars = toggled;
state->details = std::move(now);
}, container->lifetime());
AddSkip(container);
const auto balance = AddDividerText(
container,
tr::lng_gift_send_stars_balance(
lt_amount,
peer->session().credits().balanceValue(
) | rpl::map([=](CreditsAmount amount) {
return base::duplicate(star).append(
Lang::FormatCreditsAmountDecimal(amount));
}),
lt_link,
tr::lng_gift_send_stars_balance_link(
) | Ui::Text::ToLink(),
Ui::Text::WithEntities));
struct State {
Settings::BuyStarsHandler buyStars;
rpl::variable<bool> loading;
};
const auto state = balance->lifetime().make_state<State>();
state->loading = state->buyStars.loadingValue();
balance->setClickHandlerFilter([=](const auto &...) {
if (!state->loading.current()) {
state->buyStars.handler(window->uiShow())();
}
return false;
});
}
}, [&](const GiftTypeStars &) {
AddDividerText(container, peer->isSelf()
? tr::lng_gift_send_anonymous_self()
: peer->isBroadcast()
? tr::lng_gift_send_anonymous_about_channel()
: rpl::conditional(
state->messageAllowed.value(),
tr::lng_gift_send_anonymous_about(
lt_user,
rpl::single(peer->shortName()),
lt_recipient,
rpl::single(peer->shortName())),
tr::lng_gift_send_anonymous_about_paid(
lt_user,
rpl::single(peer->shortName()),
lt_recipient,
rpl::single(peer->shortName()))));
});
const auto button = box->addButton(rpl::single(QString()), [=] {
if (state->submitting) {
return;
}
state->submitting = true;
auto details = state->details.current();
if (!state->messageAllowed.current()) {
details.text = {};
}
const auto copy = state->media; // Let media outlive the box.
const auto weak = base::make_weak(box);
const auto done = [=](Payments::CheckoutResult result) {
if (result == Payments::CheckoutResult::Paid) {
if (details.byStars
|| v::is<GiftTypeStars>(details.descriptor)) {
window->session().credits().load(true);
}
const auto another = copy; // Let media outlive the box.
window->showPeerHistory(peer);
ShowSentToast(window, details.descriptor, details);
}
if (const auto strong = weak.get()) {
strong->closeBox();
}
};
SendGift(window, peer, api, details, done);
});
if (limited) {
AddSoldLeftSlider(button, *stars);
}
SetButtonMarkedLabel(
button,
(peer->isSelf()
? tr::lng_gift_send_button_self
: tr::lng_gift_send_button)(
lt_cost,
std::move(cost),
Text::WithEntities),
session,
st::creditsBoxButtonLabel,
&st::giftBox.button.textFg);
}
[[nodiscard]] object_ptr<RpWidget> MakeGiftsList(
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
@@ -2012,7 +1718,8 @@ void SendGiftBox(
window,
peer,
state->api,
descriptor));
descriptor,
nullptr));
});
const auto unique = star ? star->info.unique : nullptr;
const auto premiumNeeded = star && star->info.requirePremium;
@@ -4443,12 +4150,6 @@ void UpgradeBox(
AddUniqueCloseButton(box, {});
}
void ShowStarGiftUpgradeBox(StarGiftUpgradeArgs &&args) {
const auto weak = base::make_weak(args.controller);
const auto session = &args.peer->session();
@@ -4742,4 +4443,316 @@ object_ptr<RpWidget> MakeGiftsSendList(
std::move(loadMore));
}
void SendGiftBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
const GiftDescriptor &descriptor,
rpl::producer<Data::GiftAuctionState> auctionState) {
const auto stars = std::get_if<GiftTypeStars>(&descriptor);
const auto limited = stars
&& (stars->info.limitedCount > stars->info.limitedLeft)
&& (stars->info.limitedLeft > 0);
const auto costToUpgrade = stars ? stars->info.starsToUpgrade : 0;
const auto user = peer->asUser();
const auto disallowed = user
? user->disallowedGiftTypes()
: Api::DisallowedGiftTypes();
const auto disallowLimited = !peer->isSelf()
&& (disallowed & Api::DisallowedGiftType::Limited);
box->setStyle(limited ? st::giftLimitedBox : st::giftBox);
box->setWidth(st::boxWideWidth);
box->setTitle(tr::lng_gift_send_title());
box->addTopButton(st::boxTitleClose, [=] {
box->closeBox();
});
const auto session = &window->session();
struct State {
rpl::variable<GiftSendDetails> details;
rpl::variable<bool> messageAllowed;
std::shared_ptr<Data::DocumentMedia> media;
rpl::variable<Data::GiftAuctionState> auction;
bool submitting = false;
};
const auto state = box->lifetime().make_state<State>();
if (auctionState) {
state->auction = std::move(auctionState);
}
state->details = GiftSendDetails{
.descriptor = descriptor,
.randomId = base::RandomValue<uint64>(),
.upgraded = disallowLimited && (costToUpgrade > 0),
};
peer->updateFull();
state->messageAllowed = peer->session().changes().peerFlagsValue(
peer,
Data::PeerUpdate::Flag::StarsPerMessage
) | rpl::map([=] {
return peer->starsPerMessageChecked() == 0;
});
auto cost = state->details.value(
) | rpl::map([](const GiftSendDetails &details) {
return v::match(details.descriptor, [&](const GiftTypePremium &data) {
const auto stars = (details.byStars && data.stars)
? data.stars
: (data.currency == kCreditsCurrency)
? data.cost
: 0;
if (stars) {
return CreditsEmojiSmall().append(
Lang::FormatCountDecimal(std::abs(stars)));
}
return TextWithEntities{
FillAmountAndCurrency(data.cost, data.currency),
};
}, [&](const GiftTypeStars &data) {
const auto amount = std::abs(data.info.stars)
+ (details.upgraded ? data.info.starsToUpgrade : 0);
return CreditsEmojiSmall().append(
Lang::FormatCountDecimal(amount));
});
});
const auto document = LookupGiftSticker(session, descriptor);
if ((state->media = document ? document->createMediaView() : nullptr)) {
state->media->checkStickerLarge();
}
const auto container = box->verticalLayout();
container->add(object_ptr<PreviewWrap>(
container,
peer,
state->details.value()));
const auto messageWrap = container->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
container,
object_ptr<Ui::VerticalLayout>(container)));
messageWrap->toggleOn(state->messageAllowed.value());
messageWrap->finishAnimating();
const auto messageInner = messageWrap->entity();
const auto limit = StarGiftMessageLimit(session);
const auto text = AddPartInput(
window,
messageInner,
box->getDelegate()->outerContainer(),
tr::lng_gift_send_message(),
QString(),
limit);
text->changes() | rpl::start_with_next([=] {
auto now = state->details.current();
auto textWithTags = text->getTextWithAppliedMarkdown();
now.text = TextWithEntities{
std::move(textWithTags.text),
TextUtilities::ConvertTextTagsToEntities(textWithTags.tags)
};
state->details = std::move(now);
}, text->lifetime());
box->setFocusCallback([=] {
text->setFocusFast();
});
const auto allow = [=](not_null<DocumentData*> emoji) {
return true;
};
InitMessageFieldHandlers({
.session = session,
.show = window->uiShow(),
.field = text,
.customEmojiPaused = [=] {
using namespace Window;
return window->isGifPausedAtLeastFor(GifPauseReason::Layer);
},
.allowPremiumEmoji = allow,
.allowMarkdownTags = {
InputField::kTagBold,
InputField::kTagItalic,
InputField::kTagUnderline,
InputField::kTagStrikeOut,
InputField::kTagSpoiler,
}
});
Emoji::SuggestionsController::Init(
box->getDelegate()->outerContainer(),
text,
session,
{ .suggestCustomEmoji = true, .allowCustomWithoutPremium = allow });
if (stars) {
if (costToUpgrade > 0 && !peer->isSelf() && !disallowLimited) {
const auto id = stars->info.id;
const auto showing = std::make_shared<bool>();
AddDivider(container);
AddSkip(container);
AddUpgradeButton(container, costToUpgrade, peer, [=](bool on) {
auto now = state->details.current();
now.upgraded = on;
state->details = std::move(now);
}, [=] {
if (*showing) {
return;
}
*showing = true;
ShowStarGiftUpgradeBox({
.controller = window,
.stargiftId = id,
.ready = [=](bool) { *showing = false; },
.peer = peer,
.cost = int(costToUpgrade),
});
});
} else {
AddDivider(container);
}
AddSkip(container);
container->add(
object_ptr<SettingsButton>(
container,
tr::lng_gift_send_anonymous(),
st::settingsButtonNoIcon)
)->toggleOn(rpl::single(peer->isSelf()))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
auto now = state->details.current();
now.anonymous = toggled;
state->details = std::move(now);
}, container->lifetime());
AddSkip(container);
}
v::match(descriptor, [&](const GiftTypePremium &data) {
AddDividerText(messageInner, tr::lng_gift_send_premium_about(
lt_user,
rpl::single(peer->shortName())));
if (const auto byStars = data.stars) {
const auto star = Ui::Text::IconEmoji(&st::starIconEmojiColored);
AddSkip(container);
container->add(
object_ptr<SettingsButton>(
container,
tr::lng_gift_send_pay_with_stars(
lt_amount,
rpl::single(base::duplicate(star).append(Lang::FormatCountDecimal(byStars))),
Ui::Text::WithEntities),
st::settingsButtonNoIcon)
)->toggleOn(rpl::single(false))->toggledValue(
) | rpl::start_with_next([=](bool toggled) {
auto now = state->details.current();
now.byStars = toggled;
state->details = std::move(now);
}, container->lifetime());
AddSkip(container);
const auto balance = AddDividerText(
container,
tr::lng_gift_send_stars_balance(
lt_amount,
peer->session().credits().balanceValue(
) | rpl::map([=](CreditsAmount amount) {
return base::duplicate(star).append(
Lang::FormatCreditsAmountDecimal(amount));
}),
lt_link,
tr::lng_gift_send_stars_balance_link(
) | Ui::Text::ToLink(),
Ui::Text::WithEntities));
struct State {
Settings::BuyStarsHandler buyStars;
rpl::variable<bool> loading;
};
const auto state = balance->lifetime().make_state<State>();
state->loading = state->buyStars.loadingValue();
balance->setClickHandlerFilter([=](const auto &...) {
if (!state->loading.current()) {
state->buyStars.handler(window->uiShow())();
}
return false;
});
}
}, [&](const GiftTypeStars &) {
AddDividerText(container, peer->isSelf()
? tr::lng_gift_send_anonymous_self()
: peer->isBroadcast()
? tr::lng_gift_send_anonymous_about_channel()
: rpl::conditional(
state->messageAllowed.value(),
tr::lng_gift_send_anonymous_about(
lt_user,
rpl::single(peer->shortName()),
lt_recipient,
rpl::single(peer->shortName())),
tr::lng_gift_send_anonymous_about_paid(
lt_user,
rpl::single(peer->shortName()),
lt_recipient,
rpl::single(peer->shortName()))));
});
const auto button = box->addButton(rpl::single(QString()), [=] {
if (state->submitting) {
return;
}
state->submitting = true;
auto details = state->details.current();
if (!state->messageAllowed.current()) {
details.text = {};
}
const auto stars = std::get_if<GiftTypeStars>(&details.descriptor);
if (stars && stars->info.auction()) {
const auto bidBox = window->show(MakeAuctionBidBox({
.peer = peer,
.show = window->uiShow(),
.state = state->auction.value(),
.details = std::make_unique<GiftSendDetails>(details),
}));
bidBox->boxClosing(
) | rpl::start_with_next([=] {
box->closeBox();
}, box->lifetime());
return;
}
const auto copy = state->media; // Let media outlive the box.
const auto weak = base::make_weak(box);
const auto done = [=](Payments::CheckoutResult result) {
if (result == Payments::CheckoutResult::Paid) {
if (details.byStars
|| v::is<GiftTypeStars>(details.descriptor)) {
window->session().credits().load(true);
}
const auto another = copy; // Let media outlive the box.
window->showPeerHistory(peer);
ShowSentToast(window, details.descriptor, details);
}
if (const auto strong = weak.get()) {
strong->closeBox();
}
};
SendGift(window, peer, api, details, done);
});
if (limited) {
AddSoldLeftSlider(button, *stars);
}
if (stars && stars->info.auction()) {
SetAuctionButtonCountdownText(
button,
AuctionButtonCountdownType::Place,
state->auction.value());
} else {
SetButtonMarkedLabel(
button,
(peer->isSelf()
? tr::lng_gift_send_button_self
: tr::lng_gift_send_button)(
lt_cost,
std::move(cost),
Text::WithEntities),
session,
st::creditsBoxButtonLabel,
&st::giftBox.button.textFg);
}
}
} // namespace Ui
@@ -22,6 +22,7 @@ struct UniqueGift;
struct GiftCode;
struct CreditsHistoryEntry;
class SavedStarGiftId;
struct GiftAuctionState;
} // namespace Data
namespace Info::PeerGifts {
@@ -162,4 +163,12 @@ struct GiftsDescriptor {
rpl::producer<GiftsDescriptor> gifts,
Fn<void()> loadMore);
void SendGiftBox(
not_null<GenericBox*> box,
not_null<Window::SessionController*> window,
not_null<PeerData*> peer,
std::shared_ptr<Api::PremiumGiftCodeOptions> api,
const Info::PeerGifts::GiftDescriptor &descriptor,
rpl::producer<Data::GiftAuctionState> auctionState);
} // namespace Ui
@@ -105,6 +105,15 @@ struct GiftDescriptor : std::variant<GiftTypePremium, GiftTypeStars> {
const GiftDescriptor&) = default;
};
struct GiftSendDetails {
GiftDescriptor descriptor;
TextWithEntities text;
uint64 randomId = 0;
bool anonymous = false;
bool upgraded = false;
bool byStars = false;
};
struct GiftBadge {
QString text;
QColor bg1;
@@ -3747,6 +3747,7 @@ void Account::writePrefs() {
}
void Account::readPrefs() {
return; AssertIsDebug();
FileReadDescriptor prefs;
if (!ReadEncryptedFile(prefs, _prefsKey, _basePath, _localKey)) {
ClearKey(_prefsKey, _basePath);