mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-17 22:57:11 +02:00
Handle t.me/channel?boost links.
This commit is contained in:
parent
39f8394f98
commit
1c2951598b
17 changed files with 920 additions and 204 deletions
BIN
Telegram/Resources/icons/limits/boost.png
Normal file
BIN
Telegram/Resources/icons/limits/boost.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 464 B |
BIN
Telegram/Resources/icons/limits/boost@2x.png
Normal file
BIN
Telegram/Resources/icons/limits/boost@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 796 B |
BIN
Telegram/Resources/icons/limits/boost@3x.png
Normal file
BIN
Telegram/Resources/icons/limits/boost@3x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
|
@ -2002,6 +2002,37 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
"lng_premium_gift_terms" = "You can review the list of features and terms of use for Telegram Premium {link}.";
|
||||
"lng_premium_gift_terms_link" = "here";
|
||||
|
||||
"lng_boost_channel_button" = "Boost Channel";
|
||||
"lng_boost_level#one" = "Level {count}";
|
||||
"lng_boost_level#other" = "Level {count}";
|
||||
"lng_boost_channel_title_first" = "Enable stories for channel";
|
||||
"lng_boost_channel_needs_first#one" = "{channel} needs **{count}** more boost to enable posting stories. Help make it possible!";
|
||||
"lng_boost_channel_needs_first#other" = "{channel} needs **{count}** more boosts to enable posting stories. Help make it possible!";
|
||||
"lng_boost_channel_title_more" = "Help upgrade channel";
|
||||
"lng_boost_channel_needs_more#one" = "{channel} needs **{count}** more boost to be able to {post}.";
|
||||
"lng_boost_channel_needs_more#other" = "{channel} needs **{count}** more boosts to be able to {post}.";
|
||||
"lng_boost_channel_you_title" = "You boosted {channel}!";
|
||||
"lng_boost_channel_you_first#one" = "This channel needs **{count}** more boost\nto enable stories.";
|
||||
"lng_boost_channel_you_first#other" = "This channel needs **{count}** more boosts\nto enable stories.";
|
||||
"lng_boost_channel_you_more#one" = "This channel needs **{count}** more boost\nto be able to {post}.";
|
||||
"lng_boost_channel_you_more#other" = "This channel needs **{count}** more boosts\nto be able to {post}.";
|
||||
"lng_boost_channel_reached_first" = "This channel reached **Level 1** and can now post stories.";
|
||||
"lng_boost_channel_reached_more#one" = "This channel reached **Level {count}** and can now {post}.";
|
||||
"lng_boost_channel_reached_more#other" = "This channel reached **Level {count}** and can now {post}.";
|
||||
"lng_boost_channel_post_stories#one" = "post **{count} story** per day";
|
||||
"lng_boost_channel_post_stories#other" = "post **{count} stories** per day";
|
||||
"lng_boost_error_gifted_title" = "Can't boost with gifted Premium!";
|
||||
"lng_boost_error_gifted_text" = "Because your **Telegram Premium** subscription was gifted to you, you can't use it to boost channels.";
|
||||
"lng_boost_error_already_title" = "Already Boosted!";
|
||||
"lng_boost_error_already_text" = "You are already boosting this channel.";
|
||||
"lng_boost_error_premium_title" = "Premium needed!";
|
||||
"lng_boost_error_premium_text" = "Only **Telegram Premium** subscribers can boost channels. Do you want to subscribe to **Telegram Premium**?";
|
||||
"lng_boost_error_premium_yes" = "Yes";
|
||||
"lng_boost_error_flood_title" = "Can't boost too often!";
|
||||
"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_accounts_limit_title" = "Limit Reached";
|
||||
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
|
||||
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";
|
||||
|
|
|
@ -410,8 +410,9 @@ void SimpleLimitBox(
|
|||
Settings::AddSkip(top, st::premiumInfographicPadding.top());
|
||||
Ui::Premium::AddBubbleRow(
|
||||
top,
|
||||
st::defaultPremiumBubble,
|
||||
BoxShowFinishes(box),
|
||||
descriptor.defaultLimit,
|
||||
0,
|
||||
descriptor.current,
|
||||
descriptor.premiumLimit,
|
||||
premiumPossible,
|
||||
|
@ -770,16 +771,18 @@ void FilterLinksLimitBox(
|
|||
|
||||
void FiltersLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session) {
|
||||
not_null<Main::Session*> session,
|
||||
std::optional<int> filtersCountOverride) {
|
||||
const auto premium = session->premium();
|
||||
const auto premiumPossible = session->premiumPossible();
|
||||
|
||||
const auto limits = Data::PremiumLimits(session);
|
||||
const auto defaultLimit = float64(limits.dialogFiltersDefault());
|
||||
const auto premiumLimit = float64(limits.dialogFiltersPremium());
|
||||
const auto current = float64(ranges::count_if(
|
||||
const auto cloud = int(ranges::count_if(
|
||||
session->data().chatsFilters().list(),
|
||||
[](const Data::ChatFilter &f) { return f.id() != FilterId(); }));
|
||||
const auto current = float64(filtersCountOverride.value_or(cloud));
|
||||
|
||||
auto text = rpl::combine(
|
||||
tr::lng_filters_limit1(
|
||||
|
@ -1079,6 +1082,7 @@ void AccountsLimitBox(
|
|||
Settings::AddSkip(top, st::premiumInfographicPadding.top());
|
||||
Ui::Premium::AddBubbleRow(
|
||||
top,
|
||||
st::defaultPremiumBubble,
|
||||
BoxShowFinishes(box),
|
||||
0,
|
||||
current,
|
||||
|
|
|
@ -42,7 +42,8 @@ void FilterLinksLimitBox(
|
|||
not_null<Main::Session*> session);
|
||||
void FiltersLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session);
|
||||
not_null<Main::Session*> session,
|
||||
std::optional<int> filtersCountOverride);
|
||||
void ShareableFiltersLimitBox(
|
||||
not_null<Ui::GenericBox*> box,
|
||||
not_null<Main::Session*> session);
|
||||
|
|
|
@ -378,6 +378,8 @@ bool ResolveUsernameOrPhone(
|
|||
startToken = params.value(u"startgroup"_q);
|
||||
} else if (params.contains(u"startchannel"_q)) {
|
||||
resolveType = ResolveType::AddToChannel;
|
||||
} else if (params.contains(u"boost"_q)) {
|
||||
resolveType = ResolveType::Boost;
|
||||
}
|
||||
auto post = ShowAtUnreadMsgId;
|
||||
auto adminRights = ChatAdminRights();
|
||||
|
@ -842,6 +844,32 @@ bool ResolveLoginCode(
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ResolveBoost(
|
||||
Window::SessionController *controller,
|
||||
const Match &match,
|
||||
const QVariant &context) {
|
||||
if (!controller) {
|
||||
return false;
|
||||
}
|
||||
const auto params = url_parse_params(
|
||||
match->captured(1),
|
||||
qthelp::UrlParamNameTransform::ToLower);
|
||||
const auto domainParam = params.value(u"domain"_q);
|
||||
const auto channelParam = params.value(u"channel"_q);
|
||||
|
||||
const auto myContext = context.value<ClickHandlerContext>();
|
||||
using Navigation = Window::SessionNavigation;
|
||||
controller->window().activate();
|
||||
controller->showPeerByLink(Navigation::PeerByLinkInfo{
|
||||
.usernameOrId = (!domainParam.isEmpty()
|
||||
? std::variant<QString, ChannelId>(domainParam)
|
||||
: ChannelId(BareId(channelParam.toULongLong()))),
|
||||
.resolveType = Window::ResolveType::Boost,
|
||||
.clickFromMessageId = myContext.itemId,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
||||
|
@ -922,6 +950,10 @@ const std::vector<LocalUrlHandler> &LocalUrlHandlers() {
|
|||
u"^login/?(\\?code=([0-9]+))(&|$)"_q,
|
||||
ResolveLoginCode
|
||||
},
|
||||
{
|
||||
u"^boost/?\\?(.+)(#|$)"_q,
|
||||
ResolveBoost,
|
||||
},
|
||||
{
|
||||
u"^([^\\?]+)(\\?|#|$)"_q,
|
||||
HandleUnknown
|
||||
|
@ -1025,8 +1057,13 @@ QString TryConvertUrlToLocal(QString url) {
|
|||
"/\\d+/?(\\?|$)|"
|
||||
"/\\d+/\\d+/?(\\?|$)"
|
||||
")"_q, query, matchOptions)) {
|
||||
const auto channel = privateMatch->captured(1);
|
||||
const auto params = query.mid(privateMatch->captured(0).size()).toString();
|
||||
const auto base = u"tg://privatepost?channel="_q + privateMatch->captured(1);
|
||||
if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0
|
||||
&& params.toLower().split('&').contains(u"boost"_q)) {
|
||||
return u"tg://boost?channel="_q + channel;
|
||||
}
|
||||
const auto base = u"tg://privatepost?channel="_q + channel;
|
||||
auto added = QString();
|
||||
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, privateMatch->captured(2))) {
|
||||
added = u"&topic=%1&post=%2"_q.arg(threadPostMatch->captured(1)).arg(threadPostMatch->captured(2));
|
||||
|
@ -1044,7 +1081,12 @@ QString TryConvertUrlToLocal(QString url) {
|
|||
"/s/\\d+/?(\\?|$)|"
|
||||
"/\\d+/\\d+/?(\\?|$)"
|
||||
")"_q, query, matchOptions)) {
|
||||
const auto domain = usernameMatch->captured(1);
|
||||
const auto params = query.mid(usernameMatch->captured(0).size()).toString();
|
||||
if (params.indexOf("boost", 0, Qt::CaseInsensitive) >= 0
|
||||
&& params.toLower().split('&').contains(u"boost"_q)) {
|
||||
return u"tg://boost?domain="_q + domain;
|
||||
}
|
||||
const auto base = u"tg://resolve?domain="_q + url_encode(usernameMatch->captured(1));
|
||||
auto added = QString();
|
||||
if (const auto threadPostMatch = regex_match(u"^/(\\d+)/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
|
||||
|
|
|
@ -362,10 +362,11 @@ void FilterRowButton::paintEvent(QPaintEvent *e) {
|
|||
const auto removed = ranges::count_if(
|
||||
state->rows,
|
||||
&FilterRow::removed);
|
||||
if (state->rows.size() < limit() + removed) {
|
||||
const auto count = int(state->rows.size() - removed);
|
||||
if (count < limit()) {
|
||||
return false;
|
||||
}
|
||||
controller->show(Box(FiltersLimitBox, session));
|
||||
controller->show(Box(FiltersLimitBox, session, count));
|
||||
return true;
|
||||
};
|
||||
const auto markForRemovalSure = [=](not_null<FilterRowButton*> button) {
|
||||
|
|
251
Telegram/SourceFiles/ui/boxes/boost_box.cpp
Normal file
251
Telegram/SourceFiles/ui/boxes/boost_box.cpp
Normal file
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#include "ui/boxes/boost_box.h"
|
||||
|
||||
#include "lang/lang_keys.h"
|
||||
#include "ui/effects/fireworks_animation.h"
|
||||
#include "ui/effects/premium_graphics.h"
|
||||
#include "ui/layers/generic_box.h"
|
||||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/widgets/buttons.h"
|
||||
#include "styles/style_layers.h"
|
||||
#include "styles/style_premium.h"
|
||||
|
||||
namespace Ui {
|
||||
namespace {
|
||||
|
||||
void StartFireworks(not_null<QWidget*> parent) {
|
||||
const auto result = Ui::CreateChild<RpWidget>(parent.get());
|
||||
result->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
result->setGeometry(parent->rect());
|
||||
result->show();
|
||||
|
||||
auto &lifetime = result->lifetime();
|
||||
const auto animation = lifetime.make_state<FireworksAnimation>([=] {
|
||||
result->update();
|
||||
});
|
||||
result->paintRequest() | rpl::start_with_next([=] {
|
||||
auto p = QPainter(result);
|
||||
if (!animation->paint(p, result->rect())) {
|
||||
crl::on_main(result, [=] { delete result; });
|
||||
}
|
||||
}, lifetime);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void BoostBox(
|
||||
not_null<GenericBox*> box,
|
||||
BoostBoxData data,
|
||||
Fn<void(Fn<void(bool)>)> boost) {
|
||||
box->setWidth(st::boxWideWidth);
|
||||
box->setStyle(st::boostBox);
|
||||
|
||||
struct State {
|
||||
rpl::variable<bool> you = false;
|
||||
bool submitted = false;
|
||||
};
|
||||
const auto state = box->lifetime().make_state<State>();
|
||||
box->addTopButton(st::boxTitleClose, [=] {
|
||||
box->closeBox();
|
||||
});
|
||||
|
||||
const auto addSkip = [&](int skip) {
|
||||
box->addRow(object_ptr<Ui::FixedHeightWidget>(box, skip));
|
||||
};
|
||||
|
||||
addSkip(st::boostSkipTop);
|
||||
|
||||
const auto levelWidth = [&](int add) {
|
||||
return st::normalFont->width(
|
||||
tr::lng_boost_level(tr::now, lt_count, data.boost.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.boost.boosts,
|
||||
data.boost.thisLevelBoosts);
|
||||
const auto max = std::max({
|
||||
data.boost.boosts,
|
||||
data.boost.nextLevelBoosts,
|
||||
1,
|
||||
});
|
||||
Assert(boosts >= min && boosts <= max);
|
||||
const auto count = (max - min);
|
||||
const auto index = (boosts - min);
|
||||
if (!index) {
|
||||
return 0.;
|
||||
} else if (index == count) {
|
||||
return 1.;
|
||||
} else if (count == 2) {
|
||||
return 0.5;
|
||||
}
|
||||
const auto available = st::boxWideWidth
|
||||
- st::boxPadding.left()
|
||||
- st::boxPadding.right();
|
||||
const auto average = available / float64(count);
|
||||
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.boost.boosts,
|
||||
data.boost.thisLevelBoosts);
|
||||
const auto now = data.boost.boosts;
|
||||
const auto max = (data.boost.nextLevelBoosts > min)
|
||||
? (data.boost.nextLevelBoosts)
|
||||
: (data.boost.boosts > 0)
|
||||
? data.boost.boosts
|
||||
: 1;
|
||||
auto bubbleRowState = state->you.value(
|
||||
) | rpl::map([=](bool mine) {
|
||||
const auto index = mine ? (now + 1) : now;
|
||||
return Premium::BubbleRowState{
|
||||
.counter = index,
|
||||
.ratio = ratio(index),
|
||||
.dynamic = true,
|
||||
};
|
||||
});
|
||||
Premium::AddBubbleRow(
|
||||
box->verticalLayout(),
|
||||
st::boostBubble,
|
||||
BoxShowFinishes(box),
|
||||
rpl::duplicate(bubbleRowState),
|
||||
max,
|
||||
true,
|
||||
nullptr,
|
||||
&st::premiumIconBoost);
|
||||
addSkip(st::premiumLineTextSkip);
|
||||
|
||||
const auto level = [](int level) {
|
||||
return tr::lng_boost_level(tr::now, lt_count, level);
|
||||
};
|
||||
auto ratioValue = std::move(
|
||||
bubbleRowState
|
||||
) | rpl::map([](const Premium::BubbleRowState &state) {
|
||||
return state.ratio;
|
||||
});
|
||||
Premium::AddLimitRow(
|
||||
box->verticalLayout(),
|
||||
st::boostLimits,
|
||||
Premium::LimitRowLabels{
|
||||
.leftLabel = level(data.boost.level),
|
||||
.rightLabel = level(data.boost.level + 1),
|
||||
.dynamic = true,
|
||||
},
|
||||
std::move(ratioValue));
|
||||
|
||||
const auto name = data.name;
|
||||
auto title = state->you.value() | rpl::map([=](bool your) {
|
||||
return your
|
||||
? tr::lng_boost_channel_you_title(
|
||||
lt_channel,
|
||||
rpl::single(data.name))
|
||||
: !data.boost.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)
|
||||
: 0;
|
||||
auto post = tr::lng_boost_channel_post_stories(
|
||||
lt_count,
|
||||
rpl::single(float64(data.boost.level + 1)),
|
||||
Ui::Text::RichLangValue);
|
||||
return your
|
||||
? ((left > 0)
|
||||
? (!data.boost.level
|
||||
? tr::lng_boost_channel_you_first(
|
||||
lt_count,
|
||||
rpl::single(float64(left)),
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_boost_channel_you_more(
|
||||
lt_count,
|
||||
rpl::single(float64(left)),
|
||||
lt_post,
|
||||
std::move(post),
|
||||
Ui::Text::RichLangValue))
|
||||
: (!data.boost.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)),
|
||||
lt_post,
|
||||
std::move(post),
|
||||
Ui::Text::RichLangValue)))
|
||||
: !data.boost.level
|
||||
? tr::lng_boost_channel_needs_first(
|
||||
lt_count,
|
||||
rpl::single(float64(left)),
|
||||
lt_channel,
|
||||
rpl::single(bold),
|
||||
Ui::Text::RichLangValue)
|
||||
: tr::lng_boost_channel_needs_more(
|
||||
lt_count,
|
||||
rpl::single(float64(left)),
|
||||
lt_channel,
|
||||
rpl::single(bold),
|
||||
lt_post,
|
||||
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,
|
||||
std::move(text),
|
||||
st::boostText),
|
||||
(st::boxRowPadding
|
||||
+ QMargins(0, st::boostTextSkip, 0, st::boostBottomSkip)));
|
||||
|
||||
auto submit = state->you.value(
|
||||
) | rpl::map([](bool mine) {
|
||||
return mine ? tr::lng_box_ok() : tr::lng_boost_channel_button();
|
||||
}) | rpl::flatten_latest();
|
||||
const auto button = box->addButton(rpl::duplicate(submit), [=] {
|
||||
if (state->submitted) {
|
||||
return;
|
||||
} else if (!state->you.current()) {
|
||||
state->submitted = true;
|
||||
boost(crl::guard(box, [=](bool success) {
|
||||
state->submitted = false;
|
||||
if (success) {
|
||||
StartFireworks(box->parentWidget());
|
||||
state->you = true;
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
box->closeBox();
|
||||
}
|
||||
});
|
||||
rpl::combine(
|
||||
std::move(submit),
|
||||
box->widthValue()
|
||||
) | rpl::start_with_next([=](const QString &, int width) {
|
||||
const auto &padding = st::boostBox.buttonPadding;
|
||||
button->resizeToWidth(width
|
||||
- padding.left()
|
||||
- padding.right());
|
||||
button->moveToLeft(padding.left(), button->y());
|
||||
}, button->lifetime());
|
||||
}
|
||||
|
||||
} // namespace Ui
|
31
Telegram/SourceFiles/ui/boxes/boost_box.h
Normal file
31
Telegram/SourceFiles/ui/boxes/boost_box.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
This file is part of Telegram Desktop,
|
||||
the official desktop application for the Telegram messaging service.
|
||||
|
||||
For license and copyright information please follow this link:
|
||||
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
namespace Ui {
|
||||
|
||||
class GenericBox;
|
||||
|
||||
struct BoostCounters {
|
||||
int level = 0;
|
||||
int boosts = 0;
|
||||
int thisLevelBoosts = 0;
|
||||
int nextLevelBoosts = 0; // Zero means no next level is available.
|
||||
};
|
||||
|
||||
struct BoostBoxData {
|
||||
QString name;
|
||||
BoostCounters boost;
|
||||
};
|
||||
|
||||
void BoostBox(
|
||||
not_null<GenericBox*> box,
|
||||
BoostBoxData data,
|
||||
Fn<void(Fn<void(bool)>)> boost);
|
||||
|
||||
} // namespace Ui
|
|
@ -13,6 +13,17 @@ PremiumLimits {
|
|||
boxLabel: FlatLabel;
|
||||
nonPremiumBg: color;
|
||||
nonPremiumFg: color;
|
||||
gradientFromLeft: bool;
|
||||
}
|
||||
PremiumBubble {
|
||||
widthLimit: pixels;
|
||||
height: pixels;
|
||||
padding: margins;
|
||||
skip: pixels;
|
||||
penWidth: pixels;
|
||||
textSkip: pixels;
|
||||
tailSize: size;
|
||||
font: font;
|
||||
}
|
||||
|
||||
defaultPremiumBoxLabel: FlatLabel(defaultFlatLabel) {
|
||||
|
@ -26,6 +37,7 @@ defaultPremiumLimits: PremiumLimits {
|
|||
boxLabel: defaultPremiumBoxLabel;
|
||||
nonPremiumBg: windowBgOver;
|
||||
nonPremiumFg: windowFg;
|
||||
gradientFromLeft: false;
|
||||
}
|
||||
|
||||
// Preview.
|
||||
|
@ -74,15 +86,16 @@ premiumVideoWidth: 182px;
|
|||
|
||||
// Graphics.
|
||||
|
||||
premiumBubblePadding: margins(14px, 0px, 14px, 0px);
|
||||
premiumBubblePenWidth: 6;
|
||||
premiumBubbleHeight: 40px;
|
||||
premiumBubbleSkip: 8px;
|
||||
premiumBubbleWidthLimit: 80px;
|
||||
premiumBubbleTextSkip: 3px;
|
||||
premiumBubbleSlideDuration: 1000;
|
||||
premiumBubbleTailSize: size(21px, 7px);
|
||||
premiumBubbleFont: font(19px);
|
||||
defaultPremiumBubble: PremiumBubble {
|
||||
widthLimit: 80px;
|
||||
height: 40px;
|
||||
padding: margins(14px, 0px, 14px, 0px);
|
||||
skip: 8px;
|
||||
penWidth: 6px;
|
||||
textSkip: 3px;
|
||||
tailSize: size(21px, 7px);
|
||||
font: font(19px);
|
||||
}
|
||||
premiumLineTextSkip: 11px;
|
||||
premiumInfographicPadding: margins(0px, 10px, 0px, 15px);
|
||||
|
||||
|
@ -93,6 +106,7 @@ premiumIconGroups: icon {{ "limits/groups", settingsIconFg }};
|
|||
premiumIconLinks: icon {{ "limits/links", settingsIconFg }};
|
||||
premiumIconPins: icon {{ "limits/pins", settingsIconFg }};
|
||||
premiumIconAccounts: icon {{ "limits/accounts", settingsIconFg }};
|
||||
premiumIconBoost: icon {{ "limits/boost", settingsIconFg }};
|
||||
|
||||
premiumAccountsCheckbox: RoundImageCheckbox(defaultPeerListCheckbox) {
|
||||
imageRadius: 27px;
|
||||
|
@ -176,3 +190,43 @@ premiumGiftTerms: FlatLabel(defaultFlatLabel) {
|
|||
premiumGiftBox: Box(premiumPreviewBox) {
|
||||
buttonPadding: margins(12px, 12px, 12px, 12px);
|
||||
}
|
||||
|
||||
boostSkipTop: 37px;
|
||||
boostLimits: PremiumLimits(defaultPremiumLimits) {
|
||||
gradientFromLeft: true;
|
||||
}
|
||||
boostBubble: PremiumBubble(defaultPremiumBubble) {
|
||||
height: 32px;
|
||||
padding: margins(7px, 0px, 11px, 0px);
|
||||
skip: 5px;
|
||||
textSkip: 2px;
|
||||
tailSize: size(14px, 6px);
|
||||
font: font(16px);
|
||||
}
|
||||
boostTitleSkip: 32px;
|
||||
boostTitle: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
textFg: windowBoldFg;
|
||||
align: align(top);
|
||||
maxHeight: 24px;
|
||||
style: TextStyle(boxTextStyle) {
|
||||
font: font(17px semibold);
|
||||
linkFont: font(17px semibold);
|
||||
linkFontOver: font(17px semibold);
|
||||
}
|
||||
}
|
||||
boostTextSkip: 5px;
|
||||
boostText: FlatLabel(defaultFlatLabel) {
|
||||
minWidth: 40px;
|
||||
align: align(top);
|
||||
}
|
||||
boostBottomSkip: 6px;
|
||||
boostBox: Box(premiumPreviewDoubledLimitsBox) {
|
||||
buttonPadding: margins(22px, 22px, 22px, 22px);
|
||||
buttonHeight: 42px;
|
||||
button: RoundButton(defaultActiveButton) {
|
||||
height: 42px;
|
||||
textTop: 12px;
|
||||
font: font(13px semibold);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ using TextFactory = Fn<QString(int)>;
|
|||
constexpr auto kBubbleRadiusSubtractor = 2;
|
||||
constexpr auto kDeflectionSmall = 20.;
|
||||
constexpr auto kDeflection = 30.;
|
||||
constexpr auto kSlideDuration = crl::time(1000);
|
||||
|
||||
constexpr auto kStepBeforeDeflection = 0.75;
|
||||
constexpr auto kStepAfterDeflection = kStepBeforeDeflection
|
||||
|
@ -185,6 +186,7 @@ public:
|
|||
using EdgeProgress = float64;
|
||||
|
||||
Bubble(
|
||||
const style::PremiumBubble &st,
|
||||
Fn<void()> updateCallback,
|
||||
TextFactory textFactory,
|
||||
const style::icon *icon,
|
||||
|
@ -206,14 +208,13 @@ public:
|
|||
private:
|
||||
[[nodiscard]] int filledWidth() const;
|
||||
|
||||
const style::PremiumBubble &_st;
|
||||
|
||||
const Fn<void()> _updateCallback;
|
||||
const TextFactory _textFactory;
|
||||
|
||||
const style::font &_font;
|
||||
const style::margins &_padding;
|
||||
const style::icon *_icon;
|
||||
NumbersAnimation _numberAnimation;
|
||||
const QSize _tailSize;
|
||||
const int _height;
|
||||
const int _textTop;
|
||||
const bool _premiumPossible;
|
||||
|
@ -227,19 +228,18 @@ private:
|
|||
};
|
||||
|
||||
Bubble::Bubble(
|
||||
const style::PremiumBubble &st,
|
||||
Fn<void()> updateCallback,
|
||||
TextFactory textFactory,
|
||||
const style::icon *icon,
|
||||
bool premiumPossible)
|
||||
: _updateCallback(std::move(updateCallback))
|
||||
: _st(st)
|
||||
, _updateCallback(std::move(updateCallback))
|
||||
, _textFactory(std::move(textFactory))
|
||||
, _font(st::premiumBubbleFont)
|
||||
, _padding(st::premiumBubblePadding)
|
||||
, _icon(icon)
|
||||
, _numberAnimation(_font, _updateCallback)
|
||||
, _tailSize(st::premiumBubbleTailSize)
|
||||
, _height(st::premiumBubbleHeight + _tailSize.height())
|
||||
, _textTop((_height - _tailSize.height() - _font->height) / 2)
|
||||
, _numberAnimation(_st.font, _updateCallback)
|
||||
, _height(_st.height + _st.tailSize.height())
|
||||
, _textTop((_height - _st.tailSize.height() - _st.font->height) / 2)
|
||||
, _premiumPossible(premiumPossible) {
|
||||
_numberAnimation.setDisabledMonospace(true);
|
||||
_numberAnimation.setWidthChangedCallback([=] {
|
||||
|
@ -258,14 +258,14 @@ int Bubble::height() const {
|
|||
}
|
||||
|
||||
int Bubble::bubbleRadius() const {
|
||||
return (_height - _tailSize.height()) / 2 - kBubbleRadiusSubtractor;
|
||||
return (_height - _st.tailSize.height()) / 2 - kBubbleRadiusSubtractor;
|
||||
}
|
||||
|
||||
int Bubble::filledWidth() const {
|
||||
return _padding.left()
|
||||
return _st.padding.left()
|
||||
+ _icon->width()
|
||||
+ st::premiumBubbleTextSkip
|
||||
+ _padding.right();
|
||||
+ _st.textSkip
|
||||
+ _st.padding.right();
|
||||
}
|
||||
|
||||
int Bubble::width() const {
|
||||
|
@ -273,7 +273,7 @@ int Bubble::width() const {
|
|||
}
|
||||
|
||||
int Bubble::countMaxWidth(int maxCounter) const {
|
||||
auto numbers = Ui::NumbersAnimation(_font, [] {});
|
||||
auto numbers = Ui::NumbersAnimation(_st.font, [] {});
|
||||
numbers.setDisabledMonospace(true);
|
||||
numbers.setDuration(0);
|
||||
numbers.setText(_textFactory(0), 0);
|
||||
|
@ -302,18 +302,18 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) {
|
|||
return;
|
||||
}
|
||||
|
||||
const auto penWidth = st::premiumBubblePenWidth;
|
||||
const auto penWidth = _st.penWidth;
|
||||
const auto penWidthHalf = penWidth / 2;
|
||||
const auto bubbleRect = r - style::margins(
|
||||
penWidthHalf,
|
||||
penWidthHalf,
|
||||
penWidthHalf,
|
||||
_tailSize.height() + penWidthHalf);
|
||||
_st.tailSize.height() + penWidthHalf);
|
||||
{
|
||||
const auto radius = bubbleRadius();
|
||||
auto pathTail = QPainterPath();
|
||||
|
||||
const auto tailWHalf = _tailSize.width() / 2.;
|
||||
const auto tailWHalf = _st.tailSize.width() / 2.;
|
||||
const auto progress = _tailEdge;
|
||||
|
||||
const auto tailTop = bubbleRect.y() + bubbleRect.height();
|
||||
|
@ -326,7 +326,7 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) {
|
|||
const auto tailCenter = tailLeft + tailWHalf;
|
||||
const auto tailRight = [&] {
|
||||
const auto max = bubbleRect.x() + bubbleRect.width();
|
||||
const auto right = tailLeft + _tailSize.width();
|
||||
const auto right = tailLeft + _st.tailSize.width();
|
||||
const auto bottomMax = max - radius;
|
||||
return (right > bottomMax)
|
||||
? std::max(float64(tailCenter), float64(bottomMax))
|
||||
|
@ -335,7 +335,7 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) {
|
|||
if (_premiumPossible) {
|
||||
pathTail.moveTo(tailLeftFull, tailTop);
|
||||
pathTail.lineTo(tailLeft, tailTop);
|
||||
pathTail.lineTo(tailCenter, tailTop + _tailSize.height());
|
||||
pathTail.lineTo(tailCenter, tailTop + _st.tailSize.height());
|
||||
pathTail.lineTo(tailRight, tailTop);
|
||||
pathTail.lineTo(tailRight, tailTop - radius);
|
||||
pathTail.moveTo(tailLeftFull, tailTop);
|
||||
|
@ -365,8 +365,8 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) {
|
|||
}
|
||||
}
|
||||
p.setPen(st::activeButtonFg);
|
||||
p.setFont(_font);
|
||||
const auto iconLeft = r.x() + _padding.left();
|
||||
p.setFont(_st.font);
|
||||
const auto iconLeft = r.x() + _st.padding.left();
|
||||
_icon->paint(
|
||||
p,
|
||||
iconLeft,
|
||||
|
@ -374,7 +374,7 @@ void Bubble::paintBubble(QPainter &p, const QRect &r, const QBrush &brush) {
|
|||
bubbleRect.width());
|
||||
_numberAnimation.paint(
|
||||
p,
|
||||
iconLeft + _icon->width() + st::premiumBubbleTextSkip,
|
||||
iconLeft + _icon->width() + _st.textSkip,
|
||||
r.y() + _textTop,
|
||||
width() / 2);
|
||||
}
|
||||
|
@ -387,8 +387,9 @@ class BubbleWidget final : public Ui::RpWidget {
|
|||
public:
|
||||
BubbleWidget(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::PremiumBubble &st,
|
||||
TextFactory textFactory,
|
||||
int current,
|
||||
rpl::producer<BubbleRowState> state,
|
||||
int maxCounter,
|
||||
bool premiumPossible,
|
||||
rpl::producer<> showFinishes,
|
||||
|
@ -398,7 +399,12 @@ protected:
|
|||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
const int _currentCounter;
|
||||
void animateTo(BubbleRowState state);
|
||||
|
||||
const style::PremiumBubble &_st;
|
||||
BubbleRowState _animatingFrom;
|
||||
float64 _animatingFromResultRatio = 0.;
|
||||
rpl::variable<BubbleRowState> _state;
|
||||
const int _maxCounter;
|
||||
Bubble _bubble;
|
||||
const int _maxBubbleWidth;
|
||||
|
@ -419,28 +425,33 @@ private:
|
|||
|
||||
BubbleWidget::BubbleWidget(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::PremiumBubble &st,
|
||||
TextFactory textFactory,
|
||||
int current,
|
||||
rpl::producer<BubbleRowState> state,
|
||||
int maxCounter,
|
||||
bool premiumPossible,
|
||||
rpl::producer<> showFinishes,
|
||||
const style::icon *icon)
|
||||
: RpWidget(parent)
|
||||
, _currentCounter(current)
|
||||
, _st(st)
|
||||
, _state(std::move(state))
|
||||
, _maxCounter(maxCounter)
|
||||
, _bubble([=] { update(); }, std::move(textFactory), icon, premiumPossible)
|
||||
, _bubble(
|
||||
_st,
|
||||
[=] { update(); },
|
||||
std::move(textFactory),
|
||||
icon,
|
||||
premiumPossible)
|
||||
, _maxBubbleWidth(_bubble.countMaxWidth(_maxCounter))
|
||||
, _premiumPossible(premiumPossible)
|
||||
, _deflection(kDeflection)
|
||||
, _stepBeforeDeflection(kStepBeforeDeflection)
|
||||
, _stepAfterDeflection(kStepAfterDeflection) {
|
||||
const auto resizeTo = [=](int w, int h) {
|
||||
_deflection = (w > st::premiumBubbleWidthLimit)
|
||||
_deflection = (w > _st.widthLimit)
|
||||
? kDeflectionSmall
|
||||
: kDeflection;
|
||||
_spaceForDeflection = QSize(
|
||||
st::premiumBubbleSkip,
|
||||
st::premiumBubbleSkip);
|
||||
_spaceForDeflection = QSize(_st.skip, _st.skip);
|
||||
resize(QSize(w, h) + _spaceForDeflection);
|
||||
};
|
||||
|
||||
|
@ -450,97 +461,113 @@ BubbleWidget::BubbleWidget(
|
|||
resizeTo(_bubble.width(), _bubble.height());
|
||||
}, lifetime());
|
||||
|
||||
const auto moveEndPoint = _currentCounter / float64(_maxCounter);
|
||||
std::move(
|
||||
showFinishes
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
_state.value(
|
||||
) | rpl::start_with_next([=](BubbleRowState state) {
|
||||
animateTo(state);
|
||||
}, lifetime());
|
||||
}, lifetime());
|
||||
}
|
||||
|
||||
void BubbleWidget::animateTo(BubbleRowState state) {
|
||||
const auto parent = parentWidget();
|
||||
const auto computeLeft = [=](float64 pointRatio, float64 animProgress) {
|
||||
const auto &padding = st::boxRowPadding;
|
||||
const auto halfWidth = (_maxBubbleWidth / 2);
|
||||
const auto left = padding.left();
|
||||
const auto right = padding.right();
|
||||
return ((parent->width() - left - right)
|
||||
* pointRatio
|
||||
* animProgress)
|
||||
- halfWidth
|
||||
+ left;
|
||||
const auto available = parent->width() - left - right;
|
||||
const auto delta = (pointRatio - _animatingFromResultRatio);
|
||||
const auto center = available
|
||||
* (_animatingFromResultRatio + delta * animProgress);
|
||||
return center - halfWidth + left;
|
||||
};
|
||||
|
||||
std::move(
|
||||
showFinishes
|
||||
) | rpl::take(1) | rpl::start_with_next([=] {
|
||||
const auto computeEdge = [=] {
|
||||
return parent->width()
|
||||
- st::boxRowPadding.right()
|
||||
- _maxBubbleWidth;
|
||||
};
|
||||
struct LeftEdge final {
|
||||
float64 goodPointRatio = 0.;
|
||||
float64 bubbleLeftEdge = 0.;
|
||||
};
|
||||
const auto leftEdge = [&]() -> LeftEdge {
|
||||
const auto finish = computeLeft(moveEndPoint, 1.);
|
||||
const auto &padding = st::boxRowPadding;
|
||||
if (finish <= padding.left()) {
|
||||
const auto halfWidth = (_maxBubbleWidth / 2);
|
||||
const auto goodPointRatio = float64(halfWidth)
|
||||
/ (parent->width() - padding.left() - padding.right());
|
||||
const auto bubbleLeftEdge = (padding.left() - finish)
|
||||
/ (_maxBubbleWidth / 2.);
|
||||
return { goodPointRatio, bubbleLeftEdge };
|
||||
}
|
||||
return {};
|
||||
}();
|
||||
const auto checkBubbleRightEdge = [&]() -> Bubble::EdgeProgress {
|
||||
const auto finish = computeLeft(moveEndPoint, 1.);
|
||||
const auto edge = computeEdge();
|
||||
return (finish >= edge)
|
||||
? (finish - edge) / (_maxBubbleWidth / 2.)
|
||||
: 0.;
|
||||
};
|
||||
const auto bubbleRightEdge = checkBubbleRightEdge();
|
||||
_ignoreDeflection = bubbleRightEdge || leftEdge.goodPointRatio;
|
||||
if (_ignoreDeflection) {
|
||||
_stepBeforeDeflection = 1.;
|
||||
_stepAfterDeflection = 1.;
|
||||
const auto moveEndPoint = state.ratio;
|
||||
const auto computeEdge = [=] {
|
||||
return parent->width()
|
||||
- st::boxRowPadding.right()
|
||||
- _maxBubbleWidth;
|
||||
};
|
||||
struct LeftEdge final {
|
||||
float64 goodPointRatio = 0.;
|
||||
float64 bubbleLeftEdge = 0.;
|
||||
};
|
||||
const auto leftEdge = [&]() -> LeftEdge {
|
||||
const auto finish = computeLeft(moveEndPoint, 1.);
|
||||
const auto &padding = st::boxRowPadding;
|
||||
if (finish <= padding.left()) {
|
||||
const auto halfWidth = (_maxBubbleWidth / 2);
|
||||
const auto goodPointRatio = float64(halfWidth)
|
||||
/ (parent->width() - padding.left() - padding.right());
|
||||
const auto bubbleLeftEdge = (padding.left() - finish)
|
||||
/ (_maxBubbleWidth / 2.);
|
||||
return { goodPointRatio, bubbleLeftEdge };
|
||||
}
|
||||
const auto resultMoveEndPoint = leftEdge.goodPointRatio
|
||||
? leftEdge.goodPointRatio
|
||||
: moveEndPoint;
|
||||
_bubble.setFlipHorizontal(leftEdge.bubbleLeftEdge);
|
||||
return {};
|
||||
}();
|
||||
const auto checkBubbleRightEdge = [&]() -> Bubble::EdgeProgress {
|
||||
const auto finish = computeLeft(moveEndPoint, 1.);
|
||||
const auto edge = computeEdge();
|
||||
return (finish >= edge)
|
||||
? (finish - edge) / (_maxBubbleWidth / 2.)
|
||||
: 0.;
|
||||
};
|
||||
const auto bubbleRightEdge = checkBubbleRightEdge();
|
||||
_ignoreDeflection = !_state.current().dynamic
|
||||
&& (bubbleRightEdge || leftEdge.goodPointRatio);
|
||||
if (_ignoreDeflection) {
|
||||
_stepBeforeDeflection = 1.;
|
||||
_stepAfterDeflection = 1.;
|
||||
}
|
||||
const auto resultMoveEndPoint = leftEdge.goodPointRatio
|
||||
? leftEdge.goodPointRatio
|
||||
: moveEndPoint;
|
||||
_bubble.setFlipHorizontal(leftEdge.bubbleLeftEdge);
|
||||
|
||||
_appearanceAnimation.start([=](float64 value) {
|
||||
const auto moveProgress = std::clamp(
|
||||
(value / _stepBeforeDeflection),
|
||||
0.,
|
||||
1.);
|
||||
const auto counterProgress = std::clamp(
|
||||
(value / _stepAfterDeflection),
|
||||
0.,
|
||||
1.);
|
||||
moveToLeft(
|
||||
computeLeft(resultMoveEndPoint, moveProgress)
|
||||
- (_maxBubbleWidth / 2.) * bubbleRightEdge,
|
||||
0);
|
||||
const auto duration = kSlideDuration
|
||||
* (_ignoreDeflection ? kStepBeforeDeflection : 1.)
|
||||
* ((_state.current().ratio < 0.001) ? 0.5 : 1.);
|
||||
_appearanceAnimation.start([=](float64 value) {
|
||||
if (!_appearanceAnimation.animating()) {
|
||||
_animatingFrom = state;
|
||||
_animatingFromResultRatio = resultMoveEndPoint;
|
||||
}
|
||||
const auto moveProgress = std::clamp(
|
||||
(value / _stepBeforeDeflection),
|
||||
0.,
|
||||
1.);
|
||||
const auto counterProgress = std::clamp(
|
||||
(value / _stepAfterDeflection),
|
||||
0.,
|
||||
1.);
|
||||
moveToLeft(
|
||||
std::max(
|
||||
int(base::SafeRound(
|
||||
(computeLeft(resultMoveEndPoint, moveProgress)
|
||||
- (_maxBubbleWidth / 2.) * bubbleRightEdge))),
|
||||
0),
|
||||
0);
|
||||
|
||||
const auto counter = int(0 + counterProgress * _currentCounter);
|
||||
// if (!(counter % 4) || counterProgress > 0.8) {
|
||||
_bubble.setCounter(counter);
|
||||
// }
|
||||
const auto now = _animatingFrom.counter
|
||||
+ counterProgress * (state.counter - _animatingFrom.counter);
|
||||
_bubble.setCounter(int(base::SafeRound(now)));
|
||||
|
||||
const auto edgeProgress = (leftEdge.bubbleLeftEdge
|
||||
? leftEdge.bubbleLeftEdge
|
||||
: bubbleRightEdge) * value;
|
||||
_bubble.setTailEdge(edgeProgress);
|
||||
update();
|
||||
},
|
||||
0.,
|
||||
1.,
|
||||
st::premiumBubbleSlideDuration
|
||||
* (_ignoreDeflection ? kStepBeforeDeflection : 1.),
|
||||
anim::easeOutCirc);
|
||||
}, lifetime());
|
||||
const auto edgeProgress = leftEdge.bubbleLeftEdge
|
||||
? leftEdge.bubbleLeftEdge
|
||||
: (bubbleRightEdge * value);
|
||||
_bubble.setTailEdge(edgeProgress);
|
||||
update();
|
||||
},
|
||||
0.,
|
||||
1.,
|
||||
duration,
|
||||
anim::easeOutCirc);
|
||||
}
|
||||
|
||||
void BubbleWidget::paintEvent(QPaintEvent *e) {
|
||||
if (_bubble.counter() <= 0) {
|
||||
if (_bubble.counter() < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -561,10 +588,11 @@ void BubbleWidget::paintEvent(QPaintEvent *e) {
|
|||
_cachedGradient = std::move(gradient);
|
||||
|
||||
const auto progress = _appearanceAnimation.value(1.);
|
||||
const auto scaleProgress = std::clamp(
|
||||
(progress / _stepBeforeDeflection),
|
||||
0.,
|
||||
1.);
|
||||
const auto finalScale = (_animatingFromResultRatio > 0.)
|
||||
|| (_state.current().ratio < 0.001);
|
||||
const auto scaleProgress = finalScale
|
||||
? 1.
|
||||
: std::clamp((progress / _stepBeforeDeflection), 0., 1.);
|
||||
const auto scale = scaleProgress;
|
||||
const auto rotationProgress = std::clamp(
|
||||
(progress - _stepBeforeDeflection) / (1. - _stepBeforeDeflection),
|
||||
|
@ -608,6 +636,12 @@ public:
|
|||
QString min,
|
||||
float64 ratio);
|
||||
|
||||
Line(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio);
|
||||
|
||||
void setColorOverride(QBrush brush);
|
||||
|
||||
protected:
|
||||
|
@ -618,16 +652,16 @@ private:
|
|||
|
||||
const style::PremiumLimits &_st;
|
||||
|
||||
int _leftWidth = 0;
|
||||
int _rightWidth = 0;
|
||||
|
||||
QPixmap _leftPixmap;
|
||||
QPixmap _rightPixmap;
|
||||
|
||||
Ui::Text::String _leftText;
|
||||
Ui::Text::String _rightText;
|
||||
Ui::Text::String _rightLabel;
|
||||
float64 _ratio = 0.;
|
||||
Ui::Animations::Simple _animation;
|
||||
Ui::Text::String _leftLabel;
|
||||
Ui::Text::String _leftText;
|
||||
Ui::Text::String _rightLabel;
|
||||
Ui::Text::String _rightText;
|
||||
bool _dynamic = false;
|
||||
|
||||
std::optional<QBrush> _overrideBrush;
|
||||
|
||||
|
@ -654,24 +688,47 @@ Line::Line(
|
|||
QString max,
|
||||
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)) {
|
||||
}
|
||||
|
||||
Line::Line(
|
||||
not_null<Ui::RpWidget*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio)
|
||||
: Ui::RpWidget(parent)
|
||||
, _st(st)
|
||||
, _leftText(st::semiboldTextStyle, tr::lng_premium_free(tr::now))
|
||||
, _rightText(st::semiboldTextStyle, tr::lng_premium(tr::now))
|
||||
, _rightLabel(st::semiboldTextStyle, max)
|
||||
, _leftLabel(st::semiboldTextStyle, min) {
|
||||
, _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);
|
||||
|
||||
sizeValue(
|
||||
) | rpl::start_with_next([=](const QSize &s) {
|
||||
if (s.isEmpty()) {
|
||||
return;
|
||||
std::move(ratio) | rpl::start_with_next([=](float64 ratio) {
|
||||
if (width() > 0) {
|
||||
const auto from = _animation.value(_ratio);
|
||||
const auto duration = kSlideDuration * kStepBeforeDeflection;
|
||||
_animation.start([=] {
|
||||
update();
|
||||
}, from, ratio, duration, anim::easeOutCirc);
|
||||
}
|
||||
_leftWidth = int(base::SafeRound(s.width() * ratio));
|
||||
_rightWidth = (s.width() - _leftWidth);
|
||||
recache(s);
|
||||
_ratio = ratio;
|
||||
}, lifetime());
|
||||
|
||||
sizeValue(
|
||||
) | rpl::filter([](QSize size) {
|
||||
return !size.isEmpty();
|
||||
}) | rpl::start_with_next([=](QSize size) {
|
||||
recache(size);
|
||||
update();
|
||||
}, lifetime());
|
||||
|
||||
}
|
||||
|
||||
void Line::setColorOverride(QBrush brush) {
|
||||
|
@ -685,52 +742,64 @@ void Line::setColorOverride(QBrush brush) {
|
|||
void Line::paintEvent(QPaintEvent *event) {
|
||||
Painter p(this);
|
||||
|
||||
p.drawPixmap(0, 0, _leftPixmap);
|
||||
p.drawPixmap(_leftWidth, 0, _rightPixmap);
|
||||
const auto ratio = _animation.value(_ratio);
|
||||
const auto left = int(base::SafeRound(ratio * width()));
|
||||
const auto dpr = int(_leftPixmap.devicePixelRatio());
|
||||
const auto height = _leftPixmap.height() / dpr;
|
||||
p.drawPixmap(
|
||||
QRect(0, 0, left, height),
|
||||
_leftPixmap,
|
||||
QRect(0, 0, left * dpr, height * dpr));
|
||||
p.drawPixmap(
|
||||
QRect(left, 0, width() - left, height),
|
||||
_rightPixmap,
|
||||
QRect(left * dpr, 0, (width() - left) * dpr, height * dpr));
|
||||
|
||||
p.setFont(st::normalFont);
|
||||
|
||||
const auto textPadding = st::premiumLineTextSkip;
|
||||
const auto textTop = (height() - _leftText.minHeight()) / 2;
|
||||
const auto textTop = (height - _leftLabel.minHeight()) / 2;
|
||||
|
||||
const auto leftMinWidth = _leftLabel.maxWidth()
|
||||
+ _leftText.maxWidth()
|
||||
+ 3 * textPadding;
|
||||
if (_leftWidth >= leftMinWidth) {
|
||||
p.setPen(_st.nonPremiumFg);
|
||||
_leftLabel.drawRight(
|
||||
const auto pen = [&](bool gradient) {
|
||||
return gradient ? st::activeButtonFg : _st.nonPremiumFg;
|
||||
};
|
||||
if (!_dynamic && left >= leftMinWidth) {
|
||||
p.setPen(pen(_st.gradientFromLeft));
|
||||
_leftLabel.drawLeft(
|
||||
p,
|
||||
textPadding,
|
||||
textTop,
|
||||
_leftWidth - textPadding,
|
||||
_leftWidth,
|
||||
left - textPadding,
|
||||
left);
|
||||
_leftText.drawRight(
|
||||
p,
|
||||
textPadding,
|
||||
textTop,
|
||||
left - textPadding,
|
||||
left,
|
||||
style::al_right);
|
||||
_leftText.drawLeft(
|
||||
p,
|
||||
textPadding,
|
||||
textTop,
|
||||
_leftWidth - textPadding,
|
||||
_leftWidth);
|
||||
}
|
||||
const auto rightMinWidth = 2 * _rightLabel.maxWidth()
|
||||
const auto right = width() - left;
|
||||
const auto rightMinWidth = 2 * _rightText.maxWidth()
|
||||
+ 3 * textPadding;
|
||||
if (_rightWidth >= rightMinWidth) {
|
||||
p.setPen(st::activeButtonFg);
|
||||
_rightLabel.drawRight(
|
||||
if (!_dynamic && right >= rightMinWidth) {
|
||||
p.setPen(pen(!_st.gradientFromLeft));
|
||||
_rightLabel.drawLeftElided(
|
||||
p,
|
||||
left + textPadding,
|
||||
textTop,
|
||||
(right - _rightText.countWidth(right) - textPadding * 2),
|
||||
right);
|
||||
_rightText.drawRight(
|
||||
p,
|
||||
textPadding,
|
||||
textTop,
|
||||
_rightWidth - textPadding,
|
||||
right - textPadding,
|
||||
width(),
|
||||
style::al_right);
|
||||
_rightText.drawLeftElided(
|
||||
p,
|
||||
_leftWidth + textPadding,
|
||||
textTop,
|
||||
(_rightWidth
|
||||
- _rightLabel.countWidth(_rightWidth)
|
||||
- textPadding * 2),
|
||||
_rightWidth);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -750,40 +819,46 @@ void Line::recache(const QSize &s) {
|
|||
result.addRoundedRect(r(width), st::buttonRadius, st::buttonRadius);
|
||||
return result;
|
||||
};
|
||||
const auto width = s.width();
|
||||
const auto fill = [&](QPainter &p, QPainterPath path, bool gradient) {
|
||||
if (!gradient) {
|
||||
p.fillPath(path, _st.nonPremiumBg);
|
||||
} else if (_overrideBrush) {
|
||||
p.fillPath(path, *_overrideBrush);
|
||||
} else {
|
||||
p.fillPath(path, QBrush(ComputeGradient(this, 0, width)));
|
||||
}
|
||||
};
|
||||
const auto textPadding = st::premiumLineTextSkip;
|
||||
const auto textTop = (s.height() - _leftLabel.minHeight()) / 2;
|
||||
const auto rwidth = _rightLabel.maxWidth();
|
||||
const auto pen = [&](bool gradient) {
|
||||
return gradient ? st::activeButtonFg : _st.nonPremiumFg;
|
||||
};
|
||||
{
|
||||
auto leftPixmap = pixmap(_leftWidth);
|
||||
auto p = QPainter(&leftPixmap);
|
||||
auto leftPixmap = pixmap(width);
|
||||
auto p = Painter(&leftPixmap);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
auto pathRect = QPainterPath();
|
||||
auto halfRect = r(_leftWidth);
|
||||
halfRect.setLeft(halfRect.center().x());
|
||||
pathRect.addRect(halfRect);
|
||||
|
||||
p.fillPath(pathRound(_leftWidth) + pathRect, _st.nonPremiumBg);
|
||||
|
||||
fill(p, pathRound(width), _st.gradientFromLeft);
|
||||
if (_dynamic) {
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(pen(_st.gradientFromLeft));
|
||||
_leftLabel.drawLeft(p, textPadding, textTop, width, width);
|
||||
_rightLabel.drawRight(p, textPadding, textTop, rwidth, width);
|
||||
}
|
||||
_leftPixmap = std::move(leftPixmap);
|
||||
}
|
||||
{
|
||||
auto rightPixmap = pixmap(_rightWidth);
|
||||
auto p = QPainter(&rightPixmap);
|
||||
auto rightPixmap = pixmap(width);
|
||||
auto p = Painter(&rightPixmap);
|
||||
PainterHighQualityEnabler hq(p);
|
||||
auto pathRect = QPainterPath();
|
||||
auto halfRect = r(_rightWidth);
|
||||
halfRect.setRight(halfRect.center().x());
|
||||
pathRect.addRect(halfRect);
|
||||
|
||||
if (_overrideBrush) {
|
||||
p.fillPath(pathRound(_rightWidth) + pathRect, *_overrideBrush);
|
||||
} else {
|
||||
auto gradient = ComputeGradient(
|
||||
this,
|
||||
_leftPixmap.width() / style::DevicePixelRatio(),
|
||||
_rightWidth);
|
||||
p.fillPath(
|
||||
pathRound(_rightWidth) + pathRect,
|
||||
QBrush(std::move(gradient)));
|
||||
fill(p, pathRound(width), !_st.gradientFromLeft);
|
||||
if (_dynamic) {
|
||||
p.setFont(st::normalFont);
|
||||
p.setPen(pen(!_st.gradientFromLeft));
|
||||
_leftLabel.drawLeft(p, textPadding, textTop, width, width);
|
||||
_rightLabel.drawRight(p, textPadding, textTop, rwidth, width);
|
||||
}
|
||||
|
||||
_rightPixmap = std::move(rightPixmap);
|
||||
}
|
||||
}
|
||||
|
@ -792,6 +867,7 @@ void Line::recache(const QSize &s) {
|
|||
|
||||
void AddBubbleRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumBubble &st,
|
||||
rpl::producer<> showFinishes,
|
||||
int min,
|
||||
int current,
|
||||
|
@ -799,12 +875,36 @@ void AddBubbleRow(
|
|||
bool premiumPossible,
|
||||
std::optional<tr::phrase<lngtag_count>> phrase,
|
||||
const style::icon *icon) {
|
||||
AddBubbleRow(
|
||||
parent,
|
||||
st,
|
||||
std::move(showFinishes),
|
||||
rpl::single(BubbleRowState{
|
||||
.counter = current,
|
||||
.ratio = (current - min) / float64(max - min),
|
||||
}),
|
||||
max,
|
||||
premiumPossible,
|
||||
ProcessTextFactory(phrase),
|
||||
icon);
|
||||
}
|
||||
|
||||
void AddBubbleRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumBubble &st,
|
||||
rpl::producer<> showFinishes,
|
||||
rpl::producer<BubbleRowState> state,
|
||||
int max,
|
||||
bool premiumPossible,
|
||||
Fn<QString(int)> text,
|
||||
const style::icon *icon) {
|
||||
const auto container = parent->add(
|
||||
object_ptr<Ui::FixedHeightWidget>(parent, 0));
|
||||
const auto bubble = Ui::CreateChild<BubbleWidget>(
|
||||
container,
|
||||
ProcessTextFactory(phrase),
|
||||
current,
|
||||
st,
|
||||
text ? std::move(text) : ProcessTextFactory(std::nullopt),
|
||||
std::move(state),
|
||||
max,
|
||||
premiumPossible,
|
||||
std::move(showFinishes),
|
||||
|
@ -844,6 +944,16 @@ void AddLimitRow(
|
|||
ratio);
|
||||
}
|
||||
|
||||
void AddLimitRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio) {
|
||||
parent->add(
|
||||
object_ptr<Line>(parent, st, std::move(labels), std::move(ratio)),
|
||||
st::boxRowPadding);
|
||||
}
|
||||
|
||||
void AddAccountsRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
AccountsRowArgs &&args) {
|
||||
|
|
|
@ -28,6 +28,7 @@ namespace style {
|
|||
struct RoundImageCheckbox;
|
||||
struct PremiumOption;
|
||||
struct TextStyle;
|
||||
struct PremiumBubble;
|
||||
} // namespace style
|
||||
|
||||
namespace Ui {
|
||||
|
@ -42,6 +43,7 @@ inline constexpr auto kLimitRowRatio = 0.5;
|
|||
|
||||
void AddBubbleRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumBubble &st,
|
||||
rpl::producer<> showFinishes,
|
||||
int min,
|
||||
int current,
|
||||
|
@ -50,6 +52,21 @@ void AddBubbleRow(
|
|||
std::optional<tr::phrase<lngtag_count>> phrase,
|
||||
const style::icon *icon);
|
||||
|
||||
struct BubbleRowState {
|
||||
int counter = 0;
|
||||
float64 ratio = 0.;
|
||||
bool dynamic = false;
|
||||
};
|
||||
void AddBubbleRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumBubble &st,
|
||||
rpl::producer<> showFinishes,
|
||||
rpl::producer<BubbleRowState> state,
|
||||
int max,
|
||||
bool premiumPossible,
|
||||
Fn<QString(int)> text,
|
||||
const style::icon *icon);
|
||||
|
||||
void AddLimitRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
|
@ -65,6 +82,19 @@ void AddLimitRow(
|
|||
int min = 0,
|
||||
float64 ratio = kLimitRowRatio);
|
||||
|
||||
struct LimitRowLabels {
|
||||
QString leftLabel;
|
||||
QString leftCount;
|
||||
QString rightLabel;
|
||||
QString rightCount;
|
||||
bool dynamic = false;
|
||||
};
|
||||
void AddLimitRow(
|
||||
not_null<Ui::VerticalLayout*> parent,
|
||||
const style::PremiumLimits &st,
|
||||
LimitRowLabels labels,
|
||||
rpl::producer<float64> ratio);
|
||||
|
||||
struct AccountsRowArgs final {
|
||||
std::shared_ptr<Ui::RadiobuttonGroup> group;
|
||||
const style::RoundImageCheckbox &st;
|
||||
|
|
|
@ -316,7 +316,10 @@ base::unique_qptr<Ui::SideBarButton> FiltersMenu::prepareButton(
|
|||
if (_reordering) {
|
||||
return;
|
||||
} else if (raw->locked()) {
|
||||
_session->show(Box(FiltersLimitBox, &_session->session()));
|
||||
_session->show(Box(
|
||||
FiltersLimitBox,
|
||||
&_session->session(),
|
||||
std::nullopt));
|
||||
} else if (id >= 0) {
|
||||
_session->setActiveChatsFilter(id);
|
||||
} else {
|
||||
|
|
|
@ -55,6 +55,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "ui/text/text_utilities.h"
|
||||
#include "ui/text/format_values.h" // Ui::FormatPhone.
|
||||
#include "ui/delayed_activation.h"
|
||||
#include "ui/boxes/boost_box.h"
|
||||
#include "ui/chat/attach/attach_bot_webview.h"
|
||||
#include "ui/chat/chat_style.h"
|
||||
#include "ui/chat/chat_theme.h"
|
||||
|
@ -80,6 +81,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "window/themes/window_theme.h"
|
||||
#include "window/window_peer_menu.h"
|
||||
#include "settings/settings_main.h"
|
||||
#include "settings/settings_premium.h"
|
||||
#include "settings/settings_privacy_security.h"
|
||||
#include "styles/style_window.h"
|
||||
#include "styles/style_dialogs.h"
|
||||
|
@ -554,6 +556,8 @@ void SessionNavigation::showPeerByLinkResolved(
|
|||
} else {
|
||||
showPeerInfo(peer, params);
|
||||
}
|
||||
} else if (resolveType == ResolveType::Boost && peer->isBroadcast()) {
|
||||
resolveBoostState(peer->asChannel());
|
||||
} else {
|
||||
// Show specific posts only in channels / supergroups.
|
||||
const auto msgId = peer->isChannel()
|
||||
|
@ -614,6 +618,145 @@ void SessionNavigation::showPeerByLinkResolved(
|
|||
}
|
||||
}
|
||||
|
||||
void SessionNavigation::resolveBoostState(not_null<ChannelData*> channel) {
|
||||
if (_boostStateResolving == channel) {
|
||||
return;
|
||||
}
|
||||
_boostStateResolving = channel;
|
||||
_api.request(MTPstories_GetBoostsStatus(
|
||||
channel->input
|
||||
)).done([=](const MTPstories_BoostsStatus &result) {
|
||||
_boostStateResolving = nullptr;
|
||||
const auto &data = result.data();
|
||||
const auto submit = [=](Fn<void(bool)> 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,
|
||||
},
|
||||
}, submit));
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
_boostStateResolving = nullptr;
|
||||
showToast(u"Error: "_q + error.type());
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SessionNavigation::applyBoost(
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(bool)> done) {
|
||||
_api.request(MTPstories_CanApplyBoost(
|
||||
channel->input
|
||||
)).done([=](const MTPstories_CanApplyBoostResult &result) {
|
||||
result.match([&](const MTPDstories_canApplyBoostOk &) {
|
||||
applyBoostChecked(channel, done);
|
||||
}, [&](const MTPDstories_canApplyBoostReplace &data) {
|
||||
_session->data().processChats(data.vchats());
|
||||
const auto peer = _session->data().peer(
|
||||
peerFromMTP(data.vcurrent_boost()));
|
||||
replaceBoostConfirm(peer, channel, done);
|
||||
});
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
const auto type = error.type();
|
||||
if (type == u"PREMIUM_ACCOUNT_REQUIRED"_q) {
|
||||
const auto jumpToPremium = [=] {
|
||||
const auto id = peerToChannel(channel->id).bare;
|
||||
Settings::ShowPremium(
|
||||
parentController(),
|
||||
"channel_boost__" + QString::number(id));
|
||||
};
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_premium_text(
|
||||
Ui::Text::RichLangValue),
|
||||
.confirmed = jumpToPremium,
|
||||
.confirmText = tr::lng_boost_error_premium_yes(),
|
||||
.title = tr::lng_boost_error_premium_title(),
|
||||
}));
|
||||
} else if (type == u"PREMIUM_GIFTED_NOT_ALLOWED"_q) {
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_gifted_text(
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_gifted_title(),
|
||||
.inform = true,
|
||||
}));
|
||||
} else if (type == u"BOOST_NOT_MODIFIED"_q) {
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_already_text(
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_already_title(),
|
||||
.inform = true,
|
||||
}));
|
||||
} else if (type.startsWith(u"FLOOD_WAIT_"_q)) {
|
||||
const auto seconds = type.mid(u"FLOOD_WAIT_"_q.size()).toInt();
|
||||
const auto days = seconds / 86400;
|
||||
const auto hours = seconds / 3600;
|
||||
const auto minutes = seconds / 60;
|
||||
uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_error_flood_text(
|
||||
lt_left,
|
||||
rpl::single(Ui::Text::Bold((days > 1)
|
||||
? tr::lng_days(tr::now, lt_count, days)
|
||||
: (hours > 1)
|
||||
? tr::lng_hours(tr::now, lt_count, hours)
|
||||
: (minutes > 1)
|
||||
? tr::lng_minutes(tr::now, lt_count, minutes)
|
||||
: tr::lng_seconds(tr::now, lt_count, seconds))),
|
||||
Ui::Text::RichLangValue),
|
||||
.title = tr::lng_boost_error_flood_title(),
|
||||
.inform = true,
|
||||
}));
|
||||
} else {
|
||||
showToast(u"Error: "_q + type);
|
||||
}
|
||||
done(false);
|
||||
}).handleFloodErrors().send();
|
||||
}
|
||||
|
||||
void SessionNavigation::replaceBoostConfirm(
|
||||
not_null<PeerData*> from,
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(bool)> done) {
|
||||
const auto forwarded = std::make_shared<bool>(false);
|
||||
const auto confirmed = [=](Fn<void()> close) {
|
||||
*forwarded = true;
|
||||
applyBoostChecked(channel, done);
|
||||
close();
|
||||
};
|
||||
const auto box = uiShow()->show(Ui::MakeConfirmBox({
|
||||
.text = tr::lng_boost_now_instead(
|
||||
lt_channel,
|
||||
rpl::single(Ui::Text::Bold(from->name())),
|
||||
lt_other,
|
||||
rpl::single(Ui::Text::Bold(channel->name())),
|
||||
Ui::Text::WithEntities),
|
||||
.confirmed = confirmed,
|
||||
.confirmText = tr::lng_boost_now_replace(),
|
||||
}));
|
||||
box->boxClosing() | rpl::filter([=] {
|
||||
return !*forwarded;
|
||||
}) | rpl::start_with_next([=] {
|
||||
done(false);
|
||||
}, box->lifetime());
|
||||
}
|
||||
|
||||
void SessionNavigation::applyBoostChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(bool)> done) {
|
||||
_api.request(MTPstories_ApplyBoost(
|
||||
channel->input
|
||||
)).done([=](const MTPBool &result) {
|
||||
done(true);
|
||||
}).fail([=](const MTP::Error &error) {
|
||||
showToast(u"Error: "_q + error.type());
|
||||
done(false);
|
||||
}).send();
|
||||
}
|
||||
|
||||
void SessionNavigation::joinVoiceChatFromLink(
|
||||
not_null<PeerData*> peer,
|
||||
const PeerByLinkInfo &info) {
|
||||
|
|
|
@ -100,6 +100,7 @@ enum class ResolveType {
|
|||
AddToChannel,
|
||||
ShareGame,
|
||||
Mention,
|
||||
Boost,
|
||||
};
|
||||
|
||||
struct PeerThemeOverride {
|
||||
|
@ -311,6 +312,16 @@ private:
|
|||
not_null<PeerData*> peer,
|
||||
const PeerByLinkInfo &info);
|
||||
|
||||
void resolveBoostState(not_null<ChannelData*> channel);
|
||||
void applyBoost(not_null<ChannelData*> channel, Fn<void(bool)> done);
|
||||
void replaceBoostConfirm(
|
||||
not_null<PeerData*> from,
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(bool)> done);
|
||||
void applyBoostChecked(
|
||||
not_null<ChannelData*> channel,
|
||||
Fn<void(bool)> done);
|
||||
|
||||
const not_null<Main::Session*> _session;
|
||||
|
||||
MTP::Sender _api;
|
||||
|
@ -321,6 +332,8 @@ private:
|
|||
MsgId _showingRepliesRootId = 0;
|
||||
mtpRequestId _showingRepliesRequestId = 0;
|
||||
|
||||
ChannelData *_boostStateResolving = nullptr;
|
||||
|
||||
};
|
||||
|
||||
class SessionController : public SessionNavigation {
|
||||
|
|
|
@ -160,6 +160,8 @@ PRIVATE
|
|||
|
||||
ui/boxes/auto_delete_settings.cpp
|
||||
ui/boxes/auto_delete_settings.h
|
||||
ui/boxes/boost_box.cpp
|
||||
ui/boxes/boost_box.h
|
||||
ui/boxes/calendar_box.cpp
|
||||
ui/boxes/calendar_box.h
|
||||
ui/boxes/choose_date_time.cpp
|
||||
|
|
Loading…
Add table
Reference in a new issue