From a41bbd27c8543b5a8237ddd1df3cb5b2526660bb Mon Sep 17 00:00:00 2001 From: John Preston Date: Tue, 7 Nov 2023 17:58:20 +0400 Subject: [PATCH] Start multiboosts, support dynamic state. --- Telegram/Resources/langs/lang.strings | 8 + Telegram/SourceFiles/api/api_statistics.cpp | 4 +- .../boxes/peers/edit_peer_color_box.cpp | 10 +- Telegram/SourceFiles/data/data_boosts.h | 2 +- .../info/boosts/info_boosts_inner_widget.cpp | 7 +- Telegram/SourceFiles/ui/boxes/boost_box.cpp | 288 +++++++++++------- Telegram/SourceFiles/ui/boxes/boost_box.h | 11 +- .../ui/effects/premium_graphics.cpp | 79 ++--- .../SourceFiles/ui/effects/premium_graphics.h | 18 +- .../window/window_session_controller.cpp | 52 ++-- .../window/window_session_controller.h | 12 +- 11 files changed, 300 insertions(+), 191 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 44576f62f..a02afaf8f 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2030,6 +2030,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_premium_gift_terms_link" = "here"; "lng_boost_channel_button" = "Boost Channel"; +"lng_boost_again_button" = "Boost Again"; "lng_boost_level#one" = "Level {count}"; "lng_boost_level#other" = "Level {count}"; "lng_boost_channel_title_first" = "Enable stories for channel"; @@ -2060,6 +2061,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_boost_error_flood_text" = "You can change the channel you boost only once a day. Next time you can boost is in {left}."; "lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?"; "lng_boost_now_replace" = "Replace"; +"lng_boost_reassign_title" = "Reassign boost"; +"lng_boost_reassign_text" = "To boost {channel}, reassign a previous boost from another channel."; +"lng_boost_remove_title" = "Remove your boost from"; +"lng_boost_reassign_button" = "Reassign"; +"lng_boost_available_in" = "available in {duration}"; +"lng_boost_available_in_toast#one" = "Wait until the boost is available or get **{count}** more boost by gifting a **Telegram Premium** subscription."; +"lng_boost_available_in_toast#other" = "Wait until the boost is available or get **{count}** more boosts by gifting a **Telegram Premium** subscription."; "lng_boost_channel_title_color" = "Enable colors"; "lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color."; diff --git a/Telegram/SourceFiles/api/api_statistics.cpp b/Telegram/SourceFiles/api/api_statistics.cpp index 9f2dd2cd3..466094d44 100644 --- a/Telegram/SourceFiles/api/api_statistics.cpp +++ b/Telegram/SourceFiles/api/api_statistics.cpp @@ -519,7 +519,9 @@ rpl::producer Boosts::request() { : 0; _boostStatus.overview = Data::BoostsOverview{ - .isBoosted = data.is_my_boost(), + .mine = (data.vmy_boost_slots() + ? data.vmy_boost_slots()->v.size() + : 0), .level = std::max(data.vlevel().v, 0), .boostCount = std::max( data.vboosts().v, diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp index 1eede85e3..7c12c4661 100644 --- a/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp +++ b/Telegram/SourceFiles/boxes/peers/edit_peer_color_box.cpp @@ -514,21 +514,17 @@ void Apply( close(); return; } - const auto next = data.vnext_level_boosts().value_or_empty(); const auto openStatistics = [=] { if (const auto controller = show->resolveWindow( ChatHelpers::WindowUsage::PremiumPromo)) { controller->showSection(Info::Boosts::Make(peer)); } }; + auto counters = Window::ParseBoostCounters(result); + counters.mine = 0; // Don't show current level as just-reached. show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{ .link = qs(data.vboost_url()), - .boost = { - .level = data.vlevel().v, - .boosts = data.vboosts().v, - .thisLevelBoosts = data.vcurrent_level_boosts().v, - .nextLevelBoosts = next, - }, + .boost = counters, .requiredLevel = required, }, openStatistics, nullptr)); cancel(); diff --git a/Telegram/SourceFiles/data/data_boosts.h b/Telegram/SourceFiles/data/data_boosts.h index 08fac640b..bada02a91 100644 --- a/Telegram/SourceFiles/data/data_boosts.h +++ b/Telegram/SourceFiles/data/data_boosts.h @@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct BoostsOverview final { - bool isBoosted = false; + int mine = 0; int level = 0; int boostCount = 0; int currentLevelBoostCount = 0; diff --git a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp index dc787dd60..84b7cad52 100644 --- a/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp +++ b/Telegram/SourceFiles/info/boosts/info_boosts_inner_widget.cpp @@ -302,17 +302,16 @@ void InnerWidget::fill() { auto dividerContent = object_ptr(inner); Ui::FillBoostLimit( fakeShowed->events(), - rpl::single(status.overview.isBoosted), dividerContent.data(), - Ui::BoostCounters{ + rpl::single(Ui::BoostCounters{ .level = status.overview.level, .boosts = status.overview.boostCount, .thisLevelBoosts = status.overview.currentLevelBoostCount, .nextLevelBoosts = status.overview.nextLevelBoostCount, - .mine = status.overview.isBoosted, - }, + .mine = status.overview.mine, + }), st::statisticsLimitsLinePadding); inner->add(object_ptr( inner, diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.cpp b/Telegram/SourceFiles/ui/boxes/boost_box.cpp index e9ffe759d..197bcc0ad 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/boost_box.cpp @@ -20,6 +20,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include namespace Ui { +namespace { + +[[nodiscrd]] BoostCounters AdjustByReached(BoostCounters data) { + const auto exact = (data.boosts == data.thisLevelBoosts); + const auto reached = !data.nextLevelBoosts || (exact && data.mine > 0); + if (reached) { + if (data.nextLevelBoosts) { + --data.level; + } + data.boosts = data.nextLevelBoosts = std::max({ + data.boosts, + data.thisLevelBoosts, + 1 + }); + data.thisLevelBoosts = 0; + } else { + data.boosts = std::max(data.thisLevelBoosts, data.boosts); + data.nextLevelBoosts = std::max( + data.nextLevelBoosts, + data.boosts + 1); + } + return data; +} + +} // namespace void StartFireworks(not_null parent) { const auto result = Ui::CreateChild(parent.get()); @@ -42,62 +67,65 @@ void StartFireworks(not_null parent) { void BoostBox( not_null box, BoostBoxData data, - Fn)> boost) { + Fn)> boost) { box->setWidth(st::boxWideWidth); box->setStyle(st::boostBox); - const auto full = !data.boost.nextLevelBoosts; + //AssertIsDebug(); + //data.boost = { + // .level = 2, + // .boosts = 3, + // .thisLevelBoosts = 2, + // .nextLevelBoosts = 5, + // .mine = 2, + //}; struct State { - rpl::variable you = false; + rpl::variable data; + rpl::variable full; bool submitted = false; }; - const auto state = box->lifetime().make_state(State{ - .you = data.boost.mine, - }); + const auto state = box->lifetime().make_state(); + state->data = std::move(data.boost); FillBoostLimit( BoxShowFinishes(box), - state->you.value(), box->verticalLayout(), - data.boost, + state->data.value(), st::boxRowPadding); - { - const auto &d = data.boost; - if (!d.nextLevelBoosts - || ((d.thisLevelBoosts == d.boosts) && d.mine)) { - --data.boost.level; - } - } - box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); const auto name = data.name; - auto title = state->you.value() | rpl::map([=](bool your) { - return your + + auto title = state->data.value( + ) | rpl::map([=](BoostCounters counters) { + return (counters.mine > 0) ? tr::lng_boost_channel_you_title( lt_channel, - rpl::single(data.name)) - : full + rpl::single(name)) + : !counters.nextLevelBoosts ? tr::lng_boost_channel_title_max() - : !data.boost.level + : !counters.level ? tr::lng_boost_channel_title_first() : tr::lng_boost_channel_title_more(); }) | rpl::flatten_latest(); - auto text = state->you.value() | rpl::map([=](bool your) { - const auto bold = Ui::Text::Bold(data.name); - const auto now = data.boost.boosts + (your ? 1 : 0); - const auto left = (data.boost.nextLevelBoosts > now) - ? (data.boost.nextLevelBoosts - now) + + auto text = state->data.value( + ) | rpl::map([=](BoostCounters counters) { + const auto bold = Ui::Text::Bold(name); + const auto now = counters.boosts; + const auto full = !counters.nextLevelBoosts; + const auto left = (counters.nextLevelBoosts > now) + ? (counters.nextLevelBoosts - now) : 0; auto post = tr::lng_boost_channel_post_stories( lt_count, - rpl::single(float64(data.boost.level + 1)), + rpl::single(float64(counters.level + (left ? 1 : 0))), Ui::Text::RichLangValue); - return (your || full) - ? ((!full && left > 0) - ? (!data.boost.level + return (counters.mine || full) + ? (left + ? (!counters.level ? tr::lng_boost_channel_you_first( lt_count, rpl::single(float64(left)), @@ -108,16 +136,16 @@ void BoostBox( lt_post, std::move(post), Ui::Text::RichLangValue)) - : (!data.boost.level + : (!counters.level ? tr::lng_boost_channel_reached_first( Ui::Text::RichLangValue) : tr::lng_boost_channel_reached_more( lt_count, - rpl::single(float64(data.boost.level + 1)), + rpl::single(float64(counters.level)), lt_post, std::move(post), Ui::Text::RichLangValue))) - : !data.boost.level + : !counters.level ? tr::lng_boost_channel_needs_first( lt_count, rpl::single(float64(left)), @@ -133,12 +161,14 @@ void BoostBox( std::move(post), Ui::Text::RichLangValue); }) | rpl::flatten_latest(); + box->addRow( object_ptr( box, std::move(title), st::boostTitle), st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); + box->addRow( object_ptr( box, @@ -147,28 +177,83 @@ void BoostBox( (st::boxRowPadding + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); - auto submit = full - ? (tr::lng_box_ok() | rpl::type_erased()) - : state->you.value( - ) | rpl::map([](bool mine) { - return mine ? tr::lng_box_ok() : tr::lng_boost_channel_button(); - }) | rpl::flatten_latest(); + auto submit = state->data.value( + ) | rpl::map([=](BoostCounters counters) { + return !counters.nextLevelBoosts + ? tr::lng_box_ok() + : (counters.mine > 0) + ? tr::lng_boost_again_button() + : tr::lng_boost_channel_button(); + }) | rpl::flatten_latest(); + const auto button = box->addButton(rpl::duplicate(submit), [=] { if (state->submitted) { return; - } else if (!full && !state->you.current()) { + } else if (state->data.current().nextLevelBoosts > 0) { state->submitted = true; - boost(crl::guard(box, [=](bool success) { + const auto was = state->data.current().mine; + + //AssertIsDebug(); + //state->submitted = false; + //if (state->data.current().level == 5 + // && state->data.current().boosts == 11) { + // state->data = BoostCounters{ + // .level = 5, + // .boosts = 14, + // .thisLevelBoosts = 9, + // .nextLevelBoosts = 15, + // .mine = 14, + // }; + //} else if (state->data.current().level == 5) { + // state->data = BoostCounters{ + // .level = 7, + // .boosts = 16, + // .thisLevelBoosts = 15, + // .nextLevelBoosts = 19, + // .mine = 16, + // }; + //} else if (state->data.current().level == 4) { + // state->data = BoostCounters{ + // .level = 5, + // .boosts = 11, + // .thisLevelBoosts = 9, + // .nextLevelBoosts = 15, + // .mine = 9, + // }; + //} else if (state->data.current().level == 3) { + // state->data = BoostCounters{ + // .level = 4, + // .boosts = 7, + // .thisLevelBoosts = 7, + // .nextLevelBoosts = 9, + // .mine = 5, + // }; + //} else { + // state->data = BoostCounters{ + // .level = 3, + // .boosts = 5, + // .thisLevelBoosts = 5, + // .nextLevelBoosts = 7, + // .mine = 3, + // }; + //} + //return; + + boost(crl::guard(box, [=](BoostCounters result) { state->submitted = false; - if (success) { - StartFireworks(box->parentWidget()); - state->you = true; + + if (result.thisLevelBoosts || result.nextLevelBoosts) { + if (result.mine > was) { + StartFireworks(box->parentWidget()); + } + state->data = result; } })); } else { box->closeBox(); } }); + rpl::combine( std::move(submit), box->widthValue() @@ -270,18 +355,14 @@ void AskBoostBox( box->setStyle(st::boostBox); struct State { - rpl::variable you = false; bool submitted = false; }; - const auto state = box->lifetime().make_state(State{ - .you = data.boost.mine, - }); + const auto state = box->lifetime().make_state(); FillBoostLimit( BoxShowFinishes(box), - state->you.value(), box->verticalLayout(), - data.boost, + rpl::single(data.boost), st::boxRowPadding); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); @@ -338,56 +419,22 @@ void AskBoostBox( void FillBoostLimit( rpl::producer<> showFinished, - rpl::producer you, not_null container, - BoostCounters data, + rpl::producer data, style::margins limitLinePadding) { - const auto full = !data.nextLevelBoosts; - - if (data.mine && data.boosts > 0) { - --data.boosts; - } - - if (full) { - data.nextLevelBoosts = data.boosts - + (data.mine ? 1 : 0); - data.thisLevelBoosts = 0; - if (data.level > 0) { - --data.level; - } - } else if (data.mine - && data.level > 0 - && data.boosts < data.thisLevelBoosts) { - --data.level; - data.nextLevelBoosts = data.thisLevelBoosts; - data.thisLevelBoosts = 0; - } - const auto addSkip = [&](int skip) { container->add(object_ptr(container, skip)); }; addSkip(st::boostSkipTop); - const auto levelWidth = [&](int add) { - return st::normalFont->width( - tr::lng_boost_level(tr::now, lt_count, data.level + add)); - }; - const auto paddings = 2 * st::premiumLineTextSkip; - const auto labelLeftWidth = paddings + levelWidth(0); - const auto labelRightWidth = paddings + levelWidth(1); - const auto ratio = [=](int boosts) { - const auto min = std::min( - data.boosts, - data.thisLevelBoosts); - const auto max = std::max({ - data.boosts, - data.nextLevelBoosts, - 1, - }); - Assert(boosts >= min && boosts <= max); + const auto ratio = [=](BoostCounters counters) { + const auto min = counters.thisLevelBoosts; + const auto max = counters.nextLevelBoosts; + + Assert(counters.boosts >= min && counters.boosts <= max); const auto count = (max - min); - const auto index = (boosts - min); + const auto index = (counters.boosts - min); if (!index) { return 0.; } else if (index == count) { @@ -399,26 +446,33 @@ void FillBoostLimit( - st::boxPadding.left() - st::boxPadding.right(); const auto average = available / float64(count); + const auto levelWidth = [&](int add) { + return st::normalFont->width( + tr::lng_boost_level( + tr::now, + lt_count, + counters.level + add)); + }; + const auto paddings = 2 * st::premiumLineTextSkip; + const auto labelLeftWidth = paddings + levelWidth(0); + const auto labelRightWidth = paddings + levelWidth(1); const auto first = std::max(average, labelLeftWidth * 1.); const auto last = std::max(average, labelRightWidth * 1.); const auto other = (available - first - last) / (count - 2); return (first + (index - 1) * other) / available; }; - const auto min = std::min(data.boosts, data.thisLevelBoosts); - const auto now = data.boosts; - const auto max = (data.nextLevelBoosts > min) - ? (data.nextLevelBoosts) - : (data.boosts > 0) - ? data.boosts - : 1; - auto bubbleRowState = ( - std::move(you) - ) | rpl::map([=](bool mine) { - const auto index = mine ? (now + 1) : now; + auto adjustedData = rpl::duplicate(data) | rpl::map(AdjustByReached); + + auto bubbleRowState = rpl::duplicate( + adjustedData + ) | rpl::combine_previous( + BoostCounters() + ) | rpl::map([=](BoostCounters previous, BoostCounters counters) { return Premium::BubbleRowState{ - .counter = index, - .ratio = ratio(index), + .counter = counters.boosts, + .ratio = ratio(counters), + .animateFromZero = (counters.level != previous.level), .dynamic = true, }; }); @@ -427,7 +481,6 @@ void FillBoostLimit( st::boostBubble, std::move(showFinished), rpl::duplicate(bubbleRowState), - max, true, nullptr, &st::premiumIconBoost, @@ -437,20 +490,33 @@ void FillBoostLimit( const auto level = [](int level) { return tr::lng_boost_level(tr::now, lt_count, level); }; - auto ratioValue = std::move( + auto limitState = std::move( bubbleRowState ) | rpl::map([](const Premium::BubbleRowState &state) { - return state.ratio; + return Premium::LimitRowState{ + .ratio = state.ratio, + .animateFromZero = state.animateFromZero, + .dynamic = state.dynamic + }; + }); + auto left = rpl::duplicate( + adjustedData + ) | rpl::map([=](BoostCounters counters) { + return level(counters.level); + }); + auto right = rpl::duplicate( + adjustedData + ) | rpl::map([=](BoostCounters counters) { + return level(counters.level + 1); }); Premium::AddLimitRow( container, st::boostLimits, Premium::LimitRowLabels{ - .leftLabel = level(data.level), - .rightLabel = level(data.level + 1), - .dynamic = true, + .leftLabel = std::move(left), + .rightLabel = std::move(right), }, - std::move(ratioValue), + std::move(limitState), limitLinePadding); } diff --git a/Telegram/SourceFiles/ui/boxes/boost_box.h b/Telegram/SourceFiles/ui/boxes/boost_box.h index da8d57572..e8ef31993 100644 --- a/Telegram/SourceFiles/ui/boxes/boost_box.h +++ b/Telegram/SourceFiles/ui/boxes/boost_box.h @@ -23,7 +23,11 @@ struct BoostCounters { int boosts = 0; int thisLevelBoosts = 0; int nextLevelBoosts = 0; // Zero means no next level is available. - bool mine = false; + int mine = 0; + + friend inline constexpr bool operator==( + BoostCounters, + BoostCounters) = default; }; struct BoostBoxData { @@ -34,7 +38,7 @@ struct BoostBoxData { void BoostBox( not_null box, BoostBoxData data, - Fn)> boost); + Fn)> boost); struct AskBoostBoxData { QString link; @@ -57,9 +61,8 @@ void AskBoostBox( void FillBoostLimit( rpl::producer<> showFinished, - rpl::producer you, not_null container, - BoostCounters data, + rpl::producer data, style::margins limitLinePadding); } // namespace Ui diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp index c7c8cadbe..6c134a8b8 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.cpp +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.cpp @@ -195,7 +195,7 @@ public: [[nodiscard]] int height() const; [[nodiscard]] int width() const; [[nodiscard]] int bubbleRadius() const; - [[nodiscard]] int countMaxWidth(int maxCounter) const; + [[nodiscard]] int countMaxWidth(int maxPossibleCounter) const; void setCounter(int value); void setTailEdge(EdgeProgress edge); @@ -271,12 +271,12 @@ int Bubble::width() const { return filledWidth() + _numberAnimation.countWidth(); } -int Bubble::countMaxWidth(int maxCounter) const { +int Bubble::countMaxWidth(int maxPossibleCounter) const { auto numbers = Ui::NumbersAnimation(_st.font, [] {}); numbers.setDisabledMonospace(true); numbers.setDuration(0); numbers.setText(_textFactory(0), 0); - numbers.setText(_textFactory(maxCounter), maxCounter); + numbers.setText(_textFactory(maxPossibleCounter), maxPossibleCounter); numbers.finishAnimating(); return filledWidth() + numbers.maxWidth(); } @@ -389,7 +389,6 @@ public: const style::PremiumBubble &st, TextFactory textFactory, rpl::producer state, - int maxCounter, bool premiumPossible, rpl::producer<> showFinishes, const style::icon *icon, @@ -414,9 +413,8 @@ private: BubbleRowState _animatingFrom; float64 _animatingFromResultRatio = 0.; rpl::variable _state; - const int _maxCounter; Bubble _bubble; - const int _maxBubbleWidth; + int _maxBubbleWidth = 0; const bool _premiumPossible; const style::margins _outerPadding; @@ -439,7 +437,6 @@ BubbleWidget::BubbleWidget( const style::PremiumBubble &st, TextFactory textFactory, rpl::producer state, - int maxCounter, bool premiumPossible, rpl::producer<> showFinishes, const style::icon *icon, @@ -447,14 +444,12 @@ BubbleWidget::BubbleWidget( : RpWidget(parent) , _st(st) , _state(std::move(state)) -, _maxCounter(maxCounter) , _bubble( _st, [=] { update(); }, std::move(textFactory), icon, premiumPossible) -, _maxBubbleWidth(_bubble.countMaxWidth(_maxCounter)) , _premiumPossible(premiumPossible) , _outerPadding(outerPadding) , _deflection(kDeflection) @@ -485,6 +480,7 @@ BubbleWidget::BubbleWidget( } void BubbleWidget::animateTo(BubbleRowState state) { + _maxBubbleWidth = _bubble.countMaxWidth(state.counter); const auto parent = parentWidget(); const auto computeLeft = [=](float64 pointRatio, float64 animProgress) { const auto halfWidth = (_maxBubbleWidth / 2); @@ -541,6 +537,11 @@ void BubbleWidget::animateTo(BubbleRowState state) { const auto duration = kSlideDuration * (_ignoreDeflection ? kStepBeforeDeflection : 1.) * ((_state.current().ratio < 0.001) ? 0.5 : 1.); + if (state.animateFromZero) { + _animatingFrom.ratio = 0.; + _animatingFrom.counter = 0; + _animatingFromResultRatio = 0.; + } _appearanceAnimation.start([=](float64 value) { if (!_appearanceAnimation.animating()) { _animatingFrom = state; @@ -658,7 +659,7 @@ public: not_null parent, const style::PremiumLimits &st, LimitRowLabels labels, - rpl::producer ratio); + rpl::producer state); void setColorOverride(QBrush brush); @@ -675,6 +676,7 @@ private: float64 _ratio = 0.; Ui::Animations::Simple _animation; + rpl::event_stream<> _recaches; Ui::Text::String _leftLabel; Ui::Text::String _leftText; Ui::Text::String _rightLabel; @@ -707,44 +709,56 @@ Line::Line( QString min, float64 ratio) : Line(parent, st, LimitRowLabels{ - .leftLabel = tr::lng_premium_free(tr::now), - .leftCount = min, - .rightLabel = tr::lng_premium(tr::now), - .rightCount = max, -}, rpl::single(ratio)) { + .leftLabel = tr::lng_premium_free(), + .leftCount = rpl::single(min), + .rightLabel = tr::lng_premium(), + .rightCount = rpl::single(max), +}, rpl::single(LimitRowState{ ratio })) { } Line::Line( not_null parent, const style::PremiumLimits &st, LimitRowLabels labels, - rpl::producer ratio) + rpl::producer state) : Ui::RpWidget(parent) -, _st(st) -, _leftLabel(st::semiboldTextStyle, labels.leftLabel) -, _leftText(st::semiboldTextStyle, labels.leftCount) -, _rightLabel(st::semiboldTextStyle, labels.rightLabel) -, _rightText(st::semiboldTextStyle, labels.rightCount) -, _dynamic(labels.dynamic) { +, _st(st) { resize(width(), st::requestsAcceptButton.height); - std::move(ratio) | rpl::start_with_next([=](float64 ratio) { + const auto set = [&]( + Ui::Text::String &label, + rpl::producer &text) { + std::move(text) | rpl::start_with_next([=, &label](QString text) { + label = { st::semiboldTextStyle, text }; + _recaches.fire({}); + }, lifetime()); + }; + set(_leftLabel, labels.leftLabel); + set(_leftText, labels.leftCount); + set(_rightLabel, labels.rightLabel); + set(_rightText, labels.rightCount); + + std::move(state) | rpl::start_with_next([=](LimitRowState state) { + _dynamic = state.dynamic; if (width() > 0) { - const auto from = _animation.value(_ratio); + const auto from = state.animateFromZero + ? 0. + : _animation.value(_ratio); const auto duration = kSlideDuration * kStepBeforeDeflection; _animation.start([=] { update(); - }, from, ratio, duration, anim::easeOutCirc); + }, from, state.ratio, duration, anim::easeOutCirc); } - _ratio = ratio; + _ratio = state.ratio; }, lifetime()); rpl::combine( sizeValue(), - parent->widthValue() - ) | rpl::filter([](const QSize &size, int parentWidth) { + parent->widthValue(), + _recaches.events_starting_with({}) + ) | rpl::filter([](const QSize &size, int parentWidth, auto) { return !size.isEmpty() && parentWidth; - }) | rpl::start_with_next([=](const QSize &size, int) { + }) | rpl::start_with_next([=](const QSize &size, auto, auto) { recache(size); update(); }, lifetime()); @@ -906,7 +920,6 @@ void AddBubbleRow( .counter = current, .ratio = (current - min) / float64(max - min), }), - max, premiumPossible, ProcessTextFactory(phrase), icon, @@ -918,7 +931,6 @@ void AddBubbleRow( const style::PremiumBubble &st, rpl::producer<> showFinishes, rpl::producer state, - int max, bool premiumPossible, Fn text, const style::icon *icon, @@ -930,7 +942,6 @@ void AddBubbleRow( st, text ? std::move(text) : ProcessTextFactory(std::nullopt), std::move(state), - max, premiumPossible, std::move(showFinishes), icon, @@ -975,10 +986,10 @@ void AddLimitRow( not_null parent, const style::PremiumLimits &st, LimitRowLabels labels, - rpl::producer ratio, + rpl::producer state, const style::margins &padding) { parent->add( - object_ptr(parent, st, std::move(labels), std::move(ratio)), + object_ptr(parent, st, std::move(labels), std::move(state)), padding); } diff --git a/Telegram/SourceFiles/ui/effects/premium_graphics.h b/Telegram/SourceFiles/ui/effects/premium_graphics.h index 1292a6980..728dd9d40 100644 --- a/Telegram/SourceFiles/ui/effects/premium_graphics.h +++ b/Telegram/SourceFiles/ui/effects/premium_graphics.h @@ -55,6 +55,7 @@ void AddBubbleRow( struct BubbleRowState { int counter = 0; float64 ratio = 0.; + bool animateFromZero = false; bool dynamic = false; }; void AddBubbleRow( @@ -62,7 +63,6 @@ void AddBubbleRow( const style::PremiumBubble &st, rpl::producer<> showFinishes, rpl::producer state, - int max, bool premiumPossible, Fn text, const style::icon *icon, @@ -84,17 +84,23 @@ void AddLimitRow( float64 ratio = kLimitRowRatio); struct LimitRowLabels { - QString leftLabel; - QString leftCount; - QString rightLabel; - QString rightCount; + rpl::producer leftLabel; + rpl::producer leftCount; + rpl::producer rightLabel; + rpl::producer rightCount; +}; + +struct LimitRowState { + float64 ratio = 0.; + bool animateFromZero = false; bool dynamic = false; }; + void AddLimitRow( not_null parent, const style::PremiumLimits &st, LimitRowLabels labels, - rpl::producer ratio, + rpl::producer state, const style::margins &padding); struct AccountsRowArgs final { diff --git a/Telegram/SourceFiles/window/window_session_controller.cpp b/Telegram/SourceFiles/window/window_session_controller.cpp index b6822f2d4..2cb64bcca 100644 --- a/Telegram/SourceFiles/window/window_session_controller.cpp +++ b/Telegram/SourceFiles/window/window_session_controller.cpp @@ -267,6 +267,19 @@ Fn PausedIn( return [=] { return IsPaused(controller, level); }; } +Ui::BoostCounters ParseBoostCounters( + const MTPpremium_BoostsStatus &status) { + const auto &data = status.data(); + const auto slots = data.vmy_boost_slots(); + return { + .level = data.vlevel().v, + .boosts = data.vboosts().v, + .thisLevelBoosts = data.vcurrent_level_boosts().v, + .nextLevelBoosts = data.vnext_level_boosts().value_or_empty(), + .mine = slots ? slots->v.size() : 0, + }; +} + bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b) { return (a.peer == b.peer) && (a.theme == b.theme); } @@ -629,20 +642,12 @@ void SessionNavigation::resolveBoostState(not_null channel) { channel->input )).done([=](const MTPpremium_BoostsStatus &result) { _boostStateResolving = nullptr; - const auto &data = result.data(); - const auto submit = [=](Fn done) { + const auto submit = [=](Fn done) { applyBoost(channel, done); }; - const auto next = data.vnext_level_boosts().value_or_empty(); uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{ .name = channel->name(), - .boost = { - .level = data.vlevel().v, - .boosts = data.vboosts().v, - .thisLevelBoosts = data.vcurrent_level_boosts().v, - .nextLevelBoosts = next, - .mine = data.is_my_boost(), - }, + .boost = ParseBoostCounters(result), }, submit)); }).fail([=](const MTP::Error &error) { _boostStateResolving = nullptr; @@ -652,7 +657,7 @@ void SessionNavigation::resolveBoostState(not_null channel) { void SessionNavigation::applyBoost( not_null channel, - Fn done) { + Fn done) { _api.request(MTPpremium_GetMyBoosts( )).done([=](const MTPpremium_MyBoosts &result) { const auto &data = result.data(); @@ -682,7 +687,7 @@ void SessionNavigation::applyBoost( .inform = true, })); } - done(false); + done({}); return; } auto slot = int(); @@ -725,7 +730,7 @@ void SessionNavigation::applyBoost( .title = tr::lng_boost_error_flood_title(), .inform = true, })); - done(false); + done({}); } else { const auto peer = _session->data().peer(different); replaceBoostConfirm(peer, channel, slot, done); @@ -737,12 +742,12 @@ void SessionNavigation::applyBoost( .title = tr::lng_boost_error_already_title(), .inform = true, })); - done(false); + done({}); } }).fail([=](const MTP::Error &error) { const auto type = error.type(); showToast(u"Error: "_q + type); - done(false); + done({}); }).handleFloodErrors().send(); } @@ -750,7 +755,7 @@ void SessionNavigation::replaceBoostConfirm( not_null from, not_null channel, int slot, - Fn done) { + Fn done) { const auto forwarded = std::make_shared(false); const auto confirmed = [=](Fn close) { *forwarded = true; @@ -777,23 +782,30 @@ void SessionNavigation::replaceBoostConfirm( box->boxClosing() | rpl::filter([=] { return !*forwarded; }) | rpl::start_with_next([=] { - done(false); + done({}); }, box->lifetime()); } void SessionNavigation::applyBoostChecked( not_null channel, int slot, - Fn done) { + Fn done) { _api.request(MTPpremium_ApplyBoost( MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots), MTP_vector({ MTP_int(slot) }), channel->input )).done([=](const MTPpremium_MyBoosts &result) { - done(true); + _api.request(MTPpremium_GetBoostsStatus( + channel->input + )).done([=](const MTPpremium_BoostsStatus &result) { + done(ParseBoostCounters(result)); + }).fail([=](const MTP::Error &error) { + showToast(u"Error: "_q + error.type()); + done({}); + }).send(); }).fail([=](const MTP::Error &error) { showToast(u"Error: "_q + error.type()); - done(false); + done({}); }).send(); } diff --git a/Telegram/SourceFiles/window/window_session_controller.h b/Telegram/SourceFiles/window/window_session_controller.h index 2ae888937..aecd4150d 100644 --- a/Telegram/SourceFiles/window/window_session_controller.h +++ b/Telegram/SourceFiles/window/window_session_controller.h @@ -68,6 +68,7 @@ struct ChatPaintContext; struct ChatThemeBackground; struct ChatThemeBackgroundData; class MessageSendingAnimationController; +struct BoostCounters; } // namespace Ui namespace Data { @@ -314,16 +315,18 @@ private: const PeerByLinkInfo &info); void resolveBoostState(not_null channel); - void applyBoost(not_null channel, Fn done); + void applyBoost( + not_null channel, + Fn done); void replaceBoostConfirm( not_null from, not_null channel, int slot, - Fn done); + Fn done); void applyBoostChecked( not_null channel, int slot, - Fn done); + Fn done); const not_null _session; @@ -752,4 +755,7 @@ void ActivateWindow(not_null controller); not_null controller, GifPauseReason level); +[[nodiscard]] Ui::BoostCounters ParseBoostCounters( + const MTPpremium_BoostsStatus &status); + } // namespace Window