| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584 |
- /*
- 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;
- }
- const auto row = creditsTypeWrap->add(
- object_ptr<Giveaway::GiveawayTypeRow>(
- box,
- GiveawayType::Credits,
- st::colorIndexOrange,
- tr::lng_credits_summary_title(),
- tr::lng_giveaway_create_subtitle(),
- QImage()));
- 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{
- .giveawayCredits = 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,
- .giveawayCredits = 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());
- }
|