diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 502ec5718..a6e68c037 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -2076,15 +2076,15 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prize_title" = "Congratulations!"; "lng_prize_about" = "You won a prize in a giveaway organized by {channel}."; -"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription for {duration}."; +"lng_prize_duration" = "Your prize is a **Telegram Premium** subscription {duration}."; "lng_prize_gift_about" = "You've received a gift from {channel}."; -"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription for {duration}."; +"lng_prize_gift_duration" = "Your gift is a **Telegram Premium** subscription {duration}."; "lng_prize_open" = "Open Gift Link"; "lng_prizes_title#one" = "Giveaway Prize"; "lng_prizes_title#other" = "Giveaway Prizes"; -"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription for {duration}."; -"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions for {duration}."; +"lng_prizes_about#one" = "**{count}** Telegram Premium Subscription {duration}."; +"lng_prizes_about#other" = "**{count}** Telegram Premium Subscriptions {duration}."; "lng_prizes_participants" = "Participants"; "lng_prizes_participants_all#one" = "All subscribers of the channel:"; "lng_prizes_participants_all#other" = "All subscribers of the channels:"; @@ -2096,10 +2096,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_end_title" = "Giveaway ended"; "lng_prizes_how_text" = "This giveaway is sponsored by {admins}."; "lng_prizes_end_text" = "This giveaway was sponsored by {admins}."; -"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers"; -"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers."; +"lng_prizes_admins#one" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscription {duration} for its followers"; +"lng_prizes_admins#other" = "the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions {duration} for its followers."; "lng_prizes_how_when_finish" = "On {date}, Telegram will automatically select {winners}."; "lng_prizes_end_when_finish" = "On {date}, Telegram automatically selected {winners}."; +"lng_prizes_end_activated#one" = "**{count}** of the winners already used their gift link."; +"lng_prizes_end_activated#other" = "**{count}** of the winners already used their gift links."; "lng_prizes_winners_all_of_one#one" = "{count} random subscribers of {channel}."; "lng_prizes_winners_all_of_one#other" = "{count} random subscribers of {channel}."; "lng_prizes_winners_all_of_many#one" = "{count} random subscribers of {channel} and other listed channels."; @@ -2112,8 +2114,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_prizes_how_participate_many" = "To take part in this giveaway please join channel {channel} and other listed channels before {date}."; "lng_prizes_how_no_admin" = "You are not eligible to participate in this giveaway, because you are an admin of participating channel ({channel})."; "lng_prizes_how_no_joined" = "You are not eligible to participate in this giveaway, because you joined this channel on {date}, which is before the contest started."; -"lng_prizes_how_yes_joined" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels)."; +"lng_prizes_how_yes_joined_one" = "You are participating in this giveaway, because you have joined channel {channel}."; +"lng_prizes_how_yes_joined_many" = "You are participating in this giveaway, because you have joined channel {channel} (and other listed channels)."; "lng_prizes_you_won" = "You won a prize in this giveaway {cup}"; +"lng_prizes_view_prize" = "View my prize"; "lng_prizes_you_didnt" = "You didn't win a prize in this giveaway."; "lng_prizes_cancelled" = "The channel cancelled the prizes by reversing the payment for them."; "lng_prizes_badge" = "x{amount}"; @@ -2123,7 +2127,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_link_label_from" = "From"; "lng_gift_link_label_to" = "To"; "lng_gift_link_label_gift" = "Gift"; -"lng_gift_link_gift_premium" = "Telegram Premium for {duration}"; +"lng_gift_link_gift_premium" = "Telegram Premium {duration}"; "lng_gift_link_label_reason" = "Reason"; "lng_gift_link_reason_giveaway" = "Giveaway"; "lng_gift_link_label_date" = "Date"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 05048e29f..433660b0d 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -242,6 +242,65 @@ rpl::producer Premium::giftCodeValue(const QString &slug) const { }); } +void Premium::applyGiftCode(const QString &slug, Fn done) { + _api.request(MTPpayments_ApplyGiftCode( + MTP_string(slug) + )).done([=](const MTPUpdates &result) { + _session->api().applyUpdates(result); + done({}); + }).fail([=](const MTP::Error &error) { + done(error.type()); + }).send(); +} + +void Premium::resolveGiveawayInfo( + not_null peer, + MsgId messageId, + Fn done) { + Expects(done != nullptr); + + _giveawayInfoDone = std::move(done); + if (_giveawayInfoRequestId) { + if (_giveawayInfoPeer == peer + && _giveawayInfoMessageId == messageId) { + return; + } + _api.request(_giveawayInfoRequestId).cancel(); + } + _giveawayInfoPeer = peer; + _giveawayInfoMessageId = messageId; + _giveawayInfoRequestId = _api.request(MTPpayments_GetGiveawayInfo( + _giveawayInfoPeer->input, + MTP_int(_giveawayInfoMessageId.bare) + )).done([=](const MTPpayments_GiveawayInfo &result) { + _giveawayInfoRequestId = 0; + + auto info = GiveawayInfo(); + result.match([&](const MTPDpayments_giveawayInfo &data) { + info.participating = data.is_participating(); + info.state = data.is_preparing_results() + ? GiveawayState::Preparing + : GiveawayState::Running; + info.adminChannelId = data.vadmin_disallowed_chat_id() + ? ChannelId(*data.vadmin_disallowed_chat_id()) + : ChannelId(); + info.tooEarlyDate + = data.vjoined_too_early_date().value_or_empty(); + }, [&](const MTPDpayments_giveawayInfoResults &data) { + info.state = data.is_refunded() + ? GiveawayState::Refunded + : GiveawayState::Finished; + info.giftCode = qs(data.vgift_code_slug().value_or_empty()); + info.activatedCount = data.vactivated_count().v; + info.finishDate = data.vfinish_date().v; + }); + _giveawayInfoDone(std::move(info)); + }).fail([=] { + _giveawayInfoRequestId = 0; + _giveawayInfoDone({}); + }).send(); +} + const Data::SubscriptionOptions &Premium::subscriptionOptions() const { return _subscriptionOptions; } diff --git a/Telegram/SourceFiles/api/api_premium.h b/Telegram/SourceFiles/api/api_premium.h index 6fe1c9d70..62c7b4c9a 100644 --- a/Telegram/SourceFiles/api/api_premium.h +++ b/Telegram/SourceFiles/api/api_premium.h @@ -34,6 +34,29 @@ struct GiftCode { const GiftCode&) = default; }; +enum class GiveawayState { + Invalid, + Running, + Preparing, + Finished, + Refunded, +}; + +struct GiveawayInfo { + QString giftCode; + ChannelId adminChannelId = 0; + GiveawayState state = GiveawayState::Invalid; + TimeId tooEarlyDate = 0; + TimeId finishDate = 0; + int winnersCount = 0; + int activatedCount = 0; + bool participating = false; + + explicit operator bool() const { + return state != GiveawayState::Invalid; + } +}; + class Premium final { public: explicit Premium(not_null api); @@ -62,6 +85,12 @@ public: GiftCode updateGiftCode(const QString &slug, const GiftCode &code); [[nodiscard]] rpl::producer giftCodeValue( const QString &slug) const; + void applyGiftCode(const QString &slug, Fn done); + + void resolveGiveawayInfo( + not_null peer, + MsgId messageId, + Fn done); [[nodiscard]] auto subscriptionOptions() const -> const Data::SubscriptionOptions &; @@ -99,6 +128,11 @@ private: base::flat_map _giftCodes; rpl::event_stream _giftCodeUpdated; + mtpRequestId _giveawayInfoRequestId = 0; + PeerData *_giveawayInfoPeer = nullptr; + MsgId _giveawayInfoMessageId = 0; + Fn _giveawayInfoDone; + Data::SubscriptionOptions _subscriptionOptions; }; diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index f797aa116..94ff50e13 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -14,6 +14,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/weak_ptr.h" #include "boxes/peers/prepare_short_info_box.h" #include "data/data_changes.h" +#include "data/data_channel.h" +#include "data/data_media_types.h" // Data::Giveaway #include "data/data_peer_values.h" // Data::PeerPremiumValue. #include "data/data_session.h" #include "data/data_subscription_option.h" @@ -312,10 +314,10 @@ struct GiftCodeLink { return result; } -[[nodiscard]] rpl::producer DurationValue(int months) { +[[nodiscard]] tr::phrase GiftDurationPhrase(int months) { return (months < 12) - ? tr::lng_months(lt_count, rpl::single(float64(months))) - : tr::lng_years(lt_count, rpl::single(float64(months / 12))); + ? tr::lng_premium_gift_duration_months + : tr::lng_premium_gift_duration_years; } [[nodiscard]] object_ptr MakePeerTableValue( @@ -437,6 +439,19 @@ void GiftPremiumValidator::showBox(not_null user) { }).send(); } +rpl::producer GiftDurationValue(int months) { + return GiftDurationPhrase(months)( + lt_count, + rpl::single(float64((months < 12) ? months : (months / 12)))); +} + +QString GiftDuration(int months) { + return GiftDurationPhrase(months)( + tr::now, + lt_count, + (months < 12) ? months : (months / 12)); +} + void GiftCodeBox( not_null box, not_null controller, @@ -444,6 +459,7 @@ void GiftCodeBox( struct State { rpl::variable data; rpl::variable used; + bool sent = false; }; const auto session = &controller->session(); const auto state = box->lifetime().make_state(State{}); @@ -508,7 +524,7 @@ void GiftCodeBox( tr::lng_gift_link_label_gift(), tr::lng_gift_link_gift_premium( lt_duration, - DurationValue(current.months))); + GiftDurationValue(current.months))); AddTableRow( table, tr::lng_gift_link_label_reason(), @@ -567,10 +583,18 @@ void GiftCodeBox( ), [=] { if (state->used.current()) { box->closeBox(); - } else { - auto copy = state->data.current(); - copy.used = base::unixtime::now(); - state->data = std::move(copy); + } else if (!state->sent) { + state->sent = true; + const auto done = crl::guard(box, [=](const QString &error) { + if (error.isEmpty()) { + auto copy = state->data.current(); + copy.used = base::unixtime::now(); + state->data = std::move(copy); + } else { + box->uiShow()->showToast(error); + } + }); + controller->session().api().premium().applyGiftCode(slug, done); } }); const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding; @@ -598,3 +622,179 @@ void ResolveGiftCode( slug, crl::guard(controller, done)); } + +void GiveawayInfoBox( + not_null box, + not_null controller, + Data::Giveaway giveaway, + Api::GiveawayInfo info) { + using State = Api::GiveawayState; + const auto finished = (info.state == State::Finished) + || (info.state == State::Refunded); + + box->setTitle((finished + ? tr::lng_prizes_end_title + : tr::lng_prizes_how_title)()); + + const auto first = !giveaway.channels.empty() + ? giveaway.channels.front()->name() + : u"channel"_q; + auto text = (finished + ? tr::lng_prizes_end_text + : tr::lng_prizes_how_text)( + tr::now, + lt_admins, + tr::lng_prizes_admins( + tr::now, + lt_count, + giveaway.quantity, + lt_channel, + Ui::Text::Bold(first), + lt_duration, + TextWithEntities{ GiftDuration(giveaway.months) }, + Ui::Text::RichLangValue), + Ui::Text::RichLangValue); + const auto many = (giveaway.channels.size() > 1); + const auto count = info.winnersCount + ? info.winnersCount + : giveaway.quantity; + auto winners = giveaway.all + ? (many + ? tr::lng_prizes_winners_all_of_many + : tr::lng_prizes_winners_all_of_one)( + tr::now, + lt_count, + count, + lt_channel, + Ui::Text::Bold(first), + Ui::Text::RichLangValue) + : (many + ? tr::lng_prizes_winners_new_of_many + : tr::lng_prizes_winners_new_of_one)( + tr::now, + lt_count, + count, + lt_channel, + Ui::Text::Bold(first), + lt_start_date, + Ui::Text::Bold("some date"), + Ui::Text::RichLangValue); + text.append("\n\n").append((finished + ? tr::lng_prizes_end_when_finish + : tr::lng_prizes_how_when_finish)( + tr::now, + lt_date, + Ui::Text::Bold(langDayOfMonthFull( + base::unixtime::parse(giveaway.untilDate).date())), + lt_winners, + winners, + Ui::Text::RichLangValue)); + if (info.activatedCount > 0) { + text.append(' ').append(tr::lng_prizes_end_activated( + tr::now, + lt_count, + info.activatedCount)); + } + if (!info.giftCode.isEmpty()) { + text.append("\n\n"); + text.append(tr::lng_prizes_you_won( + tr::now, + lt_cup, + QString::fromUtf8("\xf0\x9f\x8f\x86"))); + } else if (info.state == State::Finished) { + text.append("\n\n"); + text.append(tr::lng_prizes_you_didnt(tr::now)); + } else if (info.state == State::Preparing) { + + } else if (info.state != State::Refunded) { + if (info.adminChannelId) { + const auto channel = controller->session().data().channel( + info.adminChannelId); + text.append("\n\n").append(tr::lng_prizes_how_no_admin( + tr::now, + lt_channel, + Ui::Text::Bold(channel->name()), + Ui::Text::RichLangValue)); + } else if (info.tooEarlyDate) { + text.append("\n\n").append(tr::lng_prizes_how_no_joined( + tr::now, + lt_date, + Ui::Text::Bold( + langDateTime(base::unixtime::parse(info.tooEarlyDate))), + Ui::Text::RichLangValue)); + } else if (info.participating) { + text.append("\n\n").append((many + ? tr::lng_prizes_how_yes_joined_many + : tr::lng_prizes_how_yes_joined_one)( + tr::now, + lt_channel, + Ui::Text::Bold(first), + Ui::Text::RichLangValue)); + } else { + text.append("\n\n").append((many + ? tr::lng_prizes_how_participate_many + : tr::lng_prizes_how_participate_one)( + tr::now, + lt_channel, + Ui::Text::Bold(first), + lt_date, + Ui::Text::Bold(langDayOfMonthFull( + base::unixtime::parse(giveaway.untilDate).date())), + Ui::Text::RichLangValue)); + } + } + const auto padding = st::boxPadding; + box->addRow( + object_ptr( + box.get(), + rpl::single(std::move(text)), + st::boxLabel), + { padding.left(), 0, padding.right(), padding.bottom() }); + + if (info.state == State::Refunded) { + const auto wrap = box->addRow( + object_ptr>( + box.get(), + object_ptr( + box.get(), + tr::lng_prizes_cancelled(), + st::giveawayRefundedLabel), + st::giveawayRefundedPadding), + { padding.left(), 0, padding.right(), padding.bottom() }); + const auto bg = wrap->lifetime().make_state( + st::boxRadius, + st::attentionBoxButton.textBgOver); + wrap->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(wrap); + bg->paint(p, wrap->rect()); + }, wrap->lifetime()); + } + if (const auto slug = info.giftCode; !slug.isEmpty()) { + box->addButton(tr::lng_prizes_view_prize(), [=] { + ResolveGiftCode(controller, slug); + }); + box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); + } else { + box->addButton(tr::lng_close(), [=] { box->closeBox(); }); + } +} + +void ResolveGiveawayInfo( + not_null controller, + not_null peer, + MsgId messageId, + Data::Giveaway giveaway) { + const auto show = [=](Api::GiveawayInfo info) { + if (!info) { + controller->showToast( + tr::lng_confirm_phone_link_invalid(tr::now)); + } else { + controller->show( + Box(GiveawayInfoBox, controller, giveaway, info)); + } + }; + controller->session().api().premium().resolveGiveawayInfo( + peer, + messageId, + crl::guard(controller, show)); +} diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index 6fbac2b76..3f6d273bc 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -15,6 +15,10 @@ namespace Api { struct GiftCode; } // namespace Api +namespace Data { +struct Giveaway; +} // namespace Data + namespace Ui { class GenericBox; } // namespace Ui @@ -38,6 +42,9 @@ private: }; +[[nodiscard]] rpl::producer GiftDurationValue(int months); +[[nodiscard]] QString GiftDuration(int months); + void GiftCodeBox( not_null box, not_null controller, @@ -45,3 +52,9 @@ void GiftCodeBox( void ResolveGiftCode( not_null controller, const QString &slug); + +void ResolveGiveawayInfo( + not_null controller, + not_null peer, + MsgId messageId, + Data::Giveaway giveaway); diff --git a/Telegram/SourceFiles/history/view/history_view_view_button.cpp b/Telegram/SourceFiles/history/view/history_view_view_button.cpp index 70c49452a..c5448392a 100644 --- a/Telegram/SourceFiles/history/view/history_view_view_button.cpp +++ b/Telegram/SourceFiles/history/view/history_view_view_button.cpp @@ -16,8 +16,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_sponsored_messages.h" #include "data/data_user.h" #include "data/data_web_page.h" -#include "history/history_item_components.h" #include "history/view/history_view_cursor_state.h" +#include "history/history_item_components.h" +#include "history/history.h" #include "lang/lang_keys.h" #include "main/main_session.h" #include "ui/click_handler.h" @@ -86,6 +87,9 @@ inline auto WebPageToPhrase(not_null webpage) { [[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler( not_null media) { if (const auto giveaway = media->giveaway()) { + const auto peer = media->parent()->history()->peer; + const auto messageId = media->parent()->id; + const auto info = *giveaway; return std::make_shared([=]( ClickContext context) { const auto my = context.other.value(); @@ -93,6 +97,7 @@ inline auto WebPageToPhrase(not_null webpage) { if (!controller) { return; } + ResolveGiveawayInfo(controller, peer, messageId, info); }); } const auto webpage = media->webpage(); diff --git a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp index 5f3e64a24..5f0d3f51d 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_giveaway.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "history/view/media/history_view_giveaway.h" #include "base/unixtime.h" +#include "boxes/gift_premium_box.h" #include "chat_helpers/stickers_gift_box_pack.h" #include "data/data_channel.h" #include "data/data_document.h" @@ -91,9 +92,6 @@ void Giveaway::fillFromData(not_null giveaway) { tr::lng_prizes_title(tr::now, lt_count, _quantity), kDefaultTextOptions); - const auto duration = (_months < 12) - ? tr::lng_months(tr::now, lt_count, _months) - : tr::lng_years(tr::now, lt_count, _months / 12); _prizes.setMarkedText( st::defaultTextStyle, tr::lng_prizes_about( @@ -101,7 +99,7 @@ void Giveaway::fillFromData(not_null giveaway) { lt_count, _quantity, lt_duration, - Ui::Text::Bold(duration), + Ui::Text::Bold(GiftDuration(_months)), Ui::Text::RichLangValue), kDefaultTextOptions); _participantsTitle.setText( diff --git a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp index fdb3943b2..1ec8cd507 100644 --- a/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp +++ b/Telegram/SourceFiles/history/view/media/history_view_premium_gift.cpp @@ -23,18 +23,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_chat.h" namespace HistoryView { -namespace { - -[[nodiscard]] QString FormatGiftMonths(int months) { - return (months < 12) - ? tr::lng_premium_gift_duration_months(tr::now, lt_count, months) - : tr::lng_premium_gift_duration_years( - tr::now, - lt_count, - std::round(months / 12.)); -} - -} // namespace PremiumGift::PremiumGift( not_null parent, @@ -62,11 +50,8 @@ QString PremiumGift::title() { TextWithEntities PremiumGift::subtitle() { if (_data.slug.isEmpty()) { - return { FormatGiftMonths(_data.months) }; + return { GiftDuration(_data.months) }; } - const auto duration = (_data.months < 12) - ? tr::lng_months(tr::now, lt_count, _data.months) - : tr::lng_years(tr::now, lt_count, _data.months / 12); const auto name = _data.channel ? _data.channel->name() : "channel"; auto result = (_data.viaGiveaway ? tr::lng_prize_about @@ -81,7 +66,7 @@ TextWithEntities PremiumGift::subtitle() { : tr::lng_prize_gift_duration)( tr::now, lt_duration, - Ui::Text::Bold(duration), + Ui::Text::Bold(GiftDuration(_data.months)), Ui::Text::RichLangValue)); return result; } diff --git a/Telegram/SourceFiles/ui/effects/premium.style b/Telegram/SourceFiles/ui/effects/premium.style index b3442e8a7..d7521700e 100644 --- a/Telegram/SourceFiles/ui/effects/premium.style +++ b/Telegram/SourceFiles/ui/effects/premium.style @@ -342,3 +342,9 @@ giveawayGiftCodeBox: Box(defaultBox) { } shadowIgnoreTopSkip: true; } +giveawayRefundedLabel: FlatLabel(boxLabel) { + align: align(top); + style: semiboldTextStyle; + textFg: attentionButtonFg; +} +giveawayRefundedPadding: margins(8px, 10px, 8px, 10px);