Start multiboosts, support dynamic state.

This commit is contained in:
John Preston 2023-11-07 17:58:20 +04:00
parent 2d67557a91
commit a41bbd27c8
11 changed files with 300 additions and 191 deletions

View file

@ -2030,6 +2030,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_gift_terms_link" = "here"; "lng_premium_gift_terms_link" = "here";
"lng_boost_channel_button" = "Boost Channel"; "lng_boost_channel_button" = "Boost Channel";
"lng_boost_again_button" = "Boost Again";
"lng_boost_level#one" = "Level {count}"; "lng_boost_level#one" = "Level {count}";
"lng_boost_level#other" = "Level {count}"; "lng_boost_level#other" = "Level {count}";
"lng_boost_channel_title_first" = "Enable stories for channel"; "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_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_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
"lng_boost_now_replace" = "Replace"; "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_title_color" = "Enable colors";
"lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color."; "lng_boost_channel_needs_level_color#one" = "Your channel needs to reach **Level {count}** to change channel color.";

View file

@ -519,7 +519,9 @@ rpl::producer<rpl::no_value, QString> Boosts::request() {
: 0; : 0;
_boostStatus.overview = Data::BoostsOverview{ _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), .level = std::max(data.vlevel().v, 0),
.boostCount = std::max( .boostCount = std::max(
data.vboosts().v, data.vboosts().v,

View file

@ -514,21 +514,17 @@ void Apply(
close(); close();
return; return;
} }
const auto next = data.vnext_level_boosts().value_or_empty();
const auto openStatistics = [=] { const auto openStatistics = [=] {
if (const auto controller = show->resolveWindow( if (const auto controller = show->resolveWindow(
ChatHelpers::WindowUsage::PremiumPromo)) { ChatHelpers::WindowUsage::PremiumPromo)) {
controller->showSection(Info::Boosts::Make(peer)); 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{ show->show(Box(Ui::AskBoostBox, Ui::AskBoostBoxData{
.link = qs(data.vboost_url()), .link = qs(data.vboost_url()),
.boost = { .boost = counters,
.level = data.vlevel().v,
.boosts = data.vboosts().v,
.thisLevelBoosts = data.vcurrent_level_boosts().v,
.nextLevelBoosts = next,
},
.requiredLevel = required, .requiredLevel = required,
}, openStatistics, nullptr)); }, openStatistics, nullptr));
cancel(); cancel();

View file

@ -10,7 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Data { namespace Data {
struct BoostsOverview final { struct BoostsOverview final {
bool isBoosted = false; int mine = 0;
int level = 0; int level = 0;
int boostCount = 0; int boostCount = 0;
int currentLevelBoostCount = 0; int currentLevelBoostCount = 0;

View file

@ -302,17 +302,16 @@ void InnerWidget::fill() {
auto dividerContent = object_ptr<Ui::VerticalLayout>(inner); auto dividerContent = object_ptr<Ui::VerticalLayout>(inner);
Ui::FillBoostLimit( Ui::FillBoostLimit(
fakeShowed->events(), fakeShowed->events(),
rpl::single(status.overview.isBoosted),
dividerContent.data(), dividerContent.data(),
Ui::BoostCounters{ rpl::single(Ui::BoostCounters{
.level = status.overview.level, .level = status.overview.level,
.boosts = status.overview.boostCount, .boosts = status.overview.boostCount,
.thisLevelBoosts .thisLevelBoosts
= status.overview.currentLevelBoostCount, = status.overview.currentLevelBoostCount,
.nextLevelBoosts .nextLevelBoosts
= status.overview.nextLevelBoostCount, = status.overview.nextLevelBoostCount,
.mine = status.overview.isBoosted, .mine = status.overview.mine,
}, }),
st::statisticsLimitsLinePadding); st::statisticsLimitsLinePadding);
inner->add(object_ptr<Ui::DividerLabel>( inner->add(object_ptr<Ui::DividerLabel>(
inner, inner,

View file

@ -20,6 +20,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include <QtGui/QGuiApplication> #include <QtGui/QGuiApplication>
namespace Ui { 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<QWidget*> parent) { void StartFireworks(not_null<QWidget*> parent) {
const auto result = Ui::CreateChild<RpWidget>(parent.get()); const auto result = Ui::CreateChild<RpWidget>(parent.get());
@ -42,62 +67,65 @@ void StartFireworks(not_null<QWidget*> parent) {
void BoostBox( void BoostBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
BoostBoxData data, BoostBoxData data,
Fn<void(Fn<void(bool)>)> boost) { Fn<void(Fn<void(BoostCounters)>)> boost) {
box->setWidth(st::boxWideWidth); box->setWidth(st::boxWideWidth);
box->setStyle(st::boostBox); 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 { struct State {
rpl::variable<bool> you = false; rpl::variable<BoostCounters> data;
rpl::variable<bool> full;
bool submitted = false; bool submitted = false;
}; };
const auto state = box->lifetime().make_state<State>(State{ const auto state = box->lifetime().make_state<State>();
.you = data.boost.mine, state->data = std::move(data.boost);
});
FillBoostLimit( FillBoostLimit(
BoxShowFinishes(box), BoxShowFinishes(box),
state->you.value(),
box->verticalLayout(), box->verticalLayout(),
data.boost, state->data.value(),
st::boxRowPadding); 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(); }); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
const auto name = data.name; 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( ? tr::lng_boost_channel_you_title(
lt_channel, lt_channel,
rpl::single(data.name)) rpl::single(name))
: full : !counters.nextLevelBoosts
? tr::lng_boost_channel_title_max() ? tr::lng_boost_channel_title_max()
: !data.boost.level : !counters.level
? tr::lng_boost_channel_title_first() ? tr::lng_boost_channel_title_first()
: tr::lng_boost_channel_title_more(); : tr::lng_boost_channel_title_more();
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
auto text = state->you.value() | rpl::map([=](bool your) {
const auto bold = Ui::Text::Bold(data.name); auto text = state->data.value(
const auto now = data.boost.boosts + (your ? 1 : 0); ) | rpl::map([=](BoostCounters counters) {
const auto left = (data.boost.nextLevelBoosts > now) const auto bold = Ui::Text::Bold(name);
? (data.boost.nextLevelBoosts - now) const auto now = counters.boosts;
const auto full = !counters.nextLevelBoosts;
const auto left = (counters.nextLevelBoosts > now)
? (counters.nextLevelBoosts - now)
: 0; : 0;
auto post = tr::lng_boost_channel_post_stories( auto post = tr::lng_boost_channel_post_stories(
lt_count, lt_count,
rpl::single(float64(data.boost.level + 1)), rpl::single(float64(counters.level + (left ? 1 : 0))),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
return (your || full) return (counters.mine || full)
? ((!full && left > 0) ? (left
? (!data.boost.level ? (!counters.level
? tr::lng_boost_channel_you_first( ? tr::lng_boost_channel_you_first(
lt_count, lt_count,
rpl::single(float64(left)), rpl::single(float64(left)),
@ -108,16 +136,16 @@ void BoostBox(
lt_post, lt_post,
std::move(post), std::move(post),
Ui::Text::RichLangValue)) Ui::Text::RichLangValue))
: (!data.boost.level : (!counters.level
? tr::lng_boost_channel_reached_first( ? tr::lng_boost_channel_reached_first(
Ui::Text::RichLangValue) Ui::Text::RichLangValue)
: tr::lng_boost_channel_reached_more( : tr::lng_boost_channel_reached_more(
lt_count, lt_count,
rpl::single(float64(data.boost.level + 1)), rpl::single(float64(counters.level)),
lt_post, lt_post,
std::move(post), std::move(post),
Ui::Text::RichLangValue))) Ui::Text::RichLangValue)))
: !data.boost.level : !counters.level
? tr::lng_boost_channel_needs_first( ? tr::lng_boost_channel_needs_first(
lt_count, lt_count,
rpl::single(float64(left)), rpl::single(float64(left)),
@ -133,12 +161,14 @@ void BoostBox(
std::move(post), std::move(post),
Ui::Text::RichLangValue); Ui::Text::RichLangValue);
}) | rpl::flatten_latest(); }) | rpl::flatten_latest();
box->addRow( box->addRow(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
box, box,
std::move(title), std::move(title),
st::boostTitle), st::boostTitle),
st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0)); st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
box->addRow( box->addRow(
object_ptr<Ui::FlatLabel>( object_ptr<Ui::FlatLabel>(
box, box,
@ -147,28 +177,83 @@ void BoostBox(
(st::boxRowPadding (st::boxRowPadding
+ QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip))); + QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
auto submit = full auto submit = state->data.value(
? (tr::lng_box_ok() | rpl::type_erased()) ) | rpl::map([=](BoostCounters counters) {
: state->you.value( return !counters.nextLevelBoosts
) | rpl::map([](bool mine) { ? tr::lng_box_ok()
return mine ? tr::lng_box_ok() : tr::lng_boost_channel_button(); : (counters.mine > 0)
}) | rpl::flatten_latest(); ? tr::lng_boost_again_button()
: tr::lng_boost_channel_button();
}) | rpl::flatten_latest();
const auto button = box->addButton(rpl::duplicate(submit), [=] { const auto button = box->addButton(rpl::duplicate(submit), [=] {
if (state->submitted) { if (state->submitted) {
return; return;
} else if (!full && !state->you.current()) { } else if (state->data.current().nextLevelBoosts > 0) {
state->submitted = true; 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; state->submitted = false;
if (success) {
StartFireworks(box->parentWidget()); if (result.thisLevelBoosts || result.nextLevelBoosts) {
state->you = true; if (result.mine > was) {
StartFireworks(box->parentWidget());
}
state->data = result;
} }
})); }));
} else { } else {
box->closeBox(); box->closeBox();
} }
}); });
rpl::combine( rpl::combine(
std::move(submit), std::move(submit),
box->widthValue() box->widthValue()
@ -270,18 +355,14 @@ void AskBoostBox(
box->setStyle(st::boostBox); box->setStyle(st::boostBox);
struct State { struct State {
rpl::variable<bool> you = false;
bool submitted = false; bool submitted = false;
}; };
const auto state = box->lifetime().make_state<State>(State{ const auto state = box->lifetime().make_state<State>();
.you = data.boost.mine,
});
FillBoostLimit( FillBoostLimit(
BoxShowFinishes(box), BoxShowFinishes(box),
state->you.value(),
box->verticalLayout(), box->verticalLayout(),
data.boost, rpl::single(data.boost),
st::boxRowPadding); st::boxRowPadding);
box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); }); box->addTopButton(st::boxTitleClose, [=] { box->closeBox(); });
@ -338,56 +419,22 @@ void AskBoostBox(
void FillBoostLimit( void FillBoostLimit(
rpl::producer<> showFinished, rpl::producer<> showFinished,
rpl::producer<bool> you,
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
BoostCounters data, rpl::producer<BoostCounters> data,
style::margins limitLinePadding) { 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) { const auto addSkip = [&](int skip) {
container->add(object_ptr<Ui::FixedHeightWidget>(container, skip)); container->add(object_ptr<Ui::FixedHeightWidget>(container, skip));
}; };
addSkip(st::boostSkipTop); addSkip(st::boostSkipTop);
const auto levelWidth = [&](int add) { const auto ratio = [=](BoostCounters counters) {
return st::normalFont->width( const auto min = counters.thisLevelBoosts;
tr::lng_boost_level(tr::now, lt_count, data.level + add)); const auto max = counters.nextLevelBoosts;
};
const auto paddings = 2 * st::premiumLineTextSkip; Assert(counters.boosts >= min && counters.boosts <= max);
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 count = (max - min); const auto count = (max - min);
const auto index = (boosts - min); const auto index = (counters.boosts - min);
if (!index) { if (!index) {
return 0.; return 0.;
} else if (index == count) { } else if (index == count) {
@ -399,26 +446,33 @@ void FillBoostLimit(
- st::boxPadding.left() - st::boxPadding.left()
- st::boxPadding.right(); - st::boxPadding.right();
const auto average = available / float64(count); 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 first = std::max(average, labelLeftWidth * 1.);
const auto last = std::max(average, labelRightWidth * 1.); const auto last = std::max(average, labelRightWidth * 1.);
const auto other = (available - first - last) / (count - 2); const auto other = (available - first - last) / (count - 2);
return (first + (index - 1) * other) / available; return (first + (index - 1) * other) / available;
}; };
const auto min = std::min(data.boosts, data.thisLevelBoosts); auto adjustedData = rpl::duplicate(data) | rpl::map(AdjustByReached);
const auto now = data.boosts;
const auto max = (data.nextLevelBoosts > min) auto bubbleRowState = rpl::duplicate(
? (data.nextLevelBoosts) adjustedData
: (data.boosts > 0) ) | rpl::combine_previous(
? data.boosts BoostCounters()
: 1; ) | rpl::map([=](BoostCounters previous, BoostCounters counters) {
auto bubbleRowState = (
std::move(you)
) | rpl::map([=](bool mine) {
const auto index = mine ? (now + 1) : now;
return Premium::BubbleRowState{ return Premium::BubbleRowState{
.counter = index, .counter = counters.boosts,
.ratio = ratio(index), .ratio = ratio(counters),
.animateFromZero = (counters.level != previous.level),
.dynamic = true, .dynamic = true,
}; };
}); });
@ -427,7 +481,6 @@ void FillBoostLimit(
st::boostBubble, st::boostBubble,
std::move(showFinished), std::move(showFinished),
rpl::duplicate(bubbleRowState), rpl::duplicate(bubbleRowState),
max,
true, true,
nullptr, nullptr,
&st::premiumIconBoost, &st::premiumIconBoost,
@ -437,20 +490,33 @@ void FillBoostLimit(
const auto level = [](int level) { const auto level = [](int level) {
return tr::lng_boost_level(tr::now, lt_count, level); return tr::lng_boost_level(tr::now, lt_count, level);
}; };
auto ratioValue = std::move( auto limitState = std::move(
bubbleRowState bubbleRowState
) | rpl::map([](const Premium::BubbleRowState &state) { ) | 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( Premium::AddLimitRow(
container, container,
st::boostLimits, st::boostLimits,
Premium::LimitRowLabels{ Premium::LimitRowLabels{
.leftLabel = level(data.level), .leftLabel = std::move(left),
.rightLabel = level(data.level + 1), .rightLabel = std::move(right),
.dynamic = true,
}, },
std::move(ratioValue), std::move(limitState),
limitLinePadding); limitLinePadding);
} }

View file

@ -23,7 +23,11 @@ struct BoostCounters {
int boosts = 0; int boosts = 0;
int thisLevelBoosts = 0; int thisLevelBoosts = 0;
int nextLevelBoosts = 0; // Zero means no next level is available. 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 { struct BoostBoxData {
@ -34,7 +38,7 @@ struct BoostBoxData {
void BoostBox( void BoostBox(
not_null<GenericBox*> box, not_null<GenericBox*> box,
BoostBoxData data, BoostBoxData data,
Fn<void(Fn<void(bool)>)> boost); Fn<void(Fn<void(BoostCounters)>)> boost);
struct AskBoostBoxData { struct AskBoostBoxData {
QString link; QString link;
@ -57,9 +61,8 @@ void AskBoostBox(
void FillBoostLimit( void FillBoostLimit(
rpl::producer<> showFinished, rpl::producer<> showFinished,
rpl::producer<bool> you,
not_null<VerticalLayout*> container, not_null<VerticalLayout*> container,
BoostCounters data, rpl::producer<BoostCounters> data,
style::margins limitLinePadding); style::margins limitLinePadding);
} // namespace Ui } // namespace Ui

View file

@ -195,7 +195,7 @@ public:
[[nodiscard]] int height() const; [[nodiscard]] int height() const;
[[nodiscard]] int width() const; [[nodiscard]] int width() const;
[[nodiscard]] int bubbleRadius() const; [[nodiscard]] int bubbleRadius() const;
[[nodiscard]] int countMaxWidth(int maxCounter) const; [[nodiscard]] int countMaxWidth(int maxPossibleCounter) const;
void setCounter(int value); void setCounter(int value);
void setTailEdge(EdgeProgress edge); void setTailEdge(EdgeProgress edge);
@ -271,12 +271,12 @@ int Bubble::width() const {
return filledWidth() + _numberAnimation.countWidth(); return filledWidth() + _numberAnimation.countWidth();
} }
int Bubble::countMaxWidth(int maxCounter) const { int Bubble::countMaxWidth(int maxPossibleCounter) const {
auto numbers = Ui::NumbersAnimation(_st.font, [] {}); auto numbers = Ui::NumbersAnimation(_st.font, [] {});
numbers.setDisabledMonospace(true); numbers.setDisabledMonospace(true);
numbers.setDuration(0); numbers.setDuration(0);
numbers.setText(_textFactory(0), 0); numbers.setText(_textFactory(0), 0);
numbers.setText(_textFactory(maxCounter), maxCounter); numbers.setText(_textFactory(maxPossibleCounter), maxPossibleCounter);
numbers.finishAnimating(); numbers.finishAnimating();
return filledWidth() + numbers.maxWidth(); return filledWidth() + numbers.maxWidth();
} }
@ -389,7 +389,6 @@ public:
const style::PremiumBubble &st, const style::PremiumBubble &st,
TextFactory textFactory, TextFactory textFactory,
rpl::producer<BubbleRowState> state, rpl::producer<BubbleRowState> state,
int maxCounter,
bool premiumPossible, bool premiumPossible,
rpl::producer<> showFinishes, rpl::producer<> showFinishes,
const style::icon *icon, const style::icon *icon,
@ -414,9 +413,8 @@ private:
BubbleRowState _animatingFrom; BubbleRowState _animatingFrom;
float64 _animatingFromResultRatio = 0.; float64 _animatingFromResultRatio = 0.;
rpl::variable<BubbleRowState> _state; rpl::variable<BubbleRowState> _state;
const int _maxCounter;
Bubble _bubble; Bubble _bubble;
const int _maxBubbleWidth; int _maxBubbleWidth = 0;
const bool _premiumPossible; const bool _premiumPossible;
const style::margins _outerPadding; const style::margins _outerPadding;
@ -439,7 +437,6 @@ BubbleWidget::BubbleWidget(
const style::PremiumBubble &st, const style::PremiumBubble &st,
TextFactory textFactory, TextFactory textFactory,
rpl::producer<BubbleRowState> state, rpl::producer<BubbleRowState> state,
int maxCounter,
bool premiumPossible, bool premiumPossible,
rpl::producer<> showFinishes, rpl::producer<> showFinishes,
const style::icon *icon, const style::icon *icon,
@ -447,14 +444,12 @@ BubbleWidget::BubbleWidget(
: RpWidget(parent) : RpWidget(parent)
, _st(st) , _st(st)
, _state(std::move(state)) , _state(std::move(state))
, _maxCounter(maxCounter)
, _bubble( , _bubble(
_st, _st,
[=] { update(); }, [=] { update(); },
std::move(textFactory), std::move(textFactory),
icon, icon,
premiumPossible) premiumPossible)
, _maxBubbleWidth(_bubble.countMaxWidth(_maxCounter))
, _premiumPossible(premiumPossible) , _premiumPossible(premiumPossible)
, _outerPadding(outerPadding) , _outerPadding(outerPadding)
, _deflection(kDeflection) , _deflection(kDeflection)
@ -485,6 +480,7 @@ BubbleWidget::BubbleWidget(
} }
void BubbleWidget::animateTo(BubbleRowState state) { void BubbleWidget::animateTo(BubbleRowState state) {
_maxBubbleWidth = _bubble.countMaxWidth(state.counter);
const auto parent = parentWidget(); const auto parent = parentWidget();
const auto computeLeft = [=](float64 pointRatio, float64 animProgress) { const auto computeLeft = [=](float64 pointRatio, float64 animProgress) {
const auto halfWidth = (_maxBubbleWidth / 2); const auto halfWidth = (_maxBubbleWidth / 2);
@ -541,6 +537,11 @@ void BubbleWidget::animateTo(BubbleRowState state) {
const auto duration = kSlideDuration const auto duration = kSlideDuration
* (_ignoreDeflection ? kStepBeforeDeflection : 1.) * (_ignoreDeflection ? kStepBeforeDeflection : 1.)
* ((_state.current().ratio < 0.001) ? 0.5 : 1.); * ((_state.current().ratio < 0.001) ? 0.5 : 1.);
if (state.animateFromZero) {
_animatingFrom.ratio = 0.;
_animatingFrom.counter = 0;
_animatingFromResultRatio = 0.;
}
_appearanceAnimation.start([=](float64 value) { _appearanceAnimation.start([=](float64 value) {
if (!_appearanceAnimation.animating()) { if (!_appearanceAnimation.animating()) {
_animatingFrom = state; _animatingFrom = state;
@ -658,7 +659,7 @@ public:
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
const style::PremiumLimits &st, const style::PremiumLimits &st,
LimitRowLabels labels, LimitRowLabels labels,
rpl::producer<float64> ratio); rpl::producer<LimitRowState> state);
void setColorOverride(QBrush brush); void setColorOverride(QBrush brush);
@ -675,6 +676,7 @@ private:
float64 _ratio = 0.; float64 _ratio = 0.;
Ui::Animations::Simple _animation; Ui::Animations::Simple _animation;
rpl::event_stream<> _recaches;
Ui::Text::String _leftLabel; Ui::Text::String _leftLabel;
Ui::Text::String _leftText; Ui::Text::String _leftText;
Ui::Text::String _rightLabel; Ui::Text::String _rightLabel;
@ -707,44 +709,56 @@ Line::Line(
QString min, QString min,
float64 ratio) float64 ratio)
: Line(parent, st, LimitRowLabels{ : Line(parent, st, LimitRowLabels{
.leftLabel = tr::lng_premium_free(tr::now), .leftLabel = tr::lng_premium_free(),
.leftCount = min, .leftCount = rpl::single(min),
.rightLabel = tr::lng_premium(tr::now), .rightLabel = tr::lng_premium(),
.rightCount = max, .rightCount = rpl::single(max),
}, rpl::single(ratio)) { }, rpl::single(LimitRowState{ ratio })) {
} }
Line::Line( Line::Line(
not_null<Ui::RpWidget*> parent, not_null<Ui::RpWidget*> parent,
const style::PremiumLimits &st, const style::PremiumLimits &st,
LimitRowLabels labels, LimitRowLabels labels,
rpl::producer<float64> ratio) rpl::producer<LimitRowState> state)
: Ui::RpWidget(parent) : Ui::RpWidget(parent)
, _st(st) , _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) {
resize(width(), st::requestsAcceptButton.height); resize(width(), st::requestsAcceptButton.height);
std::move(ratio) | rpl::start_with_next([=](float64 ratio) { const auto set = [&](
Ui::Text::String &label,
rpl::producer<QString> &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) { if (width() > 0) {
const auto from = _animation.value(_ratio); const auto from = state.animateFromZero
? 0.
: _animation.value(_ratio);
const auto duration = kSlideDuration * kStepBeforeDeflection; const auto duration = kSlideDuration * kStepBeforeDeflection;
_animation.start([=] { _animation.start([=] {
update(); update();
}, from, ratio, duration, anim::easeOutCirc); }, from, state.ratio, duration, anim::easeOutCirc);
} }
_ratio = ratio; _ratio = state.ratio;
}, lifetime()); }, lifetime());
rpl::combine( rpl::combine(
sizeValue(), sizeValue(),
parent->widthValue() parent->widthValue(),
) | rpl::filter([](const QSize &size, int parentWidth) { _recaches.events_starting_with({})
) | rpl::filter([](const QSize &size, int parentWidth, auto) {
return !size.isEmpty() && parentWidth; return !size.isEmpty() && parentWidth;
}) | rpl::start_with_next([=](const QSize &size, int) { }) | rpl::start_with_next([=](const QSize &size, auto, auto) {
recache(size); recache(size);
update(); update();
}, lifetime()); }, lifetime());
@ -906,7 +920,6 @@ void AddBubbleRow(
.counter = current, .counter = current,
.ratio = (current - min) / float64(max - min), .ratio = (current - min) / float64(max - min),
}), }),
max,
premiumPossible, premiumPossible,
ProcessTextFactory(phrase), ProcessTextFactory(phrase),
icon, icon,
@ -918,7 +931,6 @@ void AddBubbleRow(
const style::PremiumBubble &st, const style::PremiumBubble &st,
rpl::producer<> showFinishes, rpl::producer<> showFinishes,
rpl::producer<BubbleRowState> state, rpl::producer<BubbleRowState> state,
int max,
bool premiumPossible, bool premiumPossible,
Fn<QString(int)> text, Fn<QString(int)> text,
const style::icon *icon, const style::icon *icon,
@ -930,7 +942,6 @@ void AddBubbleRow(
st, st,
text ? std::move(text) : ProcessTextFactory(std::nullopt), text ? std::move(text) : ProcessTextFactory(std::nullopt),
std::move(state), std::move(state),
max,
premiumPossible, premiumPossible,
std::move(showFinishes), std::move(showFinishes),
icon, icon,
@ -975,10 +986,10 @@ void AddLimitRow(
not_null<Ui::VerticalLayout*> parent, not_null<Ui::VerticalLayout*> parent,
const style::PremiumLimits &st, const style::PremiumLimits &st,
LimitRowLabels labels, LimitRowLabels labels,
rpl::producer<float64> ratio, rpl::producer<LimitRowState> state,
const style::margins &padding) { const style::margins &padding) {
parent->add( parent->add(
object_ptr<Line>(parent, st, std::move(labels), std::move(ratio)), object_ptr<Line>(parent, st, std::move(labels), std::move(state)),
padding); padding);
} }

View file

@ -55,6 +55,7 @@ void AddBubbleRow(
struct BubbleRowState { struct BubbleRowState {
int counter = 0; int counter = 0;
float64 ratio = 0.; float64 ratio = 0.;
bool animateFromZero = false;
bool dynamic = false; bool dynamic = false;
}; };
void AddBubbleRow( void AddBubbleRow(
@ -62,7 +63,6 @@ void AddBubbleRow(
const style::PremiumBubble &st, const style::PremiumBubble &st,
rpl::producer<> showFinishes, rpl::producer<> showFinishes,
rpl::producer<BubbleRowState> state, rpl::producer<BubbleRowState> state,
int max,
bool premiumPossible, bool premiumPossible,
Fn<QString(int)> text, Fn<QString(int)> text,
const style::icon *icon, const style::icon *icon,
@ -84,17 +84,23 @@ void AddLimitRow(
float64 ratio = kLimitRowRatio); float64 ratio = kLimitRowRatio);
struct LimitRowLabels { struct LimitRowLabels {
QString leftLabel; rpl::producer<QString> leftLabel;
QString leftCount; rpl::producer<QString> leftCount;
QString rightLabel; rpl::producer<QString> rightLabel;
QString rightCount; rpl::producer<QString> rightCount;
};
struct LimitRowState {
float64 ratio = 0.;
bool animateFromZero = false;
bool dynamic = false; bool dynamic = false;
}; };
void AddLimitRow( void AddLimitRow(
not_null<Ui::VerticalLayout*> parent, not_null<Ui::VerticalLayout*> parent,
const style::PremiumLimits &st, const style::PremiumLimits &st,
LimitRowLabels labels, LimitRowLabels labels,
rpl::producer<float64> ratio, rpl::producer<LimitRowState> state,
const style::margins &padding); const style::margins &padding);
struct AccountsRowArgs final { struct AccountsRowArgs final {

View file

@ -267,6 +267,19 @@ Fn<bool()> PausedIn(
return [=] { return IsPaused(controller, level); }; 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) { bool operator==(const PeerThemeOverride &a, const PeerThemeOverride &b) {
return (a.peer == b.peer) && (a.theme == b.theme); return (a.peer == b.peer) && (a.theme == b.theme);
} }
@ -629,20 +642,12 @@ void SessionNavigation::resolveBoostState(not_null<ChannelData*> channel) {
channel->input channel->input
)).done([=](const MTPpremium_BoostsStatus &result) { )).done([=](const MTPpremium_BoostsStatus &result) {
_boostStateResolving = nullptr; _boostStateResolving = nullptr;
const auto &data = result.data(); const auto submit = [=](Fn<void(Ui::BoostCounters)> done) {
const auto submit = [=](Fn<void(bool)> done) {
applyBoost(channel, done); applyBoost(channel, done);
}; };
const auto next = data.vnext_level_boosts().value_or_empty();
uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{ uiShow()->show(Box(Ui::BoostBox, Ui::BoostBoxData{
.name = channel->name(), .name = channel->name(),
.boost = { .boost = ParseBoostCounters(result),
.level = data.vlevel().v,
.boosts = data.vboosts().v,
.thisLevelBoosts = data.vcurrent_level_boosts().v,
.nextLevelBoosts = next,
.mine = data.is_my_boost(),
},
}, submit)); }, submit));
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
_boostStateResolving = nullptr; _boostStateResolving = nullptr;
@ -652,7 +657,7 @@ void SessionNavigation::resolveBoostState(not_null<ChannelData*> channel) {
void SessionNavigation::applyBoost( void SessionNavigation::applyBoost(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
Fn<void(bool)> done) { Fn<void(Ui::BoostCounters)> done) {
_api.request(MTPpremium_GetMyBoosts( _api.request(MTPpremium_GetMyBoosts(
)).done([=](const MTPpremium_MyBoosts &result) { )).done([=](const MTPpremium_MyBoosts &result) {
const auto &data = result.data(); const auto &data = result.data();
@ -682,7 +687,7 @@ void SessionNavigation::applyBoost(
.inform = true, .inform = true,
})); }));
} }
done(false); done({});
return; return;
} }
auto slot = int(); auto slot = int();
@ -725,7 +730,7 @@ void SessionNavigation::applyBoost(
.title = tr::lng_boost_error_flood_title(), .title = tr::lng_boost_error_flood_title(),
.inform = true, .inform = true,
})); }));
done(false); done({});
} else { } else {
const auto peer = _session->data().peer(different); const auto peer = _session->data().peer(different);
replaceBoostConfirm(peer, channel, slot, done); replaceBoostConfirm(peer, channel, slot, done);
@ -737,12 +742,12 @@ void SessionNavigation::applyBoost(
.title = tr::lng_boost_error_already_title(), .title = tr::lng_boost_error_already_title(),
.inform = true, .inform = true,
})); }));
done(false); done({});
} }
}).fail([=](const MTP::Error &error) { }).fail([=](const MTP::Error &error) {
const auto type = error.type(); const auto type = error.type();
showToast(u"Error: "_q + type); showToast(u"Error: "_q + type);
done(false); done({});
}).handleFloodErrors().send(); }).handleFloodErrors().send();
} }
@ -750,7 +755,7 @@ void SessionNavigation::replaceBoostConfirm(
not_null<PeerData*> from, not_null<PeerData*> from,
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
int slot, int slot,
Fn<void(bool)> done) { Fn<void(Ui::BoostCounters)> done) {
const auto forwarded = std::make_shared<bool>(false); const auto forwarded = std::make_shared<bool>(false);
const auto confirmed = [=](Fn<void()> close) { const auto confirmed = [=](Fn<void()> close) {
*forwarded = true; *forwarded = true;
@ -777,23 +782,30 @@ void SessionNavigation::replaceBoostConfirm(
box->boxClosing() | rpl::filter([=] { box->boxClosing() | rpl::filter([=] {
return !*forwarded; return !*forwarded;
}) | rpl::start_with_next([=] { }) | rpl::start_with_next([=] {
done(false); done({});
}, box->lifetime()); }, box->lifetime());
} }
void SessionNavigation::applyBoostChecked( void SessionNavigation::applyBoostChecked(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
int slot, int slot,
Fn<void(bool)> done) { Fn<void(Ui::BoostCounters)> done) {
_api.request(MTPpremium_ApplyBoost( _api.request(MTPpremium_ApplyBoost(
MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots), MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots),
MTP_vector<MTPint>({ MTP_int(slot) }), MTP_vector<MTPint>({ MTP_int(slot) }),
channel->input channel->input
)).done([=](const MTPpremium_MyBoosts &result) { )).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) { }).fail([=](const MTP::Error &error) {
showToast(u"Error: "_q + error.type()); showToast(u"Error: "_q + error.type());
done(false); done({});
}).send(); }).send();
} }

View file

@ -68,6 +68,7 @@ struct ChatPaintContext;
struct ChatThemeBackground; struct ChatThemeBackground;
struct ChatThemeBackgroundData; struct ChatThemeBackgroundData;
class MessageSendingAnimationController; class MessageSendingAnimationController;
struct BoostCounters;
} // namespace Ui } // namespace Ui
namespace Data { namespace Data {
@ -314,16 +315,18 @@ private:
const PeerByLinkInfo &info); const PeerByLinkInfo &info);
void resolveBoostState(not_null<ChannelData*> channel); void resolveBoostState(not_null<ChannelData*> channel);
void applyBoost(not_null<ChannelData*> channel, Fn<void(bool)> done); void applyBoost(
not_null<ChannelData*> channel,
Fn<void(Ui::BoostCounters)> done);
void replaceBoostConfirm( void replaceBoostConfirm(
not_null<PeerData*> from, not_null<PeerData*> from,
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
int slot, int slot,
Fn<void(bool)> done); Fn<void(Ui::BoostCounters)> done);
void applyBoostChecked( void applyBoostChecked(
not_null<ChannelData*> channel, not_null<ChannelData*> channel,
int slot, int slot,
Fn<void(bool)> done); Fn<void(Ui::BoostCounters)> done);
const not_null<Main::Session*> _session; const not_null<Main::Session*> _session;
@ -752,4 +755,7 @@ void ActivateWindow(not_null<SessionController*> controller);
not_null<SessionController*> controller, not_null<SessionController*> controller,
GifPauseReason level); GifPauseReason level);
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
const MTPpremium_BoostsStatus &status);
} // namespace Window } // namespace Window