Support giveaway information box.

This commit is contained in:
John Preston 2023-10-11 13:29:02 +04:00
parent 0926bb1288
commit 981babf302
9 changed files with 342 additions and 38 deletions

View File

@ -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";

View File

@ -242,6 +242,65 @@ rpl::producer<GiftCode> Premium::giftCodeValue(const QString &slug) const {
});
}
void Premium::applyGiftCode(const QString &slug, Fn<void(QString)> 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<PeerData*> peer,
MsgId messageId,
Fn<void(GiveawayInfo)> 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;
}

View File

@ -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<ApiWrap*> api);
@ -62,6 +85,12 @@ public:
GiftCode updateGiftCode(const QString &slug, const GiftCode &code);
[[nodiscard]] rpl::producer<GiftCode> giftCodeValue(
const QString &slug) const;
void applyGiftCode(const QString &slug, Fn<void(QString)> done);
void resolveGiveawayInfo(
not_null<PeerData*> peer,
MsgId messageId,
Fn<void(GiveawayInfo)> done);
[[nodiscard]] auto subscriptionOptions() const
-> const Data::SubscriptionOptions &;
@ -99,6 +128,11 @@ private:
base::flat_map<QString, GiftCode> _giftCodes;
rpl::event_stream<QString> _giftCodeUpdated;
mtpRequestId _giveawayInfoRequestId = 0;
PeerData *_giveawayInfoPeer = nullptr;
MsgId _giveawayInfoMessageId = 0;
Fn<void(GiveawayInfo)> _giveawayInfoDone;
Data::SubscriptionOptions _subscriptionOptions;
};

View File

@ -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<QString> DurationValue(int months) {
[[nodiscard]] tr::phrase<lngtag_count> 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<Ui::RpWidget> MakePeerTableValue(
@ -437,6 +439,19 @@ void GiftPremiumValidator::showBox(not_null<UserData*> user) {
}).send();
}
rpl::producer<QString> 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<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
@ -444,6 +459,7 @@ void GiftCodeBox(
struct State {
rpl::variable<Api::GiftCode> data;
rpl::variable<bool> used;
bool sent = false;
};
const auto session = &controller->session();
const auto state = box->lifetime().make_state<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<Ui::GenericBox*> box,
not_null<Window::SessionController*> 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<Ui::FlatLabel>(
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<Ui::PaddingWrap<Ui::FlatLabel>>(
box.get(),
object_ptr<Ui::FlatLabel>(
box.get(),
tr::lng_prizes_cancelled(),
st::giveawayRefundedLabel),
st::giveawayRefundedPadding),
{ padding.left(), 0, padding.right(), padding.bottom() });
const auto bg = wrap->lifetime().make_state<Ui::RoundRect>(
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<Window::SessionController*> controller,
not_null<PeerData*> 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));
}

View File

@ -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<QString> GiftDurationValue(int months);
[[nodiscard]] QString GiftDuration(int months);
void GiftCodeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
@ -45,3 +52,9 @@ void GiftCodeBox(
void ResolveGiftCode(
not_null<Window::SessionController*> controller,
const QString &slug);
void ResolveGiveawayInfo(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
MsgId messageId,
Data::Giveaway giveaway);

View File

@ -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<WebPageData*> webpage) {
[[nodiscard]] ClickHandlerPtr MakeMediaButtonClickHandler(
not_null<Data::Media*> 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<LambdaClickHandler>([=](
ClickContext context) {
const auto my = context.other.value<ClickHandlerContext>();
@ -93,6 +97,7 @@ inline auto WebPageToPhrase(not_null<WebPageData*> webpage) {
if (!controller) {
return;
}
ResolveGiveawayInfo(controller, peer, messageId, info);
});
}
const auto webpage = media->webpage();

View File

@ -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<Data::Giveaway*> 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<Data::Giveaway*> giveaway) {
lt_count,
_quantity,
lt_duration,
Ui::Text::Bold(duration),
Ui::Text::Bold(GiftDuration(_months)),
Ui::Text::RichLangValue),
kDefaultTextOptions);
_participantsTitle.setText(

View File

@ -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<Element*> 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;
}

View File

@ -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);