AyuGramDesktop/Telegram/SourceFiles/info/channel_statistics/boosts/create_giveaway_box.cpp

1611 lines
49 KiB
C++

/*
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 "info/channel_statistics/boosts/create_giveaway_box.h"
#include "api/api_credits.h"
#include "api/api_premium.h"
#include "base/call_delayed.h"
#include "base/unixtime.h"
#include "countries/countries_instance.h"
#include "data/data_peer.h"
#include "info/channel_statistics/boosts/giveaway/boost_badge.h"
#include "info/channel_statistics/boosts/giveaway/giveaway_list_controllers.h"
#include "info/channel_statistics/boosts/giveaway/giveaway_type_row.h"
#include "info/channel_statistics/boosts/giveaway/select_countries_box.h"
#include "info/channel_statistics/boosts/info_boosts_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "info/statistics/info_statistics_list_controllers.h"
#include "lang/lang_keys.h"
#include "main/main_session.h"
#include "payments/payments_checkout_process.h" // Payments::CheckoutProcess
#include "payments/payments_form.h" // Payments::InvoicePremiumGiftCode
#include "settings/settings_common.h"
#include "settings/settings_premium.h" // Settings::ShowPremium
#include "ui/boxes/choose_date_time.h"
#include "ui/boxes/confirm_box.h"
#include "ui/effects/credits_graphics.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/vertical_list.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/continuous_sliders.h"
#include "ui/widgets/fields/input_field.h"
#include "ui/widgets/labels.h"
#include "ui/wrap/slide_wrap.h"
#include "ui/ui_utility.h"
#include "styles/style_color_indices.h"
#include "styles/style_credits.h"
#include "styles/style_giveaway.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
#include "styles/style_statistics.h"
#include <xxhash.h> // XXH64.
namespace {
constexpr auto kDoneTooltipDuration = 5 * crl::time(1000);
constexpr auto kAdditionalPrizeLengthMax = 128;
[[nodiscard]] QDateTime ThreeDaysAfterToday() {
auto dateNow = QDateTime::currentDateTime();
dateNow = dateNow.addDays(3);
auto timeNow = dateNow.time();
while (timeNow.minute() % 5) {
timeNow = timeNow.addSecs(60);
}
dateNow.setTime(timeNow);
return dateNow;
}
[[nodiscard]] uint64 UniqueIdFromCreditsOption(
const Data::CreditsGiveawayOption &d,
not_null<PeerData*> peer) {
const auto string = QString::number(d.credits)
+ d.storeProduct
+ d.currency
+ QString::number(d.amount)
+ QString::number(peer->id.value)
+ QString::number(peer->session().uniqueId());
return XXH64(string.data(), string.size() * sizeof(ushort), 0);
}
[[nodiscard]] Fn<bool(int)> CreateErrorCallback(
int max,
tr::phrase<lngtag_count> phrase) {
return [=](int count) {
const auto error = (count >= max);
if (error) {
Ui::Toast::Show(phrase(tr::now, lt_count, max));
}
return error;
};
}
[[nodiscard]] QWidget *FindFirstShadowInBox(not_null<Ui::BoxContent*> box) {
for (const auto &child : box->children()) {
if (child && child->isWidgetType()) {
const auto w = static_cast<QWidget*>(child);
if (w->height() == st::lineWidth) {
return w;
}
}
}
return nullptr;
}
void AddPremiumTopBarWithDefaultTitleBar(
not_null<Ui::GenericBox*> box,
rpl::producer<> showFinished,
rpl::producer<QString> titleText,
rpl::producer<TextWithEntities> subtitleText) {
struct State final {
Ui::Animations::Simple animation;
Ui::Text::String title;
Ui::RpWidget close;
};
const auto state = box->lifetime().make_state<State>();
box->setNoContentMargin(true);
std::move(
titleText
) | rpl::start_with_next([=](const QString &s) {
state->title.setText(st::startGiveawayBox.title.style, s);
}, box->lifetime());
const auto hPadding = rect::m::sum::h(st::boxRowPadding);
const auto titlePaintContext = Ui::Text::PaintContext{
.position = st::boxTitlePosition,
.outerWidth = (st::boxWideWidth - hPadding),
.availableWidth = (st::boxWideWidth - hPadding),
};
const auto isCloseBarShown = [=] { return box->scrollTop() > 0; };
const auto closeTopBar = box->setPinnedToTopContent(
object_ptr<Ui::RpWidget>(box));
closeTopBar->resize(box->width(), st::boxTitleHeight);
closeTopBar->paintRequest(
) | rpl::start_with_next([=] {
auto p = Painter(closeTopBar);
const auto r = closeTopBar->rect();
const auto radius = st::boxRadius;
const auto progress = state->animation.value(isCloseBarShown()
? 1.
: 0.);
const auto resultRect = r + QMargins{ 0, 0, 0, radius };
{
auto hq = PainterHighQualityEnabler(p);
if (progress < 1.) {
auto path = QPainterPath();
path.addRect(resultRect);
path.addRect(
st::boxRowPadding.left(),
0,
resultRect.width() - hPadding,
resultRect.height());
p.setClipPath(path);
p.setPen(Qt::NoPen);
p.setBrush(st::boxDividerBg);
p.drawRoundedRect(resultRect, radius, radius);
}
if (progress > 0.) {
p.setOpacity(progress);
p.setClipping(false);
p.setPen(Qt::NoPen);
p.setBrush(st::boxBg);
p.drawRoundedRect(resultRect, radius, radius);
p.setPen(st::startGiveawayBox.title.textFg);
p.setBrush(Qt::NoBrush);
state->title.draw(p, titlePaintContext);
}
}
}, closeTopBar->lifetime());
{
const auto close = Ui::CreateChild<Ui::IconButton>(
closeTopBar.get(),
st::startGiveawayBoxTitleClose);
close->setClickedCallback([=] { box->closeBox(); });
closeTopBar->widthValue(
) | rpl::start_with_next([=](int w) {
const auto &pos = st::giveawayGiftCodeCoverClosePosition;
close->moveToRight(pos.x(), pos.y());
}, box->lifetime());
close->show();
}
const auto bar = Ui::CreateChild<Ui::Premium::TopBar>(
box.get(),
st::startGiveawayCover,
Ui::Premium::TopBarDescriptor{
.clickContextOther = nullptr,
.title = tr::lng_giveaway_new_title(),
.about = std::move(subtitleText),
.light = true,
.optimizeMinistars = false,
});
bar->setAttribute(Qt::WA_TransparentForMouseEvents);
box->addRow(
object_ptr<Ui::BoxContentDivider>(
box.get(),
st::giveawayGiftCodeTopHeight
- st::boxTitleHeight
+ st::boxDividerHeight
+ st::defaultVerticalListSkip,
st::boxDividerBg,
RectPart::Bottom),
{});
bar->setPaused(true);
bar->setRoundEdges(false);
bar->setMaximumHeight(st::giveawayGiftCodeTopHeight);
bar->setMinimumHeight(st::infoLayerTopBarHeight);
bar->resize(bar->width(), bar->maximumHeight());
box->widthValue(
) | rpl::start_with_next([=](int w) {
bar->resizeToWidth(w - hPadding);
bar->moveToLeft(st::boxRowPadding.left(), bar->y());
}, box->lifetime());
std::move(
showFinished
) | rpl::take(1) | rpl::start_with_next([=] {
closeTopBar->raise();
if (const auto shadow = FindFirstShadowInBox(box)) {
bar->stackUnder(shadow);
}
bar->setPaused(false);
box->scrolls(
) | rpl::map(isCloseBarShown) | rpl::distinct_until_changed(
) | rpl::start_with_next([=](bool showBar) {
state->animation.stop();
state->animation.start(
[=] { closeTopBar->update(); },
showBar ? 0. : 1.,
showBar ? 1. : 0.,
st::slideWrapDuration);
}, box->lifetime());
box->scrolls(
) | rpl::start_with_next([=] {
bar->moveToLeft(bar->x(), -box->scrollTop());
}, box->lifetime());
}, box->lifetime());
bar->show();
}
} // namespace
void CreateGiveawayBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionNavigation*> navigation,
not_null<PeerData*> peer,
Fn<void()> reloadOnDone,
std::optional<Data::BoostPrepaidGiveaway> prepaid) {
box->setWidth(st::boxWideWidth);
const auto weakWindow = base::make_weak(navigation->parentController());
using GiveawayType = Giveaway::GiveawayTypeRow::Type;
using GiveawayGroup = Ui::RadioenumGroup<GiveawayType>;
using CreditsGroup = Ui::RadioenumGroup<int>;
struct State final {
State(not_null<PeerData*> p) : apiOptions(p), apiCreditsOptions(p) {
}
Api::PremiumGiftCodeOptions apiOptions;
Api::CreditsGiveawayOptions apiCreditsOptions;
rpl::lifetime lifetimeApi;
std::vector<not_null<PeerData*>> selectedToAward;
rpl::event_stream<> toAwardAmountChanged;
std::vector<not_null<PeerData*>> selectedToSubscribe;
rpl::variable<GiveawayType> typeValue;
rpl::variable<int> sliderValue;
rpl::variable<TimeId> dateValue;
rpl::variable<std::vector<QString>> countriesValue;
rpl::variable<QString> additionalPrize;
rpl::variable<int> chosenMonths;
rpl::variable<bool> showWinners;
rpl::variable<bool> confirmButtonBusy = true;
};
const auto group = peer->isMegagroup();
const auto state = box->lifetime().make_state<State>(peer);
const auto typeGroup = std::make_shared<GiveawayGroup>();
const auto creditsGroup = std::make_shared<CreditsGroup>();
const auto isPrepaidCredits = (prepaid && prepaid->credits);
const auto isSpecificUsers = [=] {
return !state->selectedToAward.empty();
};
const auto hideSpecificUsersOn = [=] {
return rpl::combine(
state->typeValue.value(),
state->toAwardAmountChanged.events_starting_with(
rpl::empty_value()) | rpl::type_erased()
) | rpl::map([=](GiveawayType type, auto) {
return (type == GiveawayType::Credits) || !isSpecificUsers();
});
};
auto showFinished = Ui::BoxShowFinishes(box);
AddPremiumTopBarWithDefaultTitleBar(
box,
rpl::duplicate(showFinished),
rpl::conditional(
hideSpecificUsersOn(),
tr::lng_giveaway_start(),
tr::lng_giveaway_award()),
rpl::conditional(
isPrepaidCredits
? rpl::single(true) | rpl::type_erased()
: state->typeValue.value() | rpl::map(
rpl::mappers::_1 == GiveawayType::Credits),
(peer->isMegagroup()
? tr::lng_giveaway_credits_new_about_group()
: tr::lng_giveaway_credits_new_about()),
(peer->isMegagroup()
? tr::lng_giveaway_new_about_group()
: tr::lng_giveaway_new_about())
) | rpl::map(Ui::Text::RichLangValue));
{
const auto &padding = st::giveawayGiftCodeCoverDividerPadding;
Ui::AddSkip(box->verticalLayout(), padding.bottom());
}
const auto loading = box->addRow(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
box,
object_ptr<Ui::VerticalLayout>(box)));
{
loading->toggle(true, anim::type::instant);
const auto container = loading->entity();
Ui::AddSkip(container);
Ui::AddSkip(container);
container->add(
object_ptr<Ui::CenterWrap<Ui::FlatLabel>>(
box,
object_ptr<Ui::FlatLabel>(
box,
tr::lng_contacts_loading(),
st::giveawayLoadingLabel)));
Ui::AddSkip(container);
Ui::AddSkip(container);
}
const auto contentWrap = box->verticalLayout()->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
box,
object_ptr<Ui::VerticalLayout>(box)));
contentWrap->toggle(false, anim::type::instant);
if (prepaid) {
contentWrap->entity()->add(
object_ptr<Giveaway::GiveawayTypeRow>(
box,
prepaid->credits
? GiveawayType::PrepaidCredits
: GiveawayType::Prepaid,
prepaid->credits ? st::colorIndexOrange : prepaid->id,
tr::lng_boosts_prepaid_giveaway_single(),
prepaid->credits
? tr::lng_boosts_prepaid_giveaway_credits_status(
lt_count,
rpl::single(prepaid->quantity) | tr::to_count(),
lt_amount,
tr::lng_prize_credits_amount(
lt_count_decimal,
rpl::single(prepaid->credits) | tr::to_count()))
: tr::lng_boosts_prepaid_giveaway_status(
lt_count,
rpl::single(prepaid->quantity) | tr::to_count(),
lt_duration,
tr::lng_premium_gift_duration_months(
lt_count,
rpl::single(prepaid->months) | tr::to_count())),
QImage())
)->setAttribute(Qt::WA_TransparentForMouseEvents);
}
if (!prepaid) {
const auto row = contentWrap->entity()->add(
object_ptr<Giveaway::GiveawayTypeRow>(
box,
GiveawayType::Random,
state->toAwardAmountChanged.events_starting_with(
rpl::empty_value()
) | rpl::map([=] {
const auto &selected = state->selectedToAward;
return selected.empty()
? tr::lng_giveaway_create_subtitle()
: (selected.size() == 1)
? rpl::single(selected.front()->name())
: tr::lng_giveaway_award_chosen(
lt_count,
rpl::single(selected.size()) | tr::to_count());
}) | rpl::flatten_latest(),
group));
row->addRadio(typeGroup);
row->setClickedCallback([=] {
auto initBox = [=](not_null<PeerListBox*> peersBox) {
peersBox->setTitle(tr::lng_giveaway_award_option());
auto aboveOwned = object_ptr<Ui::VerticalLayout>(peersBox);
const auto above = aboveOwned.data();
peersBox->peerListSetAboveWidget(std::move(aboveOwned));
Ui::AddSkip(above);
const auto buttonRandom = above->add(
object_ptr<Ui::SettingsButton>(
peersBox,
tr::lng_giveaway_random_button(),
st::settingsButtonLightNoIcon));
buttonRandom->setClickedCallback([=] {
state->selectedToAward.clear();
state->toAwardAmountChanged.fire({});
state->typeValue.force_assign(GiveawayType::Random);
peersBox->closeBox();
});
Ui::AddSkip(above);
peersBox->addButton(tr::lng_settings_save(), [=] {
state->selectedToAward = peersBox->collectSelectedRows();
state->toAwardAmountChanged.fire({});
state->typeValue.force_assign(GiveawayType::Random);
peersBox->closeBox();
});
peersBox->addButton(tr::lng_cancel(), [=] {
peersBox->closeBox();
});
};
using Controller = Giveaway::AwardMembersListController;
auto listController = std::make_unique<Controller>(
navigation,
peer,
state->selectedToAward);
listController->setCheckError(CreateErrorCallback(
state->apiOptions.giveawayAddPeersMax(),
tr::lng_giveaway_maximum_users_error));
box->uiShow()->showBox(
Box<PeerListBox>(
std::move(listController),
std::move(initBox)),
Ui::LayerOption::KeepOther);
});
}
const auto creditsOption = [=](int index) {
const auto options = state->apiCreditsOptions.options();
return (index >= 0 && index < options.size())
? options[index]
: Data::CreditsGiveawayOption();
};
const auto creditsOptionWinners = [=](int index) {
const auto winners = creditsOption(index).winners;
return ranges::views::all(
winners
) | ranges::views::transform([](const auto &w) {
return w.users;
}) | ranges::to_vector;
};
const auto creditsTypeWrap = contentWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(contentWrap->entity()));
const auto fillCreditsTypeWrap = [=] {
if (state->apiCreditsOptions.options().empty()) {
return;
}
static constexpr auto kOutdated = 1735689600;
auto badge = [&] {
if (base::unixtime::now() > kOutdated) {
return QImage();
}
const auto badge = Ui::CreateChild<Ui::PaddingWrap<>>(
creditsTypeWrap,
object_ptr<Ui::FlatLabel>(
creditsTypeWrap,
tr::lng_premium_summary_new_badge(tr::now),
st::settingsPremiumNewBadge),
st::settingsPremiumNewBadgePadding);
badge->setAttribute(Qt::WA_TransparentForMouseEvents);
badge->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(badge);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(st::windowBgActive);
const auto r = st::settingsPremiumNewBadgePadding.left();
p.drawRoundedRect(badge->rect(), r, r);
}, badge->lifetime());
badge->show();
auto result = Ui::GrabWidget(badge).toImage();
badge->hide();
return result;
}();
const auto row = creditsTypeWrap->add(
object_ptr<Giveaway::GiveawayTypeRow>(
box,
GiveawayType::Credits,
st::colorIndexOrange,
tr::lng_credits_summary_title(),
tr::lng_giveaway_create_subtitle(),
std::move(badge)));
row->addRadio(typeGroup);
row->setClickedCallback([=] {
state->typeValue.force_assign(GiveawayType::Credits);
});
};
{
const auto &padding = st::giveawayGiftCodeTypeDividerPadding;
Ui::AddSkip(contentWrap->entity(), padding.top());
Ui::AddDivider(contentWrap->entity());
Ui::AddSkip(contentWrap->entity(), padding.bottom());
}
const auto randomWrap = contentWrap->entity()->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
contentWrap,
object_ptr<Ui::VerticalLayout>(box)));
state->typeValue.value(
) | rpl::start_with_next([=](GiveawayType type) {
randomWrap->toggle(!isSpecificUsers(), anim::type::instant);
}, randomWrap->lifetime());
randomWrap->toggleOn(hideSpecificUsersOn(), anim::type::instant);
const auto randomCreditsWrap = randomWrap->entity()->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
contentWrap,
object_ptr<Ui::VerticalLayout>(box)));
randomCreditsWrap->toggleOn(
state->typeValue.value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits),
anim::type::instant);
const auto fillCreditsOptions = [=] {
randomCreditsWrap->entity()->clear();
const auto &st = st::giveawayTypeListItem;
const auto &stButton = st::defaultSettingsButton;
const auto &stStatus = st::defaultTextStyle;
const auto buttonInnerSkip = st.height - stButton.height;
const auto options = state->apiCreditsOptions.options();
const auto content = randomCreditsWrap->entity();
const auto title = Ui::AddSubsectionTitle(
content,
tr::lng_giveaway_credits_options_title());
const auto rightLabel = Ui::CreateChild<Ui::FlatLabel>(
content,
st::giveawayGiftCodeQuantitySubtitle);
rightLabel->show();
rpl::combine(
tr::lng_giveaway_quantity(
lt_count,
creditsGroup->value() | rpl::map([=](int i) -> float64 {
return creditsOption(i).yearlyBoosts;
})),
title->positionValue(),
content->geometryValue()
) | rpl::start_with_next([=](QString s, const QPoint &p, QRect) {
rightLabel->setText(std::move(s));
rightLabel->moveToRight(st::boxRowPadding.right(), p.y());
}, rightLabel->lifetime());
const auto buttonHeight = st.height;
const auto minCredits = 0;
struct State final {
rpl::variable<bool> isExtended = false;
};
const auto creditsState = content->lifetime().make_state<State>();
for (auto i = 0; i < options.size(); i++) {
const auto &option = options[i];
if (option.credits < minCredits) {
continue;
}
struct State final {
std::optional<Ui::Text::String> text;
QString status;
bool hasStatus = false;
};
const auto buttonWrap = content->add(
object_ptr<Ui::SlideWrap<Ui::SettingsButton>>(
content,
object_ptr<Ui::SettingsButton>(
content,
rpl::never<QString>(),
stButton)));
const auto button = buttonWrap->entity();
button->setPaddingOverride({ 0, buttonInnerSkip, 0, 0 });
const auto buttonState = button->lifetime().make_state<State>();
buttonState->text.emplace(
st.nameStyle,
tr::lng_credits_summary_options_credits(
tr::now,
lt_count_decimal,
option.credits));
buttonState->status = tr::lng_giveaway_credits_option_status(
tr::now,
lt_count_decimal,
option.credits);
const auto price = Ui::CreateChild<Ui::FlatLabel>(
button,
Ui::FillAmountAndCurrency(option.amount, option.currency),
st::creditsTopupPrice);
const auto inner = Ui::CreateChild<Ui::RpWidget>(button);
const auto stars = Ui::GenerateStars(
st.nameStyle.font->height,
(i + 1));
const auto textLeft = st.photoPosition.x()
+ (st.nameStyle.font->spacew * 2)
+ (stars.width() / style::DevicePixelRatio());
state->sliderValue.value(
) | rpl::start_with_next([=](int users) {
const auto option = creditsOption(i);
buttonState->hasStatus = false;
for (const auto &winner : option.winners) {
if (winner.users == users) {
auto status = tr::lng_giveaway_credits_option_status(
tr::now,
lt_count_decimal,
winner.perUserStars);
buttonState->status = std::move(status);
buttonState->hasStatus = true;
inner->update();
return;
}
}
inner->update();
}, button->lifetime());
inner->paintRequest(
) | rpl::start_with_next([=](const QRect &rect) {
auto p = QPainter(inner);
const auto namey = buttonState->hasStatus
? st.namePosition.y()
: (buttonHeight - stStatus.font->height) / 2;
p.drawImage(st.photoPosition.x(), namey, stars);
p.setPen(st.nameFg);
buttonState->text->draw(p, {
.position = QPoint(textLeft, namey),
.availableWidth = inner->width() - textLeft,
.elisionLines = 1,
});
if (buttonState->hasStatus) {
p.setFont(stStatus.font);
p.setPen(st.statusFg);
p.setBrush(Qt::NoBrush);
p.drawText(
st.photoPosition.x(),
st.statusPosition.y() + stStatus.font->ascent,
buttonState->status);
}
}, inner->lifetime());
button->widthValue(
) | rpl::start_with_next([=](int width) {
price->moveToRight(
st::boxRowPadding.right(),
(buttonHeight - price->height()) / 2);
inner->moveToLeft(0, 0);
inner->resize(
width
- price->width()
- st::boxRowPadding.right()
- st::boxRowPadding.left() / 2,
buttonHeight);
}, button->lifetime());
{
const auto &st = st::defaultCheckbox;
const auto radio = Ui::CreateChild<Ui::Radioenum<int>>(
button,
creditsGroup,
i,
QString(),
st);
radio->moveToLeft(
st::boxRowPadding.left(),
(buttonHeight - radio->checkRect().height()) / 2);
radio->setAttribute(Qt::WA_TransparentForMouseEvents);
radio->show();
}
button->setClickedCallback([=] {
creditsGroup->setValue(i);
});
if (option.isDefault) {
creditsGroup->setValue(i);
}
buttonWrap->toggle(
(!option.isExtended) || option.isDefault,
anim::type::instant);
if (option.isExtended) {
buttonWrap->toggleOn(creditsState->isExtended.value());
}
Ui::ToggleChildrenVisibility(button, true);
}
{
Ui::AddSkip(content, st::settingsButton.padding.top());
const auto showMoreWrap = Info::Statistics::AddShowMoreButton(
content,
tr::lng_stories_show_more());
showMoreWrap->toggle(true, anim::type::instant);
showMoreWrap->entity()->setClickedCallback([=] {
showMoreWrap->toggle(false, anim::type::instant);
creditsState->isExtended = true;
});
}
Ui::AddSkip(content);
Ui::AddDividerText(content, tr::lng_giveaway_credits_options_about());
Ui::AddSkip(content);
};
const auto sliderContainerWrap = randomWrap->entity()->add(
object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
randomWrap,
object_ptr<Ui::VerticalLayout>(randomWrap)));
const auto sliderContainer = sliderContainerWrap->entity();
sliderContainerWrap->toggle(true, anim::type::instant);
const auto fillSliderContainer = [=] {
const auto availablePresets = state->apiOptions.availablePresets();
const auto creditsOptions = state->apiCreditsOptions.options();
if (prepaid) {
state->sliderValue = prepaid->quantity;
return;
}
if (availablePresets.empty()
&& (creditsOptions.empty()
|| creditsOptions.front().winners.empty())) {
return;
}
state->sliderValue = availablePresets.empty()
? creditsOptions.front().winners.front().users
: availablePresets.front();
auto creditsValueType = typeGroup->value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits);
const auto title = Ui::AddSubsectionTitle(
sliderContainer,
rpl::conditional(
rpl::duplicate(creditsValueType),
tr::lng_giveaway_credits_quantity_title(),
tr::lng_giveaway_quantity_title()));
const auto rightLabel = Ui::CreateChild<Ui::FlatLabel>(
sliderContainer,
st::giveawayGiftCodeQuantitySubtitle);
rightLabel->show();
rpl::duplicate(
creditsValueType
) | rpl::start_with_next([=](bool isCredits) {
rightLabel->setVisible(!isCredits);
}, rightLabel->lifetime());
const auto floatLabel = Ui::CreateChild<Ui::FlatLabel>(
sliderContainer,
st::giveawayGiftCodeQuantityFloat);
floatLabel->show();
rpl::combine(
tr::lng_giveaway_quantity(
lt_count,
state->sliderValue.value(
) | rpl::map([=](int v) -> float64 {
return state->apiOptions.giveawayBoostsPerPremium() * v;
})),
title->positionValue(),
sliderContainer->geometryValue()
) | rpl::start_with_next([=](QString s, const QPoint &p, QRect) {
rightLabel->setText(std::move(s));
rightLabel->moveToRight(st::boxRowPadding.right(), p.y());
}, rightLabel->lifetime());
const auto &padding = st::giveawayGiftCodeSliderPadding;
Ui::AddSkip(sliderContainer, padding.top());
const auto sliderParent = sliderContainer->add(
object_ptr<Ui::VerticalLayout>(sliderContainer),
st::boxRowPadding);
struct State final {
Ui::MediaSliderWheelless *slider = nullptr;
};
const auto sliderState = sliderParent->lifetime().make_state<State>();
Ui::AddSkip(sliderContainer, padding.bottom());
rpl::combine(
rpl::duplicate(creditsValueType),
creditsGroup->value()
) | rpl::start_with_next([=](bool isCredits, int value) {
while (sliderParent->count()) {
delete sliderParent->widgetAt(0);
}
sliderState->slider = sliderParent->add(
object_ptr<Ui::MediaSliderWheelless>(
sliderContainer,
st::settingsScale));
sliderState->slider->resize(
sliderState->slider->width(),
st::settingsScale.seekSize.height());
const auto &values = isCredits
? creditsOptionWinners(value)
: availablePresets;
const auto resultValue = [&] {
const auto sliderValue = state->sliderValue.current();
return ranges::contains(values, sliderValue)
? sliderValue
: values.front();
}();
state->sliderValue.force_assign(resultValue);
if (values.size() <= 1) {
sliderContainerWrap->toggle(false, anim::type::instant);
return;
} else {
sliderContainerWrap->toggle(true, anim::type::instant);
}
sliderState->slider->setPseudoDiscrete(
values.size(),
[=](int index) { return values[index]; },
resultValue,
[=](int boosts) { state->sliderValue = boosts; },
[](int) {});
}, sliderParent->lifetime());
rpl::combine(
rpl::duplicate(creditsValueType),
creditsGroup->value(),
state->sliderValue.value()
) | rpl::start_with_next([=](
bool isCredits,
int credits,
int boosts) {
floatLabel->setText(QString::number(boosts));
if (!sliderState->slider) {
return;
}
const auto &values = isCredits
? creditsOptionWinners(credits)
: availablePresets;
const auto count = values.size();
if (count <= 1) {
return;
}
const auto sliderWidth = sliderState->slider->width()
- st::settingsScale.seekSize.width();
for (auto i = 0; i < count; i++) {
if ((i + 1 == count || values[i + 1] > boosts)
&& values[i] <= boosts) {
const auto x = (sliderWidth * i) / (count - 1);
const auto mapped = sliderState->slider->mapTo(
sliderContainer,
sliderState->slider->pos());
floatLabel->moveToLeft(
mapped.x()
+ x
+ st::settingsScale.seekSize.width() / 2
- floatLabel->width() / 2,
mapped.y()
- floatLabel->height()
- st::giveawayGiftCodeSliderFloatSkip);
break;
}
}
}, floatLabel->lifetime());
Ui::AddSkip(sliderContainer);
Ui::AddDividerText(
sliderContainer,
rpl::conditional(
rpl::duplicate(creditsValueType),
tr::lng_giveaway_credits_quantity_about(),
tr::lng_giveaway_quantity_about()));
Ui::AddSkip(sliderContainer);
sliderContainer->resizeToWidth(box->width());
};
{
const auto channelsContainer = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap));
Ui::AddSubsectionTitle(
channelsContainer,
tr::lng_giveaway_channels_title(),
st::giveawayGiftCodeChannelsSubsectionPadding);
struct ListState final {
ListState(not_null<PeerData*> p) : controller(p) {
}
PeerListContentDelegateSimple delegate;
Giveaway::SelectedChannelsListController controller;
};
const auto listState = box->lifetime().make_state<ListState>(peer);
listState->delegate.setContent(channelsContainer->add(
object_ptr<PeerListContent>(
channelsContainer,
&listState->controller)));
listState->controller.setDelegate(&listState->delegate);
listState->controller.channelRemoved(
) | rpl::start_with_next([=](not_null<PeerData*> peer) {
auto &list = state->selectedToSubscribe;
list.erase(ranges::remove(list, peer), end(list));
}, box->lifetime());
listState->controller.setTopStatus((peer->isMegagroup()
? tr::lng_giveaway_channels_this_group
: tr::lng_giveaway_channels_this)(
lt_count,
state->sliderValue.value(
) | rpl::map([=](int v) -> float64 {
return (prepaid && prepaid->boosts)
? prepaid->boosts
: (state->apiOptions.giveawayBoostsPerPremium() * v);
})));
using IconType = Settings::IconType;
Settings::AddButtonWithIcon(
channelsContainer,
tr::lng_giveaway_channels_add(),
st::giveawayGiftCodeChannelsAddButton,
{ &st::settingsIconAdd, IconType::Round, &st::windowBgActive }
)->setClickedCallback([=] {
auto initBox = [=](not_null<PeerListBox*> peersBox) {
peersBox->setTitle(tr::lng_giveaway_channels_add());
peersBox->addButton(tr::lng_settings_save(), [=] {
const auto selected = peersBox->collectSelectedRows();
state->selectedToSubscribe = selected;
listState->controller.rebuild(selected);
peersBox->closeBox();
});
peersBox->addButton(tr::lng_cancel(), [=] {
peersBox->closeBox();
});
};
using Controller = Giveaway::MyChannelsListController;
auto controller = std::make_unique<Controller>(
peer,
box->uiShow(),
state->selectedToSubscribe);
controller->setCheckError(CreateErrorCallback(
state->apiOptions.giveawayAddPeersMax(),
tr::lng_giveaway_maximum_channels_error));
box->uiShow()->showBox(
Box<PeerListBox>(std::move(controller), std::move(initBox)),
Ui::LayerOption::KeepOther);
});
const auto &padding = st::giveawayGiftCodeChannelsDividerPadding;
Ui::AddSkip(channelsContainer, padding.top());
Ui::AddDividerText(
channelsContainer,
tr::lng_giveaway_channels_about());
Ui::AddSkip(channelsContainer, padding.bottom());
}
const auto membersGroup = std::make_shared<GiveawayGroup>();
{
const auto countriesContainer = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap));
Ui::AddSubsectionTitle(
countriesContainer,
tr::lng_giveaway_users_title());
membersGroup->setValue(GiveawayType::AllMembers);
auto subtitle = state->countriesValue.value(
) | rpl::map([=](const std::vector<QString> &list) {
return list.empty()
? tr::lng_giveaway_users_from_all_countries()
: (list.size() == 1)
? tr::lng_giveaway_users_from_one_country(
lt_country,
rpl::single(Countries::Instance().countryNameByISO2(
list.front())))
: tr::lng_giveaway_users_from_countries(
lt_count,
rpl::single(list.size()) | tr::to_count());
}) | rpl::flatten_latest();
const auto showBox = [=] {
auto done = [=](std::vector<QString> list) {
state->countriesValue = std::move(list);
};
auto error = CreateErrorCallback(
state->apiOptions.giveawayCountriesMax(),
tr::lng_giveaway_maximum_countries_error);
box->uiShow()->showBox(Box(
Ui::SelectCountriesBox,
state->countriesValue.current(),
std::move(done),
std::move(error)));
};
const auto createCallback = [=](GiveawayType type) {
return [=] {
const auto was = membersGroup->current();
membersGroup->setValue(type);
const auto now = membersGroup->current();
if (was == now) {
base::call_delayed(
st::defaultRippleAnimation.hideDuration,
box,
showBox);
}
};
};
{
const auto row = countriesContainer->add(
object_ptr<Giveaway::GiveawayTypeRow>(
box,
GiveawayType::AllMembers,
rpl::duplicate(subtitle),
group));
row->addRadio(membersGroup);
row->setClickedCallback(createCallback(GiveawayType::AllMembers));
}
const auto row = countriesContainer->add(
object_ptr<Giveaway::GiveawayTypeRow>(
box,
GiveawayType::OnlyNewMembers,
std::move(subtitle),
group));
row->addRadio(membersGroup);
row->setClickedCallback(createCallback(GiveawayType::OnlyNewMembers));
Ui::AddSkip(countriesContainer);
Ui::AddDividerText(
countriesContainer,
(group
? tr::lng_giveaway_users_about_group()
: tr::lng_giveaway_users_about()));
Ui::AddSkip(countriesContainer);
}
const auto addTerms = [=](not_null<Ui::VerticalLayout*> c) {
auto terms = object_ptr<Ui::FlatLabel>(
c,
tr::lng_premium_gift_terms(
lt_link,
tr::lng_premium_gift_terms_link(
) | rpl::map([](const QString &t) {
return Ui::Text::Link(t, 1);
}),
Ui::Text::WithEntities),
st::boxDividerLabel);
terms->setLink(1, std::make_shared<LambdaClickHandler>([=] {
box->closeBox();
Settings::ShowPremium(&peer->session(), QString());
}));
c->add(std::move(terms));
};
const auto durationGroup = std::make_shared<Ui::RadiobuttonGroup>(0);
durationGroup->setChangedCallback([=](int value) {
state->chosenMonths = state->apiOptions.monthsFromPreset(value);
});
const auto listOptionsRandom = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(box));
const auto listOptionsSpecific = contentWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(box));
const auto rebuildListOptions = [=](GiveawayType type, int usersCount) {
if (prepaid) {
return;
}
while (listOptionsRandom->count()) {
delete listOptionsRandom->widgetAt(0);
}
while (listOptionsSpecific->count()) {
delete listOptionsSpecific->widgetAt(0);
}
const auto listOptions = isSpecificUsers()
? listOptionsSpecific
: listOptionsRandom;
if (type != GiveawayType::Credits) {
Ui::AddSubsectionTitle(
listOptions,
tr::lng_giveaway_duration_title(
lt_count,
rpl::single(usersCount) | tr::to_count()),
st::giveawayGiftCodeChannelsSubsectionPadding);
Ui::Premium::AddGiftOptions(
listOptions,
durationGroup,
state->apiOptions.options(usersCount),
st::giveawayGiftCodeGiftOption,
true);
Ui::AddSkip(listOptions);
auto termsContainer = object_ptr<Ui::VerticalLayout>(listOptions);
addTerms(termsContainer.data());
listOptions->add(object_ptr<Ui::DividerLabel>(
listOptions,
std::move(termsContainer),
st::defaultBoxDividerLabelPadding));
Ui::AddSkip(listOptions);
}
box->verticalLayout()->resizeToWidth(box->width());
};
if (!prepaid) {
rpl::combine(
state->sliderValue.value(),
state->typeValue.value()
) | rpl::start_with_next([=](int users, GiveawayType type) {
typeGroup->setValue(type);
rebuildListOptions(
type,
isSpecificUsers() ? state->selectedToAward.size() : users);
}, box->lifetime());
} else {
typeGroup->setValue(GiveawayType::Random);
}
{
const auto additionalWrap = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap));
const auto additionalToggle = additionalWrap->add(
object_ptr<Ui::SettingsButton>(
additionalWrap,
tr::lng_giveaway_additional_prizes(),
st::defaultSettingsButton));
const auto additionalInner = additionalWrap->add(
object_ptr<Ui::SlideWrap<Ui::InputField>>(
additionalWrap,
object_ptr<Ui::InputField>(
additionalWrap,
st::giveawayGiftCodeAdditionalField,
Ui::InputField::Mode::SingleLine,
tr::lng_giveaway_additional_prizes_ph()),
st::giveawayGiftCodeAdditionalPaddingMin));
const auto additionalPadded = additionalInner->wrapped();
const auto additional = additionalInner->entity();
additionalInner->hide(anim::type::instant);
additional->setMaxLength(kAdditionalPrizeLengthMax);
const auto fillAdditionalPrizeValue = [=] {
state->additionalPrize = additional->getLastText().trimmed();
};
additionalToggle->toggleOn(rpl::single(false))->toggledChanges(
) | rpl::start_with_next([=](bool toggled) {
if (!toggled && Ui::InFocusChain(additional)) {
additionalWrap->setFocus();
state->additionalPrize = QString();
}
additionalInner->toggle(toggled, anim::type::normal);
if (toggled) {
additional->setFocusFast();
fillAdditionalPrizeValue();
}
}, additionalInner->lifetime());
additionalInner->finishAnimating();
additional->changes() | rpl::filter([=] {
return additionalInner->toggled();
}) | rpl::start_with_next(
fillAdditionalPrizeValue,
additional->lifetime());
Ui::AddSkip(additionalWrap);
auto monthsValue = prepaid
? (rpl::single(prepaid->months) | rpl::type_erased())
: state->chosenMonths.value();
const auto usersCountByType = [=](GiveawayType type) {
if (!isSpecificUsers()) {
return state->sliderValue.value() | rpl::type_erased();
}
return state->toAwardAmountChanged.events_starting_with_copy(
rpl::empty
) | rpl::map([=] {
return int(state->selectedToAward.size());
}) | rpl::type_erased();
};
auto usersCountValue = prepaid
? (rpl::single(prepaid->quantity) | rpl::type_erased())
: state->typeValue.value(
) | rpl::map(usersCountByType) | rpl::flatten_latest();
const auto additionalLabel = Ui::CreateChild<Ui::FlatLabel>(
additionalInner,
rpl::duplicate(usersCountValue) | rpl::map([](int count) {
return QString::number(count);
}),
st::giveawayGiftCodeAdditionalLabel);
additionalLabel->widthValue() | rpl::start_with_next([=](int width) {
const auto min = st::giveawayGiftCodeAdditionalPaddingMin;
const auto skip = st::giveawayGiftCodeAdditionalLabelSkip;
const auto added = std::max(width + skip - min.left(), 0);
const auto &field = st::giveawayGiftCodeAdditionalField;
const auto top = field.textMargins.top();
additionalLabel->moveToLeft(min.right(), min.top() + top);
additionalPadded->setPadding(min + QMargins(added, 0, 0, 0));
}, additionalLabel->lifetime());
auto additionalAbout = rpl::combine(
state->additionalPrize.value(),
std::move(monthsValue),
std::move(usersCountValue)
) | rpl::map([=](QString prize, int months, int users) {
const auto duration = ((months >= 12)
? tr::lng_premium_gift_duration_years
: tr::lng_premium_gift_duration_months)(
tr::now,
lt_count,
(months >= 12) ? (months / 12) : months);
if (prize.isEmpty()) {
return tr::lng_giveaway_prizes_just_premium(
tr::now,
lt_count,
users,
lt_duration,
TextWithEntities{ duration },
Ui::Text::RichLangValue);
}
return tr::lng_giveaway_prizes_additional(
tr::now,
lt_count,
users,
lt_prize,
TextWithEntities{ prize },
lt_duration,
TextWithEntities{ duration },
Ui::Text::RichLangValue);
});
auto creditsAdditionalAbout = rpl::combine(
state->additionalPrize.value(),
state->sliderValue.value(),
creditsGroup->value()
) | rpl::map([=](QString prize, int users, int creditsIndex) {
const auto credits = creditsOption(creditsIndex).credits;
return prize.isEmpty()
? tr::lng_giveaway_prizes_just_credits(
tr::now,
lt_count,
credits,
Ui::Text::RichLangValue)
: tr::lng_giveaway_prizes_additional_credits(
tr::now,
lt_count,
users,
lt_prize,
TextWithEntities{ prize },
lt_amount,
tr::lng_giveaway_prizes_additional_credits_amount(
tr::now,
lt_count,
credits,
Ui::Text::RichLangValue),
Ui::Text::RichLangValue);
});
auto creditsValueType = typeGroup->value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits);
Ui::AddDividerText(
additionalWrap,
rpl::conditional(
additionalToggle->toggledValue(),
rpl::conditional(
rpl::duplicate(creditsValueType),
std::move(creditsAdditionalAbout),
std::move(additionalAbout)),
rpl::conditional(
rpl::duplicate(creditsValueType),
tr::lng_giveaway_additional_credits_about(),
tr::lng_giveaway_additional_about()
) | rpl::map(Ui::Text::WithEntities)));
Ui::AddSkip(additionalWrap);
}
{
const auto dateContainer = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap));
Ui::AddSubsectionTitle(
dateContainer,
tr::lng_giveaway_date_title(),
st::giveawayGiftCodeChannelsSubsectionPadding);
state->dateValue = ThreeDaysAfterToday().toSecsSinceEpoch();
const auto button = Settings::AddButtonWithLabel(
dateContainer,
tr::lng_giveaway_date(),
state->dateValue.value() | rpl::map(
base::unixtime::parse
) | rpl::map(Ui::FormatDateTime),
st::defaultSettingsButton);
button->setClickedCallback([=] {
box->uiShow()->showBox(Box([=](not_null<Ui::GenericBox*> b) {
Ui::ChooseDateTimeBox(b, {
.title = tr::lng_giveaway_date_select(),
.submit = tr::lng_settings_save(),
.done = [=](TimeId time) {
state->dateValue = time;
b->closeBox();
},
.min = QDateTime::currentSecsSinceEpoch,
.time = state->dateValue.current(),
.max = [=] {
return QDateTime::currentSecsSinceEpoch()
+ state->apiOptions.giveawayPeriodMax();
},
});
}));
});
Ui::AddSkip(dateContainer);
if (prepaid) {
auto terms = object_ptr<Ui::VerticalLayout>(dateContainer);
terms->add(object_ptr<Ui::FlatLabel>(
terms,
(group
? tr::lng_giveaway_date_about_group
: tr::lng_giveaway_date_about)(
lt_count,
state->sliderValue.value() | tr::to_count()),
st::boxDividerLabel));
Ui::AddSkip(terms.data());
Ui::AddSkip(terms.data());
addTerms(terms.data());
dateContainer->add(object_ptr<Ui::DividerLabel>(
dateContainer,
std::move(terms),
st::defaultBoxDividerLabelPadding));
Ui::AddSkip(dateContainer);
} else {
Ui::AddDividerText(
dateContainer,
(group
? tr::lng_giveaway_date_about_group
: tr::lng_giveaway_date_about)(
lt_count,
state->sliderValue.value() | tr::to_count()));
Ui::AddSkip(dateContainer);
}
}
{
const auto winnersWrap = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap));
const auto winnersToggle = winnersWrap->add(
object_ptr<Ui::SettingsButton>(
winnersWrap,
tr::lng_giveaway_show_winners(),
st::defaultSettingsButton));
state->showWinners = winnersToggle->toggleOn(
rpl::single(false)
)->toggledValue();
Ui::AddSkip(winnersWrap);
Ui::AddDividerText(
winnersWrap,
tr::lng_giveaway_show_winners_about());
}
{
using namespace Info::Statistics;
const auto &stButton = st::startGiveawayBox;
box->setStyle(stButton);
auto button = object_ptr<Ui::RoundButton>(
box,
rpl::never<QString>(),
st::giveawayGiftCodeStartButton);
AddLabelWithBadgeToButton(
button,
rpl::conditional(
hideSpecificUsersOn(),
tr::lng_giveaway_start(),
tr::lng_giveaway_award()),
(prepaid && prepaid->boosts)
? rpl::single(prepaid->boosts) | rpl::type_erased()
: rpl::conditional(
state->typeValue.value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Credits),
creditsGroup->value() | rpl::map([=](int v) {
return creditsOption(v).yearlyBoosts;
}),
rpl::combine(
state->sliderValue.value(),
hideSpecificUsersOn()
) | rpl::map([=](int value, bool random) -> int {
return state->apiOptions.giveawayBoostsPerPremium()
* (random
? value
: int(state->selectedToAward.size()));
})),
state->confirmButtonBusy.value() | rpl::map(!rpl::mappers::_1));
{
const auto loadingAnimation = InfiniteRadialAnimationWidget(
button,
st::giveawayGiftCodeStartButton.height / 2);
AddChildToWidgetCenter(button.data(), loadingAnimation);
loadingAnimation->showOn(state->confirmButtonBusy.value());
}
button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform);
state->typeValue.value(
) | rpl::start_with_next([=, raw = button.data()] {
raw->resizeToWidth(box->width()
- stButton.buttonPadding.left()
- stButton.buttonPadding.right());
}, button->lifetime());
button->setClickedCallback([=] {
if (state->confirmButtonBusy.current()) {
return;
}
const auto type = typeGroup->current();
const auto isSpecific = isSpecificUsers();
const auto isRandom = (type == GiveawayType::Random);
const auto isCredits = (type == GiveawayType::Credits);
if (!isSpecific && !isRandom && !isCredits) {
return;
}
auto invoice = [&] {
if (isPrepaidCredits) {
return Payments::InvoicePremiumGiftCode{
.creditsAmount = prepaid->credits,
.randomId = prepaid->id,
.users = prepaid->quantity,
};
} else if (isCredits) {
const auto option = creditsOption(
creditsGroup->current());
return Payments::InvoicePremiumGiftCode{
.currency = option.currency,
.storeProduct = option.storeProduct,
.creditsAmount = option.credits,
.randomId = UniqueIdFromCreditsOption(option, peer),
.amount = option.amount,
.users = state->sliderValue.current(),
};
}
return state->apiOptions.invoice(
isSpecific
? state->selectedToAward.size()
: state->sliderValue.current(),
prepaid
? prepaid->months
: state->apiOptions.monthsFromPreset(
durationGroup->current()));
}();
if (isSpecific) {
if (state->selectedToAward.empty()) {
return;
}
invoice.purpose = Payments::InvoicePremiumGiftCodeUsers{
ranges::views::all(
state->selectedToAward
) | ranges::views::transform([](
const not_null<PeerData*> p) {
return not_null{ p->asUser() };
}) | ranges::to_vector,
peer->asChannel(),
};
} else if (isRandom || isCredits || isPrepaidCredits) {
invoice.purpose = Payments::InvoicePremiumGiftCodeGiveaway{
.boostPeer = peer->asChannel(),
.additionalChannels = ranges::views::all(
state->selectedToSubscribe
) | ranges::views::transform([](
const not_null<PeerData*> p) {
return not_null{ p->asChannel() };
}) | ranges::to_vector,
.countries = state->countriesValue.current(),
.additionalPrize = state->additionalPrize.current(),
.untilDate = state->dateValue.current(),
.onlyNewSubscribers = (membersGroup->current()
== GiveawayType::OnlyNewMembers),
.showWinners = state->showWinners.current(),
};
}
state->confirmButtonBusy = true;
const auto show = box->uiShow();
const auto weak = Ui::MakeWeak(box.get());
const auto done = [=](Payments::CheckoutResult result) {
const auto isPaid = result == Payments::CheckoutResult::Paid;
if (result == Payments::CheckoutResult::Pending || isPaid) {
if (const auto strong = weak.data()) {
strong->window()->setFocus();
strong->closeBox();
}
}
if (isPaid) {
reloadOnDone();
const auto filter = [=](const auto &...) {
if (const auto window = weakWindow.get()) {
window->showSection(Info::Boosts::Make(peer));
}
return false;
};
const auto group = peer->isMegagroup();
const auto title = isSpecific
? tr::lng_giveaway_awarded_title
: tr::lng_giveaway_created_title;
const auto body = isSpecific
? (group
? tr::lng_giveaway_awarded_body_group
: tr::lng_giveaway_awarded_body)
: (group
? tr::lng_giveaway_created_body_group
: tr::lng_giveaway_created_body);
show->showToast({
.text = Ui::Text::Bold(
title(tr::now)).append('\n').append(
body(
tr::now,
lt_link,
Ui::Text::Link(
tr::lng_giveaway_created_link(
tr::now)),
Ui::Text::WithEntities)),
.filter = filter,
.adaptive = true,
.duration = kDoneTooltipDuration,
});
} else if (weak) {
state->confirmButtonBusy = false;
}
};
const auto startPrepaid = [=](Fn<void()> close) {
if (!weak) {
close();
return;
}
state->apiOptions.applyPrepaid(
invoice,
prepaid->id
) | rpl::start_with_error_done([=](const QString &error) {
if (const auto window = weakWindow.get()) {
window->uiShow()->showToast(error);
close();
done(Payments::CheckoutResult::Cancelled);
}
}, [=] {
close();
done(Payments::CheckoutResult::Paid);
}, box->lifetime());
};
if (prepaid) {
const auto cancel = [=](Fn<void()> close) {
if (weak) {
state->confirmButtonBusy = false;
}
close();
};
show->show(Ui::MakeConfirmBox({
.text = tr::lng_giveaway_start_sure(tr::now),
.confirmed = startPrepaid,
.cancelled = cancel,
}));
} else {
Payments::CheckoutProcess::Start(std::move(invoice), done);
}
});
box->addButton(std::move(button));
}
state->typeValue.force_assign(GiveawayType::Random);
std::move(
showFinished
) | rpl::take(1) | rpl::start_with_next([=] {
if (!loading->toggled()) {
return;
}
const auto done = [=] {
state->lifetimeApi.destroy();
loading->toggle(false, anim::type::instant);
state->confirmButtonBusy = false;
fillSliderContainer();
if (!prepaid) {
state->chosenMonths = state->apiOptions.monthsFromPreset(0);
}
fillCreditsTypeWrap();
fillCreditsOptions();
rebuildListOptions(state->typeValue.current(), 1);
contentWrap->toggle(true, anim::type::instant);
contentWrap->resizeToWidth(box->width());
};
const auto receivedOptions = [=] {
state->lifetimeApi.destroy();
state->lifetimeApi = state->apiCreditsOptions.request(
) | rpl::start_with_error_done([=](const QString &error) {
box->uiShow()->showToast(error);
box->closeBox();
}, done);
};
if (prepaid) {
return done();
}
state->lifetimeApi = state->apiOptions.request(
) | rpl::start_with_error_done([=](const QString &error) {
box->uiShow()->showToast(error);
box->closeBox();
}, receivedOptions);
}, box->lifetime());
}