Handle t.me/channel?boost links.

This commit is contained in:
John Preston 2023-09-12 21:00:39 +04:00
parent 39f8394f98
commit 1c2951598b
17 changed files with 920 additions and 204 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 796 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -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.";

View File

@ -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,

View File

@ -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);

View File

@ -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))) {

View File

@ -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) {

View 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

View 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

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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;

View File

@ -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 {

View File

@ -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) {

View File

@ -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 {

View File

@ -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