diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index aba65b839f..79fee8b0ce 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -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."; diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp index 62acc9303c..36310e2bc2 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp @@ -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( - 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 peer; - std::shared_ptr show; - rpl::producer state; -}; - void PlaceAuctionBid( std::shared_ptr show, not_null to, int64 amount, const Data::GiftAuctionState &state, + std::unique_ptr details, Fn 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 MakeAuctionInfoBlocks( { .title = std::move(bidTitle), .subtext = tr::lng_auction_bid_minimal( - lt_count, + lt_count, std::move(minimal)), }, { @@ -349,9 +354,9 @@ object_ptr MakeAuctionInfoBlocks( } void AddBidPlaces( - not_null box, - std::shared_ptr show, - rpl::producer value, + not_null box, + std::shared_ptr show, + rpl::producer value, rpl::producer 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 box, AuctionBidBoxArgs &&args) { const auto weak = base::make_weak(box); + using namespace Info::PeerGifts; struct State { State(rpl::producer value) : value(std::move(value)) { @@ -492,6 +498,9 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { state->chosen = chosen; const auto show = args.show; + const auto details = args.details + ? *args.details + : std::optional(); const auto save = [=, peer = args.peer](int amount) { const auto ¤t = state->value.current(); if (amount > current.my.bid) { @@ -513,7 +522,10 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { }); } }; - PlaceAuctionBid(show, peer, amount, now, done); + auto owned = details + ? std::make_unique(*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 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 box, AuctionBidBoxArgs &&args) { }) | rpl::flatten_latest()); AddStarSelectBalance( - box, - &show->session(), + box, + &show->session(), show->session().credits().balanceValue()); } -[[nodiscard]] object_ptr MakeAuctionBidBox( - AuctionBidBoxArgs &&args) { - return Box(AuctionBidBox, std::move(args)); -} - [[nodiscard]] object_ptr MakeAveragePriceValue( not_null table, std::shared_ptr 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 box, - std::shared_ptr show, + not_null box, + std::shared_ptr show, const Data::StarGift &gift, std::vector 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 box, - std::shared_ptr show, + not_null window, not_null peer, rpl::producer value) { using namespace Info::PeerGifts; @@ -924,6 +931,7 @@ void AuctionInfoBox( std::vector acquired; bool acquiredRequested = false; }; + const auto show = window->uiShow(); const auto state = box->lifetime().make_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 result) { + crl::guard(box, [=]( + std::vector 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 ChooseAndShowAuctionBox( - std::shared_ptr show, + not_null window, not_null peer, std::shared_ptr> state, Fn boxClosed) { @@ -1143,28 +1114,65 @@ base::weak_qptr 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(kAuctionAboutShownPref) || finished); auto box = base::weak_qptr(); 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 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(kAuctionAboutShownPref, true); const auto understood = [=](Fn 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>( - state, + state, &state->value), boxClosed); } else { @@ -1231,4 +1238,69 @@ rpl::lifetime ShowStarGiftAuction( return result; } +object_ptr MakeAuctionBidBox(AuctionBidBoxArgs &&args) { + return Box(AuctionBidBox, std::move(args)); +} + +void SetAuctionButtonCountdownText( + not_null button, + AuctionButtonCountdownType type, + rpl::producer value) { + struct State { + rpl::variable value; + rpl::variable minutesLeft; + }; + const auto state = button->lifetime().make_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 diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.h b/Telegram/SourceFiles/boxes/star_gift_auction_box.h index 254c59a18b..5588a96dd6 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.h @@ -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 controller, PeerData *peer, @@ -20,4 +35,22 @@ namespace Ui { Fn finishRequesting, Fn boxClosed); +struct AuctionBidBoxArgs { + not_null peer; + std::shared_ptr show; + rpl::producer state; + std::unique_ptr details; +}; +[[nodiscard]] object_ptr MakeAuctionBidBox( + AuctionBidBoxArgs &&args); + +enum class AuctionButtonCountdownType { + Join, + Place, +}; +void SetAuctionButtonCountdownText( + not_null button, + AuctionButtonCountdownType type, + rpl::producer value); + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 36a48e1a01..b64a4d1142 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -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; }; -struct GiftDetails { - GiftDescriptor descriptor; - TextWithEntities text; - uint64 randomId = 0; - bool anonymous = false; - bool upgraded = false; - bool byStars = false; -}; - struct SessionResalePrices { explicit SessionResalePrices(not_null session) : api(std::make_unique(session->user())) { @@ -291,14 +283,14 @@ public: PreviewWrap( not_null parent, not_null recipient, - rpl::producer details); + rpl::producer details); ~PreviewWrap(); private: void paintEvent(QPaintEvent *e) override; void resizeTo(int width); - void prepare(rpl::producer details); + void prepare(rpl::producer details); const not_null _history; const not_null _recipient; @@ -437,7 +429,7 @@ auto GenerateGiftMedia( not_null parent, Element *replacing, not_null recipient, - const GiftDetails &data) + const GiftSendDetails &data) -> Fn, Fn)>)> { @@ -571,7 +563,7 @@ auto GenerateGiftMedia( PreviewWrap::PreviewWrap( not_null parent, not_null recipient, - rpl::producer details) + rpl::producer details) : RpWidget(parent) , _history(recipient->owner().history(recipient->session().userPeerId())) , _recipient(recipient) @@ -603,7 +595,7 @@ PreviewWrap::PreviewWrap( void ShowSentToast( not_null 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 details) { - std::move(details) | rpl::start_with_next([=](GiftDetails details) { +void PreviewWrap::prepare(rpl::producer 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, not_null peer, std::shared_ptr api, - const GiftDetails &details, + const GiftSendDetails &details, Fn done) { const auto processNonPanelPaymentFormFactory = Payments::ProcessNonPanelPaymentFormFactory(window, done); @@ -1618,292 +1610,6 @@ void CheckMaybeGiftLocked( })).send(); } -void SendGiftBox( - not_null box, - not_null window, - not_null peer, - std::shared_ptr api, - const GiftDescriptor &descriptor) { - const auto stars = std::get_if(&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 details; - rpl::variable messageAllowed; - std::shared_ptr media; - bool submitting = false; - }; - const auto state = box->lifetime().make_state(); - state->details = GiftDetails{ - .descriptor = descriptor, - .randomId = base::RandomValue(), - .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( - container, - peer, - state->details.value())); - - const auto messageWrap = container->add( - object_ptr>( - container, - object_ptr(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 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(); - 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( - 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( - 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 loading; - }; - const auto state = balance->lifetime().make_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(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 MakeGiftsList( not_null window, not_null 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 MakeGiftsSendList( std::move(loadMore)); } +void SendGiftBox( + not_null box, + not_null window, + not_null peer, + std::shared_ptr api, + const GiftDescriptor &descriptor, + rpl::producer auctionState) { + const auto stars = std::get_if(&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 details; + rpl::variable messageAllowed; + std::shared_ptr media; + rpl::variable auction; + bool submitting = false; + }; + const auto state = box->lifetime().make_state(); + if (auctionState) { + state->auction = std::move(auctionState); + } + state->details = GiftSendDetails{ + .descriptor = descriptor, + .randomId = base::RandomValue(), + .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( + container, + peer, + state->details.value())); + + const auto messageWrap = container->add( + object_ptr>( + container, + object_ptr(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 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(); + 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( + 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( + 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 loading; + }; + const auto state = balance->lifetime().make_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(&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(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(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 diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index 64f20564d4..e767e13493 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -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 gifts, Fn loadMore); +void SendGiftBox( + not_null box, + not_null window, + not_null peer, + std::shared_ptr api, + const Info::PeerGifts::GiftDescriptor &descriptor, + rpl::producer auctionState); + } // namespace Ui diff --git a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h index 95fb6b1f0c..655c9810b1 100644 --- a/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h +++ b/Telegram/SourceFiles/info/peer_gifts/info_peer_gifts_common.h @@ -105,6 +105,15 @@ struct GiftDescriptor : std::variant { 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; diff --git a/Telegram/SourceFiles/storage/storage_account.cpp b/Telegram/SourceFiles/storage/storage_account.cpp index ab62a58beb..71ea94f0a1 100644 --- a/Telegram/SourceFiles/storage/storage_account.cpp +++ b/Telegram/SourceFiles/storage/storage_account.cpp @@ -3747,6 +3747,7 @@ void Account::writePrefs() { } void Account::readPrefs() { + return; AssertIsDebug(); FileReadDescriptor prefs; if (!ReadEncryptedFile(prefs, _prefsKey, _basePath, _localKey)) { ClearKey(_prefsKey, _basePath);