tdesktop/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp

1130 lines
34 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/boosts/create_giveaway_box.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/boosts/giveaway/boost_badge.h"
#include "info/boosts/giveaway/giveaway_list_controllers.h"
#include "info/boosts/giveaway/giveaway_type_row.h"
#include "info/boosts/giveaway/select_countries_box.h"
#include "info/boosts/info_boosts_widget.h"
#include "info/info_controller.h"
#include "info/info_memento.h"
#include "lang/lang_keys.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/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/vertical_list.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.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 "styles/style_giveaway.h"
#include "styles/style_info.h"
#include "styles/style_layers.h"
#include "styles/style_premium.h"
#include "styles/style_settings.h"
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]] 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) {
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([=](const QRect &r) {
auto p = Painter(closeTopBar);
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);
PainterHighQualityEnabler hq(p);
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,
nullptr,
tr::lng_giveaway_new_title(),
tr::lng_giveaway_new_about(Ui::Text::RichLangValue),
true,
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<Info::Controller*> controller,
not_null<PeerData*> peer,
Fn<void()> reloadOnDone,
std::optional<Data::BoostPrepaidGiveaway> prepaid) {
box->setWidth(st::boxWideWidth);
const auto weakWindow = base::make_weak(controller->parentController());
using GiveawayType = Giveaway::GiveawayTypeRow::Type;
using GiveawayGroup = Ui::RadioenumGroup<GiveawayType>;
struct State final {
State(not_null<PeerData*> p) : apiOptions(p) {
}
Api::PremiumGiftCodeOptions apiOptions;
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 state = box->lifetime().make_state<State>(peer);
const auto typeGroup = std::make_shared<GiveawayGroup>();
auto showFinished = Ui::BoxShowFinishes(box);
AddPremiumTopBarWithDefaultTitleBar(
box,
rpl::duplicate(showFinished),
rpl::conditional(
state->typeValue.value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
tr::lng_giveaway_start(),
tr::lng_giveaway_award()));
{
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,
GiveawayType::Prepaid,
prepaid->id,
tr::lng_boosts_prepaid_giveaway_single(),
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,
tr::lng_giveaway_create_subtitle()));
row->addRadio(typeGroup);
row->setClickedCallback([=] {
state->typeValue.force_assign(GiveawayType::Random);
});
}
if (!prepaid) {
const auto row = contentWrap->entity()->add(
object_ptr<Giveaway::GiveawayTypeRow>(
box,
GiveawayType::SpecificUsers,
state->toAwardAmountChanged.events_starting_with(
rpl::empty_value()
) | rpl::map([=] {
const auto &selected = state->selectedToAward;
return selected.empty()
? tr::lng_giveaway_award_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()));
row->addRadio(typeGroup);
row->setClickedCallback([=] {
auto initBox = [=](not_null<PeerListBox*> peersBox) {
peersBox->setTitle(tr::lng_giveaway_award_option());
peersBox->addButton(tr::lng_settings_save(), [=] {
state->selectedToAward = peersBox->collectSelectedRows();
state->toAwardAmountChanged.fire({});
peersBox->closeBox();
});
peersBox->addButton(tr::lng_cancel(), [=] {
peersBox->closeBox();
});
peersBox->boxClosing(
) | rpl::start_with_next([=] {
state->typeValue.force_assign(
state->selectedToAward.empty()
? GiveawayType::Random
: GiveawayType::SpecificUsers);
}, peersBox->lifetime());
};
using Controller = Giveaway::AwardMembersListController;
auto listController = std::make_unique<Controller>(
controller,
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 &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(type == GiveawayType::Random, anim::type::instant);
}, randomWrap->lifetime());
randomWrap->toggleOn(
state->typeValue.value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
anim::type::instant);
const auto sliderContainer = randomWrap->entity()->add(
object_ptr<Ui::VerticalLayout>(randomWrap));
const auto fillSliderContainer = [=] {
const auto availablePresets = state->apiOptions.availablePresets();
if (prepaid) {
state->sliderValue = prepaid->quantity;
return;
}
if (availablePresets.empty()) {
return;
}
state->sliderValue = availablePresets.front();
const auto title = Ui::AddSubsectionTitle(
sliderContainer,
tr::lng_giveaway_quantity_title());
const auto rightLabel = Ui::CreateChild<Ui::FlatLabel>(
sliderContainer,
st::giveawayGiftCodeQuantitySubtitle);
rightLabel->show();
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());
class Slider : public Ui::MediaSlider {
public:
using Ui::MediaSlider::MediaSlider;
protected:
void wheelEvent(QWheelEvent *e) override {
e->ignore();
}
};
const auto slider = sliderContainer->add(
object_ptr<Slider>(sliderContainer, st::settingsScale),
st::boxRowPadding);
Ui::AddSkip(sliderContainer, padding.bottom());
slider->resize(slider->width(), st::settingsScale.seekSize.height());
slider->setPseudoDiscrete(
availablePresets.size(),
[=](int index) { return availablePresets[index]; },
availablePresets.front(),
[=](int boosts) { state->sliderValue = boosts; },
[](int) {});
state->sliderValue.value(
) | rpl::start_with_next([=](int boosts) {
floatLabel->setText(QString::number(boosts));
const auto count = availablePresets.size();
const auto sliderWidth = slider->width()
- st::settingsScale.seekSize.width();
for (auto i = 0; i < count; i++) {
if ((i + 1 == count || availablePresets[i + 1] > boosts)
&& availablePresets[i] <= boosts) {
const auto x = (sliderWidth * i) / (count - 1);
floatLabel->moveToLeft(
slider->x()
+ x
+ st::settingsScale.seekSize.width() / 2
- floatLabel->width() / 2,
slider->y()
- floatLabel->height()
- st::giveawayGiftCodeSliderFloatSkip);
break;
}
}
}, floatLabel->lifetime());
Ui::AddSkip(sliderContainer);
Ui::AddDividerText(
sliderContainer,
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(tr::lng_giveaway_channels_this(
lt_count,
state->sliderValue.value(
) | rpl::map([=](int v) -> float64 {
return 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->value();
membersGroup->setValue(type);
const auto now = membersGroup->value();
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)));
row->addRadio(membersGroup);
row->setClickedCallback(createCallback(GiveawayType::AllMembers));
}
const auto row = countriesContainer->add(
object_ptr<Giveaway::GiveawayTypeRow>(
box,
GiveawayType::OnlyNewMembers,
std::move(subtitle)));
row->addRadio(membersGroup);
row->setClickedCallback(createCallback(GiveawayType::OnlyNewMembers));
Ui::AddSkip(countriesContainer);
Ui::AddDividerText(
countriesContainer,
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 = (type == GiveawayType::SpecificUsers)
? listOptionsSpecific
: listOptionsRandom;
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, (type == GiveawayType::SpecificUsers)
? 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 (type != GiveawayType::SpecificUsers) {
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);
});
Ui::AddDividerText(additionalWrap, std::move(additionalAbout));
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,
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,
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(
state->typeValue.value(
) | rpl::map(rpl::mappers::_1 == GiveawayType::Random),
tr::lng_giveaway_start(),
tr::lng_giveaway_award()),
state->sliderValue.value(
) | rpl::map([=](int v) -> int {
return state->apiOptions.giveawayBoostsPerPremium() * v;
}),
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->value();
const auto isSpecific = (type == GiveawayType::SpecificUsers);
const auto isRandom = (type == GiveawayType::Random);
if (!isSpecific && !isRandom) {
return;
}
auto invoice = state->apiOptions.invoice(
isSpecific
? state->selectedToAward.size()
: state->sliderValue.current(),
prepaid
? prepaid->months
: state->apiOptions.monthsFromPreset(
durationGroup->value()));
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) {
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->value()
== 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 title = isSpecific
? tr::lng_giveaway_awarded_title
: tr::lng_giveaway_created_title;
const auto body = isSpecific
? tr::lng_giveaway_awarded_body
: 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)),
.duration = kDoneTooltipDuration,
.adaptive = true,
.filter = filter,
});
} 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);
}
rebuildListOptions(state->typeValue.current(), 1);
contentWrap->toggle(true, anim::type::instant);
contentWrap->resizeToWidth(box->width());
};
if (prepaid) {
return done();
}
state->lifetimeApi = state->apiOptions.request(
) | rpl::start_with_error_done([=](const QString &error) {
box->uiShow()->showToast(error);
box->closeBox();
}, done);
}, box->lifetime());
}