diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index c7160a2a3..f17be156b 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2170,6 +2170,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_giveaway_maximum_users_error#other" = "You can select maximum {count} users."; "lng_giveaway_channels_confirm_title" = "Channel is Private"; "lng_giveaway_channels_confirm_about" = "Are you sure you want to add a private channel? Users won't be able to join it without an invite link."; +"lng_giveaway_additional_prizes" = "Additional prizes"; +"lng_giveaway_additional_about" = "Turn this on if you want to give the winners your own prizes in addition to Premium subscriptions."; +"lng_giveaway_additional_prizes_ph" = "Enter your prize"; +"lng_giveaway_prizes_just_premium#one" = "All prizes: **{count}** Telegram Premium subscription {duration}."; +"lng_giveaway_prizes_just_premium#other" = "All prizes: **{count}** Telegram Premium subscriptions {duration}."; +"lng_giveaway_prizes_additional#one" = "All prizes: **{count}** {prize} with Telegram Premium subscription {duration}."; +"lng_giveaway_prizes_additional#other" = "All prizes: **{count}** {prize} with Telegram Premium subscriptions {duration}."; +"lng_giveaway_show_winners" = "Show winners"; +"lng_giveaway_show_winners_about" = "Choose whether to make the list of winners public when the giveaway ends."; "lng_giveaway_created_title" = "Giveaway created"; "lng_giveaway_created_body" = "Check your channels' {link} to see how this giveaway boosted your channel."; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 58950f6f4..c23cca814 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -420,6 +420,8 @@ const std::vector &PremiumGiftCodeOptions::availablePresets() const { } [[nodiscard]] int PremiumGiftCodeOptions::monthsFromPreset(int monthsIndex) { + Expects(monthsIndex >= 0 && monthsIndex < _availablePresets.size()); + return _optionsForOnePerson.months[monthsIndex]; } diff --git a/Telegram/SourceFiles/apiwrap.cpp b/Telegram/SourceFiles/apiwrap.cpp index 3b1287598..00a5f30a1 100644 --- a/Telegram/SourceFiles/apiwrap.cpp +++ b/Telegram/SourceFiles/apiwrap.cpp @@ -221,11 +221,11 @@ void ApiWrap::setupSupportMode() { void ApiWrap::requestChangelog( const QString &sinceVersion, Fn callback) { - request(MTPhelp_GetAppChangelog( - MTP_string(sinceVersion) - )).done( - callback - ).send(); + //request(MTPhelp_GetAppChangelog( + // MTP_string(sinceVersion) + //)).done( + // callback + //).send(); } void ApiWrap::refreshTopPromotion() { diff --git a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp index b34175a13..927d08de0 100644 --- a/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp +++ b/Telegram/SourceFiles/info/boosts/create_giveaway_box.cpp @@ -37,6 +37,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #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" @@ -48,6 +49,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { constexpr auto kDoneTooltipDuration = 5 * crl::time(1000); +constexpr auto kAdditionalPrizeLengthMax = 128; [[nodiscard]] QDateTime ThreeDaysAfterToday() { auto dateNow = QDateTime::currentDateTime(); @@ -257,6 +259,10 @@ void CreateGiveawayBox( rpl::variable dateValue; rpl::variable> countriesValue; + rpl::variable additionalPrize; + rpl::variable chosenMonths; + rpl::variable showWinners; + rpl::variable confirmButtonBusy = true; }; const auto state = box->lifetime().make_state(peer); @@ -669,6 +675,182 @@ void CreateGiveawayBox( c->add(std::move(terms)); }; + const auto durationGroup = std::make_shared(0); + durationGroup->setChangedCallback([=](int value) { + state->chosenMonths = state->apiOptions.monthsFromPreset(value); + }); + const auto listOptionsRandom = randomWrap->entity()->add( + object_ptr(box)); + const auto listOptionsSpecific = contentWrap->entity()->add( + object_ptr(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(listOptions); + addTerms(termsContainer.data()); + listOptions->add(object_ptr( + 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(randomWrap)); + const auto additionalToggle = additionalWrap->add( + object_ptr( + additionalWrap, + tr::lng_giveaway_additional_prizes(), + st::defaultSettingsButton)); + const auto additionalInner = additionalWrap->add( + object_ptr>( + additionalWrap, + object_ptr( + 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( + 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(randomWrap)); @@ -699,7 +881,7 @@ void CreateGiveawayBox( .time = state->dateValue.current(), .max = [=] { return QDateTime::currentSecsSinceEpoch() - + state->apiOptions.giveawayPeriodMax();; + + state->apiOptions.giveawayPeriodMax(); }, }); })); @@ -721,6 +903,7 @@ void CreateGiveawayBox( dateContainer, std::move(terms), st::defaultBoxDividerLabelPadding)); + Ui::AddSkip(dateContainer); } else { Ui::AddDividerText( dateContainer, @@ -731,53 +914,24 @@ void CreateGiveawayBox( } } - const auto durationGroup = std::make_shared(0); - const auto listOptions = contentWrap->entity()->add( - object_ptr(box)); - const auto rebuildListOptions = [=](int amountUsers) { - if (prepaid) { - return; - } - while (listOptions->count()) { - delete listOptions->widgetAt(0); - } - Ui::AddSubsectionTitle( - listOptions, - tr::lng_giveaway_duration_title( - lt_count, - rpl::single(amountUsers) | tr::to_count()), - st::giveawayGiftCodeChannelsSubsectionPadding); - Ui::Premium::AddGiftOptions( - listOptions, - durationGroup, - state->apiOptions.options(amountUsers), - st::giveawayGiftCodeGiftOption, - true); + { + const auto winnersWrap = randomWrap->entity()->add( + object_ptr(randomWrap)); + const auto winnersToggle = winnersWrap->add( + object_ptr( + winnersWrap, + tr::lng_giveaway_show_winners(), + st::defaultSettingsButton)); + state->showWinners = winnersToggle->toggleOn( + rpl::single(false) + )->toggledValue(); + Ui::AddSkip(winnersWrap); - Ui::AddSkip(listOptions); - - auto termsContainer = object_ptr(listOptions); - addTerms(termsContainer.data()); - listOptions->add(object_ptr( - listOptions, - std::move(termsContainer), - st::defaultBoxDividerLabelPadding)); - - 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 == GiveawayType::SpecificUsers) - ? state->selectedToAward.size() - : users); - }, box->lifetime()); - } else { - typeGroup->setValue(GiveawayType::Random); + Ui::AddDividerText( + winnersWrap, + tr::lng_giveaway_show_winners_about()); } + { using namespace Info::Statistics; const auto &stButton = st::startGiveawayBox; @@ -862,9 +1016,11 @@ void CreateGiveawayBox( 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; @@ -960,7 +1116,10 @@ void CreateGiveawayBox( loading->toggle(false, anim::type::instant); state->confirmButtonBusy = false; fillSliderContainer(); - rebuildListOptions(1); + if (!prepaid) { + state->chosenMonths = state->apiOptions.monthsFromPreset(0); + } + rebuildListOptions(state->typeValue.current(), 1); contentWrap->toggle(true, anim::type::instant); contentWrap->resizeToWidth(box->width()); }; diff --git a/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style index 4f04fdabb..2368a616f 100644 --- a/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style +++ b/Telegram/SourceFiles/info/boosts/giveaway/giveaway.style @@ -121,6 +121,13 @@ giveawayGiftCodeTypeDividerPadding: margins(0px, 7px, 0px, 5px); giveawayGiftCodeSliderPadding: margins(0px, 24px, 0px, 10px); giveawayGiftCodeSliderFloatSkip: 6px; giveawayGiftCodeChannelsSubsectionPadding: margins(0px, -1px, 0px, -4px); +giveawayGiftCodeAdditionalPaddingMin: margins(50px, 4px, 22px, 0px); +giveawayGiftCodeAdditionalField: InputField(defaultMultiSelectSearchField) { +} +giveawayGiftCodeAdditionalLabel: FlatLabel(defaultFlatLabel) { + style: semiboldTextStyle; +} +giveawayGiftCodeAdditionalLabelSkip: 12px; giveawayGiftCodeChannelsPeerList: PeerList(boostsListBox) { padding: margins(0px, 7px, 0px, 0px); diff --git a/Telegram/SourceFiles/mtproto/scheme/api.tl b/Telegram/SourceFiles/mtproto/scheme/api.tl index 27b9a2c3e..ae30cdcbd 100644 --- a/Telegram/SourceFiles/mtproto/scheme/api.tl +++ b/Telegram/SourceFiles/mtproto/scheme/api.tl @@ -131,7 +131,7 @@ messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int story:flags.0?StoryItem = MessageMedia; -messageMediaGiveaway#58260664 flags:# only_new_subscribers:flags.0?true channels:Vector countries_iso2:flags.1?Vector quantity:int months:int until_date:int = MessageMedia; +messageMediaGiveaway#daad85b0 flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector countries_iso2:flags.1?Vector prize_description:flags.3?string quantity:int months:int until_date:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; @@ -1426,7 +1426,7 @@ help.premiumPromo#5334759c status_text:string status_entities:Vector boost_peer:flags.0?InputPeer currency:string amount:long = InputStorePaymentPurpose; -inputStorePaymentPremiumGiveaway#7c9375e6 flags:# only_new_subscribers:flags.0?true boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; +inputStorePaymentPremiumGiveaway#160544ca flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.3?true boost_peer:InputPeer additional_peers:flags.1?Vector countries_iso2:flags.2?Vector prize_description:flags.4?string random_id:long until_date:int currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1583,7 +1583,7 @@ premiumGiftCodeOption#257e962b flags:# users:int months:int store_product:flags. payments.checkedGiftCode#b722f158 flags:# via_giveaway:flags.2?true from_id:Peer giveaway_msg_id:flags.3?int to_id:flags.0?long date:int months:int used_date:flags.1?int chats:Vector users:Vector = payments.CheckedGiftCode; payments.giveawayInfo#4367daa0 flags:# participating:flags.0?true preparing_results:flags.3?true start_date:int joined_too_early_date:flags.1?int admin_disallowed_chat_id:flags.2?long disallowed_country:flags.4?string = payments.GiveawayInfo; -payments.giveawayInfoResults#cd5570 flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int = payments.GiveawayInfo; +payments.giveawayInfoResults#8ac7e167 flags:# winner:flags.0?true refunded:flags.1?true start_date:int gift_code_slug:flags.0?string finish_date:int winners_count:int activated_count:int winners:flags.2?Vector = payments.GiveawayInfo; prepaidGiveaway#b2539d54 id:long months:int quantity:int date:int = PrepaidGiveaway; @@ -1619,6 +1619,10 @@ help.peerColorOption#135bd42f flags:# hidden:flags.0?true color_id:int colors:fl help.peerColorsNotModified#2ba1f5ce = help.PeerColors; help.peerColors#f8ed08 hash:int colors:Vector = help.PeerColors; +storyPeerReaction#7decc433 peer_id:Peer date:int reaction:Reaction = StoryPeerReaction; + +stories.storyReactionsList#d86c162a flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = stories.StoryReactionsList; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1986,7 +1990,6 @@ help.getNearestDc#1fb33026 = NearestDc; help.getAppUpdate#522d5a7d source:string = help.AppUpdate; help.getInviteText#4d392343 = help.InviteText; help.getSupport#9cdf08cd = help.Support; -help.getAppChangelog#9010ef6f prev_app_version:string = Updates; help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool; help.getCdnConfig#52029342 = CdnConfig; help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; @@ -2195,6 +2198,7 @@ stories.getAllReadPeerStories#9b5ae7f9 = Updates; stories.getPeerMaxIDs#535983c3 id:Vector = Vector; stories.getChatsToSend#a56a8b60 = messages.Chats; stories.togglePeerStoriesHidden#bd0415c4 peer:InputPeer hidden:Bool = Bool; +stories.getStoryReactionsList#b9b2881f flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = stories.StoryReactionsList; premium.getBoostsList#60f67660 flags:# gifts:flags.0?true peer:InputPeer offset:string limit:int = premium.BoostsList; premium.getMyBoosts#be77b4a = premium.MyBoosts; @@ -2202,4 +2206,4 @@ premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = p premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList; -// LAYER 167 +// LAYER 168 diff --git a/Telegram/SourceFiles/payments/payments_form.cpp b/Telegram/SourceFiles/payments/payments_form.cpp index 0ad303669..5eea98353 100644 --- a/Telegram/SourceFiles/payments/payments_form.cpp +++ b/Telegram/SourceFiles/payments/payments_form.cpp @@ -145,7 +145,13 @@ MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL( : Flag::f_additional_peers) | (giveaway.countries.empty() ? Flag() - : Flag::f_countries_iso2)), + : Flag::f_countries_iso2) + | (giveaway.showWinners + ? Flag::f_winners_are_visible + : Flag()) + | (giveaway.additionalPrize.isEmpty() + ? Flag() + : Flag::f_prize_description)), giveaway.boostPeer->input, MTP_vector_from_range(ranges::views::all( giveaway.additionalChannels @@ -157,6 +163,7 @@ MTPinputStorePaymentPurpose InvoicePremiumGiftCodeGiveawayToTL( ) | ranges::views::transform([](QString value) { return MTP_string(value); })), + MTP_string(giveaway.additionalPrize), MTP_long(invoice.randomId), MTP_int(giveaway.untilDate), MTP_string(invoice.currency), diff --git a/Telegram/SourceFiles/payments/payments_form.h b/Telegram/SourceFiles/payments/payments_form.h index acf375043..bd95e68ea 100644 --- a/Telegram/SourceFiles/payments/payments_form.h +++ b/Telegram/SourceFiles/payments/payments_form.h @@ -190,8 +190,10 @@ struct InvoicePremiumGiftCodeGiveaway { not_null boostPeer; std::vector> additionalChannels; std::vector countries; + QString additionalPrize; TimeId untilDate = 0; bool onlyNewSubscribers = false; + bool showWinners = false; }; struct InvoicePremiumGiftCodeUsers { diff --git a/Telegram/SourceFiles/ui/vertical_list.cpp b/Telegram/SourceFiles/ui/vertical_list.cpp index 0fcf7012f..34c6c0916 100644 --- a/Telegram/SourceFiles/ui/vertical_list.cpp +++ b/Telegram/SourceFiles/ui/vertical_list.cpp @@ -7,6 +7,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #include "ui/vertical_list.h" +#include "ui/text/text_utilities.h" #include "ui/widgets/box_content_divider.h" #include "ui/widgets/labels.h" #include "ui/wrap/padding_wrap.h" @@ -30,6 +31,12 @@ void AddDivider(not_null container) { void AddDividerText( not_null container, rpl::producer text) { + AddDividerText(container, std::move(text) | Ui::Text::ToWithEntities()); +} + +void AddDividerText( + not_null container, + rpl::producer text) { container->add(object_ptr( container, object_ptr( diff --git a/Telegram/SourceFiles/ui/vertical_list.h b/Telegram/SourceFiles/ui/vertical_list.h index 66fc03bff..970e73f4c 100644 --- a/Telegram/SourceFiles/ui/vertical_list.h +++ b/Telegram/SourceFiles/ui/vertical_list.h @@ -22,6 +22,9 @@ void AddDivider(not_null container); void AddDividerText( not_null container, rpl::producer text); +void AddDividerText( + not_null container, + rpl::producer text); not_null AddSubsectionTitle( not_null container, rpl::producer text,