From 19225c7dd302d6b83125accd54bcc40ba3c75589 Mon Sep 17 00:00:00 2001 From: John Preston Date: Thu, 20 Nov 2025 20:46:53 +0400 Subject: [PATCH] Rebuild slider when relevant values change. --- .../boxes/star_gift_auction_box.cpp | 210 +++++++++++------- .../data/components/gift_auctions.cpp | 2 + .../payments/ui/payments_reaction_box.cpp | 11 +- .../payments/ui/payments_reaction_box.h | 3 +- .../SourceFiles/ui/effects/premium_bubble.cpp | 49 +++- .../SourceFiles/ui/effects/premium_bubble.h | 3 + 6 files changed, 177 insertions(+), 101 deletions(-) diff --git a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp index 1d6d0c1025..a338d776e6 100644 --- a/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_auction_box.cpp @@ -85,6 +85,16 @@ struct BidRowData { const BidRowData &) = default; }; +struct BidSliderValues { + int min = 0; + int explicitlyAllowed = 0; + int max = 0; + + friend inline bool operator==( + const BidSliderValues &, + const BidSliderValues &) = default; +}; + [[nodiscard]] std::optional BidColorOverride(int position, int per) { return (position <= per) ? st::boxTextFgGood->c @@ -538,8 +548,6 @@ void EditCustomBid( } void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { - const auto weak = base::make_weak(box); - using namespace Info::PeerGifts; struct State { State(rpl::producer value) @@ -547,47 +555,61 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { } rpl::variable value; + rpl::variable sliderValues; rpl::variable chosen; rpl::variable subtext; + bool placing = false; }; const auto state = box->lifetime().make_state( std::move(args.state)); - const auto &now = state->value.current(); - const auto mine = int(now.my.bid); - const auto min = std::max( - int(now.my.minBidAmount ? now.my.minBidAmount : now.minBidAmount), - 1); - const auto last = now.bidLevels.empty() - ? 0 - : now.bidLevels.front().amount; - const auto max = std::max({ - min + 1, - kMaxShownBid, - int(base::SafeRound(mine * 1.2)), - int(base::SafeRound(last * 1.2)), + state->sliderValues = state->value.value( + ) | rpl::map([=](const Data::GiftAuctionState &value) { + const auto mine = int(value.my.bid); + const auto min = std::max(1, int(value.my.minBidAmount + ? value.my.minBidAmount + : value.minBidAmount)); + const auto last = value.bidLevels.empty() + ? 0 + : value.bidLevels.front().amount; + auto max = std::max({ + min + 1, + kMaxShownBid, + int(base::SafeRound(mine * 1.2)), + }); + if (max < last * 1.05) { + max = int(base::SafeRound(last * 1.2)); + } + return BidSliderValues{ + .min = min, + .explicitlyAllowed = mine, + .max = max, + }; }); - const auto chosen = mine ? mine : std::clamp(mine, min, max); - state->chosen = chosen; + + const auto show = args.show; + const auto giftId = state->value.current().gift->id; + const auto &sliderValues = state->sliderValues.current(); + state->chosen = sliderValues.explicitlyAllowed + ? sliderValues.explicitlyAllowed + : sliderValues.min; state->subtext = rpl::combine( state->value.value(), state->chosen.value() ) | rpl::map([=]( - const Data::GiftAuctionState &state, + const Data::GiftAuctionState &value, int chosen) { - if (state.my.bid == chosen) { + if (value.my.bid == chosen) { return tr::lng_auction_bid_your(tr::now); - } else if (chosen == max) { + } else if (chosen == state->sliderValues.current().max) { return tr::lng_auction_bid_custom(tr::now); - } else if (state.my.bid && chosen > state.my.bid) { - const auto delta = chosen - state.my.bid; + } else if (value.my.bid && chosen > value.my.bid) { + const auto delta = chosen - value.my.bid; return '+' + Lang::FormatCountDecimal(delta); } return QString(); }); - const auto giftId = now.gift->id; - const auto show = args.show; args.peer->owner().giftAuctionGots( ) | rpl::start_with_next([=](const Data::GiftAuctionGot &update) { if (update.giftId == giftId) { @@ -602,36 +624,6 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { 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) { - const auto was = (current.my.bid > 0); - const auto perRound = current.gift->auctionGiftsPerRound; - const auto done = [=](Payments::CheckoutResult result) { - if (result == Payments::CheckoutResult::Paid) { - show->showToast({ - .title = (was - ? tr::lng_auction_bid_increased_title - : tr::lng_auction_bid_placed_title)( - tr::now), - .text = tr::lng_auction_bid_done_text( - tr::now, - lt_count, - perRound, - tr::rich), - .duration = kBidPlacedToastDuration, - }); - } - }; - 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(); - } - }; const auto colorings = show->session().appConfig().groupCallColorings(); box->setWidth(st::boxWideWidth); @@ -647,36 +639,54 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { count); return ColorFromSerialized(coloring.bgLight); }; - const auto bubble = AddStarSelectBubble( - box, - state->chosen.value(), - max, - activeFgOverride); - bubble->setAttribute(Qt::WA_TransparentForMouseEvents, false); - bubble->setClickedCallback([=, show = args.show] { - auto min = state->value.value( - ) | rpl::map([=](const Data::GiftAuctionState &state) { - return std::max(1, int(state.my.minBidAmount - ? state.my.minBidAmount - : state.minBidAmount)); - }); - show->show(Box(EditCustomBid, show, crl::guard(box, [=](int value) { - state->chosen = value; - }), std::move(min), state->chosen.current())); - }); - state->subtext.value() | rpl::start_with_next([=](QString &&text) { - bubble->setSubtext(std::move(text)); - }, bubble->lifetime()); + const auto sliderWrap = content->add( + object_ptr(content)); + state->sliderValues.value( + ) | rpl::start_with_next([=](const BidSliderValues &values) { + const auto initial = !sliderWrap->count(); + if (!initial) { + while (sliderWrap->count()) { + delete sliderWrap->widgetAt(0); + } + while (!sliderWrap->children().isEmpty()) { + delete sliderWrap->children().front(); + } + } - PaidReactionSlider( - content, - st::paidReactSlider, - min, - mine, - state->chosen.value(), - max, - [=](int count) { state->chosen = count; }, - activeFgOverride); + const auto bubble = AddStarSelectBubble( + sliderWrap, + initial ? BoxShowFinishes(box) : nullptr, + state->chosen.value(), + values.max, + activeFgOverride); + bubble->setAttribute(Qt::WA_TransparentForMouseEvents, false); + bubble->setClickedCallback([=] { + auto min = state->value.value( + ) | rpl::map([=](const Data::GiftAuctionState &state) { + return std::max(1, int(state.my.minBidAmount + ? state.my.minBidAmount + : state.minBidAmount)); + }); + show->show(Box(EditCustomBid, show, crl::guard(box, [=](int v) { + state->chosen = v; + }), std::move(min), state->chosen.current())); + }); + state->subtext.value() | rpl::start_with_next([=](QString &&text) { + bubble->setSubtext(std::move(text)); + }, bubble->lifetime()); + + PaidReactionSlider( + sliderWrap, + st::paidReactSlider, + values.min, + values.explicitlyAllowed, + state->chosen.value(), + values.max, + [=](int count) { state->chosen = count; }, + activeFgOverride); + + sliderWrap->resizeToWidth(st::boxWideWidth); + }, sliderWrap->lifetime()); box->addTopButton( st::boxTitleClose, @@ -723,8 +733,40 @@ void AuctionBidBox(not_null box, AuctionBidBoxArgs &&args) { AddSkip(content); AddSkip(content); + const auto peer = args.peer; const auto button = box->addButton(rpl::single(QString()), [=] { - save(state->chosen.current()); + const auto ¤t = state->value.current(); + const auto amount = state->chosen.current(); + if (amount <= current.my.bid) { + box->closeBox(); + return; + } else if (state->placing) { + return; + } + state->placing = true; + const auto was = (current.my.bid > 0); + const auto perRound = current.gift->auctionGiftsPerRound; + const auto done = [=](Payments::CheckoutResult result) { + state->placing = false; + if (result == Payments::CheckoutResult::Paid) { + show->showToast({ + .title = (was + ? tr::lng_auction_bid_increased_title + : tr::lng_auction_bid_placed_title)( + tr::now), + .text = tr::lng_auction_bid_done_text( + tr::now, + lt_count, + perRound, + tr::rich), + .duration = kBidPlacedToastDuration, + }); + } + }; + auto owned = details + ? std::make_unique(*details) + : nullptr; + PlaceAuctionBid(show, peer, amount, current, std::move(owned), done); }); button->setText(rpl::combine( diff --git a/Telegram/SourceFiles/data/components/gift_auctions.cpp b/Telegram/SourceFiles/data/components/gift_auctions.cpp index 0e2cea906e..350975f999 100644 --- a/Telegram/SourceFiles/data/components/gift_auctions.cpp +++ b/Telegram/SourceFiles/data/components/gift_auctions.cpp @@ -51,12 +51,14 @@ rpl::producer GiftAuctions::state(const QString &slug) { void GiftAuctions::apply(const MTPDupdateStarGiftAuctionState &data) { if (const auto entry = find(data.vgift_id().v)) { apply(entry, data.vstate()); + entry->changes.fire({}); } } void GiftAuctions::apply(const MTPDupdateStarGiftAuctionUserState &data) { if (const auto entry = find(data.vgift_id().v)) { apply(entry, data.vuser_state()); + entry->changes.fire({}); } } diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp index 15ed49ab2e..1fa9b384bc 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.cpp @@ -515,7 +515,8 @@ void PaidReactionsBox( return Ui::ColorFromSerialized(coloring.bgLight); }; AddStarSelectBubble( - box, + content, + BoxShowFinishes(box), state->chosen.value(), args.max, videoStream ? activeFgOverride : Fn()); @@ -938,7 +939,8 @@ void AddStarSelectBalance( } not_null AddStarSelectBubble( - not_null box, + not_null container, + rpl::producer<> showFinishes, rpl::producer value, int max, Fn activeFgOverride) { @@ -958,14 +960,15 @@ not_null AddStarSelectBubble( }); const auto bubble = Premium::AddBubbleRow( - box->verticalLayout(), + container, st::boostBubble, - BoxShowFinishes(box), + std::move(showFinishes), std::move(bubbleRowState), Premium::BubbleType::Credits, nullptr, &st::paidReactBubbleIcon, st::boxRowPadding); + bubble->show(); if (activeFgOverride) { std::move(value) | rpl::start_with_next([=](int count) { bubble->setBrushOverride(activeFgOverride(count)); diff --git a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h index 26836f0630..7bf16b6074 100644 --- a/Telegram/SourceFiles/payments/ui/payments_reaction_box.h +++ b/Telegram/SourceFiles/payments/ui/payments_reaction_box.h @@ -99,7 +99,8 @@ void AddStarSelectBalance( bool dark = false); not_null AddStarSelectBubble( - not_null box, + not_null container, + rpl::producer<> showFinishes, rpl::producer value, int max, Fn activeFgOverride = nullptr); diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp index 88ed2352e7..9ab7e2b5a2 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.cpp @@ -217,6 +217,10 @@ void Bubble::setSubtext(QString subtext) { _updateCallback(); } +void Bubble::finishAnimating() { + _numberAnimation.finishAnimating(); +} + void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) { if (!_counter.has_value()) { return; @@ -332,6 +336,10 @@ BubbleWidget::BubbleWidget( resizeTo(_bubble.width(), _bubble.height()); }, lifetime()); + const auto instant = !showFinishes; + if (instant) { + showFinishes = rpl::single(rpl::empty); + } std::move( showFinishes ) | rpl::take(1) | rpl::start_with_next([=] { @@ -339,15 +347,27 @@ BubbleWidget::BubbleWidget( ) | rpl::start_with_next([=](BubbleRowState state) { animateTo(state); }, lifetime()); + if (instant) { + _appearanceAnimation.stop(); + if (const auto onstack = base::take(_appearanceCallback)) { + onstack(1.); + } + _bubble.finishAnimating(); + } parent->widthValue() | rpl::start_with_next([=](int w) { if (!_appearanceAnimation.animating()) { - const auto x = base::SafeRound( - w * _state.current().ratio - width() / 2); - const auto padding = _spaceForDeflection.width(); - moveToLeft( - std::clamp(int(x), -padding, w - width() + padding), - y()); + const auto available = w + - _outerPadding.left() + - _outerPadding.right(); + const auto x = (available * _animatingFromResultRatio); + moveToLeft(-_spaceForDeflection.width() + + std::max( + int(base::SafeRound(x + - (_bubble.width() / 2) + + _outerPadding.left())), + 0), + 0); } }, lifetime()); }, lifetime()); @@ -482,11 +502,12 @@ void BubbleWidget::animateTo(BubbleRowState state) { _animatingFromResultRatio = 0.; _animatingFromBubbleEdge = 0.; } - _appearanceAnimation.start([=](float64 value) { + _appearanceCallback = [=](float64 value) { if (!_appearanceAnimation.animating()) { _animatingFrom = state; _animatingFromResultRatio = resultMoveEndPoint; _animatingFromBubbleEdge = finalEdge; + _appearanceCallback = nullptr; } value = std::abs(value); const auto moveProgress = std::clamp( @@ -518,11 +539,13 @@ void BubbleWidget::animateTo(BubbleRowState state) { _bubble.setFlipHorizontal(nowBubbleEdge < 0); _bubble.setTailEdge(std::abs(nowBubbleEdge)); update(); - }, - 0., - (state.ratio >= _animatingFrom.ratio) ? 1. : -1., - duration, - anim::easeOutCirc); + }; + _appearanceAnimation.start( + _appearanceCallback, + 0., + (state.ratio >= _animatingFrom.ratio) ? 1. : -1., + duration, + anim::easeOutCirc); } void BubbleWidget::setBrushOverride(std::optional brushOverride) { @@ -646,6 +669,8 @@ not_null AddBubbleRow( const style::margins &outerPadding) { const auto container = parent->add( object_ptr(parent, 0)); + container->show(); + const auto bubble = Ui::CreateChild( container, st, diff --git a/Telegram/SourceFiles/ui/effects/premium_bubble.h b/Telegram/SourceFiles/ui/effects/premium_bubble.h index b0434ab8ff..5ecf232547 100644 --- a/Telegram/SourceFiles/ui/effects/premium_bubble.h +++ b/Telegram/SourceFiles/ui/effects/premium_bubble.h @@ -66,6 +66,8 @@ public: [[nodiscard]] QPainterPath bubblePath(const QRect &r) const; void setSubtext(QString subtext); + void finishAnimating(); + [[nodiscard]] rpl::producer<> widthChanges() const; private: @@ -153,6 +155,7 @@ private: const style::margins _outerPadding; Ui::Animations::Simple _appearanceAnimation; + Fn _appearanceCallback; QSize _spaceForDeflection; QLinearGradient _cachedGradient;