mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-15 21:57:10 +02:00
Start multiboosts, support dynamic state.
This commit is contained in:
parent
2d67557a91
commit
a41bbd27c8
11 changed files with 300 additions and 191 deletions
|
@ -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.";
|
||||
|
|
|
@ -519,7 +519,9 @@ rpl::producer<rpl::no_value, QString> 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,
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -302,17 +302,16 @@ void InnerWidget::fill() {
|
|||
auto dividerContent = object_ptr<Ui::VerticalLayout>(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<Ui::DividerLabel>(
|
||||
inner,
|
||||
|
|
|
@ -20,6 +20,31 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QtGui/QGuiApplication>
|
||||
|
||||
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) {
|
||||
const auto result = Ui::CreateChild<RpWidget>(parent.get());
|
||||
|
@ -42,62 +67,65 @@ void StartFireworks(not_null<QWidget*> parent) {
|
|||
void BoostBox(
|
||||
not_null<GenericBox*> box,
|
||||
BoostBoxData data,
|
||||
Fn<void(Fn<void(bool)>)> boost) {
|
||||
Fn<void(Fn<void(BoostCounters)>)> 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<bool> you = false;
|
||||
rpl::variable<BoostCounters> data;
|
||||
rpl::variable<bool> full;
|
||||
bool submitted = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.you = data.boost.mine,
|
||||
});
|
||||
const auto state = box->lifetime().make_state<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<Ui::FlatLabel>(
|
||||
box,
|
||||
std::move(title),
|
||||
st::boostTitle),
|
||||
st::boxRowPadding + QMargins(0, st::boostTitleSkip, 0, 0));
|
||||
|
||||
box->addRow(
|
||||
object_ptr<Ui::FlatLabel>(
|
||||
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<bool> you = false;
|
||||
bool submitted = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>(State{
|
||||
.you = data.boost.mine,
|
||||
});
|
||||
const auto state = box->lifetime().make_state<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<bool> you,
|
||||
not_null<VerticalLayout*> container,
|
||||
BoostCounters data,
|
||||
rpl::producer<BoostCounters> 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<Ui::FixedHeightWidget>(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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<GenericBox*> box,
|
||||
BoostBoxData data,
|
||||
Fn<void(Fn<void(bool)>)> boost);
|
||||
Fn<void(Fn<void(BoostCounters)>)> boost);
|
||||
|
||||
struct AskBoostBoxData {
|
||||
QString link;
|
||||
|
@ -57,9 +61,8 @@ void AskBoostBox(
|
|||
|
||||
void FillBoostLimit(
|
||||
rpl::producer<> showFinished,
|
||||
rpl::producer<bool> you,
|
||||
not_null<VerticalLayout*> container,
|
||||
BoostCounters data,
|
||||
rpl::producer<BoostCounters> data,
|
||||
style::margins limitLinePadding);
|
||||
|
||||
} // namespace Ui
|
||||
|
|
|
@ -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<BubbleRowState> state,
|
||||
int maxCounter,
|
||||
bool premiumPossible,
|
||||
rpl::producer<> showFinishes,
|
||||
const style::icon *icon,
|
||||
|
@ -414,9 +413,8 @@ private:
|
|||
BubbleRowState _animatingFrom;
|
||||
float64 _animatingFromResultRatio = 0.;
|
||||
rpl::variable<BubbleRowState> _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<BubbleRowState> 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<Ui::RpWidget*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio);
|
||||
rpl::producer<LimitRowState> 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<Ui::RpWidget*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio)
|
||||
rpl::producer<LimitRowState> 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<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) {
|
||||
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<BubbleRowState> state,
|
||||
int max,
|
||||
bool premiumPossible,
|
||||
Fn<QString(int)> 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<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio,
|
||||
rpl::producer<LimitRowState> state,
|
||||
const style::margins &padding) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<BubbleRowState> state,
|
||||
int max,
|
||||
bool premiumPossible,
|
||||
Fn<QString(int)> 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<QString> leftLabel;
|
||||
rpl::producer<QString> leftCount;
|
||||
rpl::producer<QString> rightLabel;
|
||||
rpl::producer<QString> rightCount;
|
||||
};
|
||||
|
||||
struct LimitRowState {
|
||||
float64 ratio = 0.;
|
||||
bool animateFromZero = false;
|
||||
bool dynamic = false;
|
||||
};
|
||||
|
||||
void AddLimitRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio,
|
||||
rpl::producer<LimitRowState> state,
|
||||
const style::margins &padding);
|
||||
|
||||
struct AccountsRowArgs final {
|
||||
|
|
|
@ -267,6 +267,19 @@ Fn<bool()> 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<ChannelData*> channel) {
|
|||
channel->input
|
||||
)).done([=](const MTPpremium_BoostsStatus &result) {
|
||||
_boostStateResolving = nullptr;
|
||||
const auto &data = result.data();
|
||||
const auto submit = [=](Fn<void(bool)> done) {
|
||||
const auto submit = [=](Fn<void(Ui::BoostCounters)> 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<ChannelData*> channel) {
|
|||
|
||||
void SessionNavigation::applyBoost(
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(bool)> done) {
|
||||
Fn<void(Ui::BoostCounters)> 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<PeerData*> from,
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done) {
|
||||
Fn<void(Ui::BoostCounters)> done) {
|
||||
const auto forwarded = std::make_shared<bool>(false);
|
||||
const auto confirmed = [=](Fn<void()> 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<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done) {
|
||||
Fn<void(Ui::BoostCounters)> done) {
|
||||
_api.request(MTPpremium_ApplyBoost(
|
||||
MTP_flags(MTPpremium_ApplyBoost::Flag::f_slots),
|
||||
MTP_vector<MTPint>({ 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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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<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(
|
||||
not_null<PeerData*> from,
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done);
|
||||
Fn<void(Ui::BoostCounters)> done);
|
||||
void applyBoostChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
int slot,
|
||||
Fn<void(bool)> done);
|
||||
Fn<void(Ui::BoostCounters)> done);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
|
@ -752,4 +755,7 @@ void ActivateWindow(not_null<SessionController*> controller);
|
|||
not_null<SessionController*> controller,
|
||||
GifPauseReason level);
|
||||
|
||||
[[nodiscard]] Ui::BoostCounters ParseBoostCounters(
|
||||
const MTPpremium_BoostsStatus &status);
|
||||
|
||||
} // namespace Window
|
||||
|
|
Loading…
Add table
Reference in a new issue