Handle GiftCode links, show Gift Link box.

This commit is contained in:
John Preston 2023-09-28 22:44:22 +04:00
parent 3fc9ed0ccb
commit 744c1b925e
15 changed files with 715 additions and 57 deletions

View File

@ -816,20 +816,6 @@ PRIVATE
history/history_view_highlight_manager.h
history/history_widget.cpp
history/history_widget.h
info/info_content_widget.cpp
info/info_content_widget.h
info/info_controller.cpp
info/info_controller.h
info/info_layer_widget.cpp
info/info_layer_widget.h
info/info_memento.cpp
info/info_memento.h
info/info_section_widget.cpp
info/info_section_widget.h
info/info_top_bar.cpp
info/info_top_bar.h
info/info_wrap_widget.cpp
info/info_wrap_widget.h
info/boosts/info_boosts_inner_widget.cpp
info/boosts/info_boosts_inner_widget.h
info/boosts/info_boosts_widget.cpp
@ -916,6 +902,20 @@ PRIVATE
info/userpic/info_userpic_emoji_builder_preview.h
info/userpic/info_userpic_emoji_builder_widget.cpp
info/userpic/info_userpic_emoji_builder_widget.h
info/info_content_widget.cpp
info/info_content_widget.h
info/info_controller.cpp
info/info_controller.h
info/info_layer_widget.cpp
info/info_layer_widget.h
info/info_memento.cpp
info/info_memento.h
info/info_section_widget.cpp
info/info_section_widget.h
info/info_top_bar.cpp
info/info_top_bar.h
info/info_wrap_widget.cpp
info/info_wrap_widget.h
inline_bots/bot_attach_web_view.cpp
inline_bots/bot_attach_web_view.h
inline_bots/inline_bot_layout_internal.cpp

View File

@ -1640,6 +1640,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_action_story_mention_button" = "View Story";
"lng_action_story_mention_me_unavailable" = "The story where you mentioned {user} is no longer available.";
"lng_action_story_mention_unavailable" = "The story where {user} mentioned you is no longer available.";
"lng_action_giveaway_started" = "{from} just started a giveaway of Telegram Premium subscriptions to its followers.";
"lng_premium_gift_duration_months#one" = "for {count} month";
"lng_premium_gift_duration_months#other" = "for {count} months";
@ -2034,6 +2035,88 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_boost_now_instead" = "You currently boost {channel}. Do you want to boost {other} instead?";
"lng_boost_now_replace" = "Replace";
"lng_giveaway_new_title" = "Boosts via Gifts";
"lng_giveaway_new_about" = "Get more boosts for your channel by gifting Premium to your subscribers.";
"lng_giveaway_create_option" = "Create Giveaway";
"lng_giveaway_create_subtitle" = "winners are chosen randomly";
"lng_giveaway_award_option" = "Award Specific Users";
"lng_giveaway_award_subtitle" = "Select recipients >";
"lng_giveaway_award_chosen#one" = "{count} recipient >";
"lng_giveaway_award_chosen#other" = "{count} recipients >";
"lng_giveaway_quantity_title" = "Quantity of prizes / boosts";
"lng_giveaway_quantity#one" = "{count} Subscription / Boost";
"lng_giveaway_quantity#other" = "{count} Subscriptions / Boosts";
"lng_giveaway_quantity_about" = "Choose how many Premium subscriptions to give away and boosts to receive.";
"lng_giveaway_channels_title" = "Channels included in the giveaway";
"lng_giveaway_channels_this#one" = "this channel will receive {count} boost";
"lng_giveaway_channels_this#other" = "this channel will receive {count} boosts";
"lng_giveaway_channels_add" = "Add Channel";
"lng_giveaway_channels_about" = "Choose the channels the users need to join to take part in the giveaway.";
"lng_giveaway_users_title" = "Users eligible for the giveaway";
"lng_giveaway_users_all" = "All subscribers";
"lng_giveaway_users_new" = "Only new subscribers";
"lng_giveaway_users_about" = "Choose if you want to limit the giveaway only to the newly joined subscribers.";
"lng_giveaway_start" = "Start Giveaway";
"lng_giveaway_award" = "Gift Premium";
"lng_giveaway_date_title" = "Date when giveaway ends";
"lng_giveaway_date" = "Date and Time";
"lng_giveaway_date_about#one" = "Choose when {count} subscriber of your channel will be randomly selected to receive Telegram Premium.";
"lng_giveaway_date_about#other" = "Choose when {count} subscribers of your channel will be randomly selected to receive Telegram Premium.";
"lng_giveaway_duration_title#one" = "Duration of Premium subscription";
"lng_giveaway_duration_title#other" = "Duration of Premium subscriptions";
"lng_giveaway_duration_price" = "{price} x {amount}";
"lng_giveaway_duration_about" = "You can review the list of features and terms of use for Telegram Premium {link}.";
"lng_giveaway_duration_about_link" = "here";
"lng_giveaway_date_select" = "Select Date and Time";
"lng_giveaway_date_confirm" = "Confirm";
"lng_giveaway_channels_select#one" = "Select up to {count} channel";
"lng_giveaway_channels_select#other" = "Select up to {count} channels";
"lng_giveaway_recipients_save" = "Save Recipients";
"lng_giveaway_recipients_deselect" = "Deselect All";
"lng_prize_title" = "Congratulations!";
"lng_prize_about" = "You won a prize in a giveaway organized by {channel}.";
"lng_prize_duration" = "Your prize is **Telegram Premium** subscription for {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_participants" = "Participants";
"lng_prizes_participants_all#one" = "All subscribers of the channel:";
"lng_prizes_participants_all#other" = "All subscribers of the channels:";
"lng_prizes_participants_new#one" = "All users who joined the channel below after this date:";
"lng_prizes_participants_new#other" = "All users who joined the channels below after this date:";
"lng_prizes_date" = "Winners Selection Date";
"lng_prizes_how_works" = "How does it work?";
"lng_prizes_how_text#one" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscription for {duration} for its followers.";
"lng_prizes_how_text#other" = "This giveaway is sponsored by the admins of {channel}, who aquired **{count} Telegram Premium** subscriptions for {duration} for its followers.";
"lng_prizes_how_when_all_of_one#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}.";
"lng_prizes_how_when_all_of_one#other" = "On {date}, Telegram will automatically select {count} random subscribers of {channel}.";
"lng_prizes_how_when_all_of_many#one" = "On {date}, Telegram will automatically select {count} random subscribers of {channel} or other listed channels.";
"lng_prizes_how_when_all_of_many#other" = "On {date}, Telegram will automatically select {count} random subscribers of {channel} or other listed channels.";
"lng_prizes_how_when_new_of_one#one" = "On {date}, Telegram will automatically select {count} random user that joined {channel} after {start_date}";
"lng_prizes_how_when_new_of_one#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} after {start_date}";
"lng_prizes_how_when_new_of_many#one" = "On {date}, Telegram will automatically select {count} random user that joined {channel} or other listed channels after {start_date}";
"lng_prizes_how_when_new_of_many#other" = "On {date}, Telegram will automatically select {count} random users that joined {channel} or other listed channels after {start_date}";
"lng_gift_link_title" = "Gift Link";
"lng_gift_link_about" = "This link allows you to activate\na **Telegram Premium** subscription.";
"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_label_reason" = "Reason";
"lng_gift_link_reason_giveaway" = "Giveaway";
"lng_gift_link_label_date" = "Date";
"lng_gift_link_also_send" = "You can also {link} to a friend as a gift.";
"lng_gift_link_also_send_link" = "send this link";
"lng_gift_link_use" = "Use Link";
"lng_gift_link_used_title" = "Used Gift Link";
"lng_gift_link_used_about" = "This link was used to activate\na **Telegram Premium** subscription.";
"lng_gift_link_used_footer" = "This link was used on {date}.";
"lng_accounts_limit_title" = "Limit Reached";
"lng_accounts_limit1#one" = "You have reached the limit of **{count}** connected accounts.";
"lng_accounts_limit1#other" = "You have reached the limit of **{count}** connected accounts.";

View File

@ -17,6 +17,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "apiwrap.h"
namespace Api {
namespace {
[[nodiscard]] GiftCode Parse(const MTPDpayments_checkedGiftCode &data) {
return {
.from = peerFromMTP(data.vfrom_id()),
.to = data.vto_id() ? peerFromUser(*data.vto_id()) : PeerId(),
.date = data.vdate().v,
.used = false,// data.vused_date().value_or_empty(),
.months = data.vmonths().v,
};
}
} // namespace
Premium::Premium(not_null<ApiWrap*> api)
: _session(&api->session())
@ -183,6 +196,52 @@ void Premium::reloadCloudSet() {
}).send();
}
void Premium::checkGiftCode(
const QString &slug,
Fn<void(GiftCode)> done) {
if (_giftCodeRequestId) {
if (_giftCodeSlug == slug) {
return;
}
_api.request(_giftCodeRequestId).cancel();
}
_giftCodeSlug = slug;
_giftCodeRequestId = _api.request(MTPpayments_CheckGiftCode(
MTP_string(slug)
)).done([=](const MTPpayments_CheckedGiftCode &result) {
_giftCodeRequestId = 0;
const auto &data = result.data();
_session->data().processUsers(data.vusers());
_session->data().processChats(data.vchats());
done(updateGiftCode(slug, Parse(data)));
}).fail([=](const MTP::Error &error) {
_giftCodeRequestId = 0;
done(updateGiftCode(slug, {}));
}).send();
}
GiftCode Premium::updateGiftCode(
const QString &slug,
const GiftCode &code) {
auto &now = _giftCodes[slug];
if (now != code) {
now = code;
_giftCodeUpdated.fire_copy(slug);
}
return code;
}
rpl::producer<GiftCode> Premium::giftCodeValue(const QString &slug) const {
return _giftCodeUpdated.events_starting_with_copy(
slug
) | rpl::filter(rpl::mappers::_1 == slug) | rpl::map([=] {
const auto i = _giftCodes.find(slug);
return (i != end(_giftCodes)) ? i->second : GiftCode();
});
}
const Data::SubscriptionOptions &Premium::subscriptionOptions() const {
return _subscriptionOptions;
}

View File

@ -18,6 +18,22 @@ class Session;
namespace Api {
struct GiftCode {
PeerId from = 0;
PeerId to = 0;
TimeId date = 0;
TimeId used = 0; // 0 if not used.
int months = 0;
explicit operator bool() const {
return months != 0;
}
friend inline bool operator==(
const GiftCode&,
const GiftCode&) = default;
};
class Premium final {
public:
explicit Premium(not_null<ApiWrap*> api);
@ -40,6 +56,13 @@ public:
[[nodiscard]] int64 monthlyAmount() const;
[[nodiscard]] QString monthlyCurrency() const;
void checkGiftCode(
const QString &slug,
Fn<void(GiftCode)> done);
GiftCode updateGiftCode(const QString &slug, const GiftCode &code);
[[nodiscard]] rpl::producer<GiftCode> giftCodeValue(
const QString &slug) const;
[[nodiscard]] auto subscriptionOptions() const
-> const Data::SubscriptionOptions &;
@ -71,6 +94,11 @@ private:
int64 _monthlyAmount = 0;
QString _monthlyCurrency;
mtpRequestId _giftCodeRequestId = 0;
QString _giftCodeSlug;
base::flat_map<QString, GiftCode> _giftCodes;
rpl::event_stream<QString> _giftCodeUpdated;
Data::SubscriptionOptions _subscriptionOptions;
};

View File

@ -8,8 +8,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "boxes/gift_premium_box.h"
#include "apiwrap.h"
#include "api/api_premium.h"
#include "api/api_premium_option.h"
#include "base/unixtime.h"
#include "base/weak_ptr.h"
#include "boxes/peers/prepare_short_info_box.h"
#include "data/data_changes.h"
#include "data/data_peer_values.h" // Data::PeerPremiumValue.
#include "data/data_session.h"
@ -22,12 +25,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/userpic_button.h"
#include "ui/effects/premium_graphics.h"
#include "ui/effects/premium_stars_colored.h"
#include "ui/effects/premium_top_bar.h"
#include "ui/layers/generic_box.h"
#include "ui/text/format_values.h"
#include "ui/text/text_utilities.h"
#include "ui/widgets/checkbox.h"
#include "ui/widgets/gradient_round_button.h"
#include "ui/wrap/padding_wrap.h"
#include "ui/wrap/table_layout.h"
#include "window/window_session_controller.h"
#include "styles/style_boxes.h"
#include "styles/style_layers.h"
@ -35,6 +40,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "styles/style_info.h"
#include "styles/style_premium.h"
#include <QtGui/QGuiApplication>
namespace {
constexpr auto kDiscountDivider = 5.;
@ -225,6 +232,175 @@ void GiftBox(
}, box->lifetime());
}
struct GiftCodeLink {
QString text;
QString link;
};
[[nodiscard]] GiftCodeLink MakeGiftCodeLink(
not_null<Main::Session*> session,
const QString &slug) {
const auto path = u"giftcode/"_q + slug;
return {
session->createInternalLink(path),
session->createInternalLinkFull(path),
};
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakeLinkLabel(
not_null<QWidget*> parent,
rpl::producer<QString> text,
rpl::producer<QString> link,
std::shared_ptr<Ui::Show> show) {
auto result = object_ptr<Ui::AbstractButton>(parent);
const auto raw = result.data();
struct State {
State(
not_null<QWidget*> parent,
rpl::producer<QString> value,
rpl::producer<QString> link)
: text(std::move(value))
, link(std::move(link))
, label(parent, text.value(), st::giveawayGiftCodeLink)
, bg(st::roundRadiusLarge, st::windowBgOver) {
}
rpl::variable<QString> text;
rpl::variable<QString> link;
Ui::FlatLabel label;
Ui::RoundRect bg;
};
const auto state = raw->lifetime().make_state<State>(
raw,
rpl::duplicate(text),
std::move(link));
state->label.setSelectable(true);
rpl::combine(
raw->widthValue(),
std::move(text)
) | rpl::start_with_next([=](int outer, const auto&) {
const auto textWidth = state->label.textMaxWidth();
const auto skipLeft = st::giveawayGiftCodeLink.margin.left();
const auto skipRight = st::giveawayGiftCodeLinkCopyWidth;
const auto available = outer - skipRight - skipLeft;
const auto use = std::min(textWidth, available);
state->label.resizeToWidth(use);
const auto left = (outer >= 2 * skipRight + textWidth)
? ((outer - textWidth) / 2)
: (outer - skipRight - use - skipLeft);
state->label.move(left, 0);
}, raw->lifetime());
raw->paintRequest() | rpl::start_with_next([=] {
auto p = QPainter(raw);
state->bg.paint(p, raw->rect());
const auto outer = raw->width();
const auto width = st::giveawayGiftCodeLinkCopyWidth;
const auto &icon = st::giveawayGiftCodeLinkCopy;
const auto left = outer - width + (width - icon.width()) / 2;
const auto top = (raw->height() - icon.height()) / 2;
icon.paint(p, left, top, raw->width());
}, raw->lifetime());
state->label.setAttribute(Qt::WA_TransparentForMouseEvents);
raw->resize(raw->width(), st::giveawayGiftCodeLinkHeight);
raw->setClickedCallback([=] {
QGuiApplication::clipboard()->setText(state->link.current());
show->showToast(tr::lng_username_copied(tr::now));
});
return result;
}
[[nodiscard]] rpl::producer<QString> DurationValue(int months) {
return (months < 12)
? tr::lng_months(lt_count, rpl::single(float64(months)))
: tr::lng_years(lt_count, rpl::single(float64(months / 12)));
}
[[nodiscard]] object_ptr<Ui::RpWidget> MakePeerTableValue(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
PeerId id) {
auto result = object_ptr<Ui::AbstractButton>(parent);
const auto raw = result.data();
const auto &st = st::giveawayGiftCodeUserpic;
raw->resize(raw->width(), st.photoSize);
const auto peer = controller->session().data().peer(id);
const auto userpic = Ui::CreateChild<Ui::UserpicButton>(raw, peer, st);
const auto label = Ui::CreateChild<Ui::FlatLabel>(
raw,
peer->name(),
st::giveawayGiftCodeValue);
raw->widthValue(
) | rpl::start_with_next([=](int width) {
const auto position = st::giveawayGiftCodeNamePosition;
label->resizeToNaturalWidth(width - position.x());
label->moveToLeft(position.x(), position.y(), width);
const auto top = (raw->height() - userpic->height()) / 2;
userpic->moveToLeft(0, top, width);
}, label->lifetime());
userpic->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setAttribute(Qt::WA_TransparentForMouseEvents);
label->setTextColorOverride(st::windowActiveTextFg->c);
raw->setClickedCallback([=] {
controller->show(PrepareShortInfoBox(peer, controller));
});
return result;
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
object_ptr<Ui::RpWidget> value,
style::margins valueMargins) {
table->addRow(
object_ptr<Ui::FlatLabel>(
table,
std::move(label),
st::giveawayGiftCodeLabel),
std::move(value),
st::giveawayGiftCodeLabelMargin,
valueMargins);
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
rpl::producer<QString> value) {
AddTableRow(
table,
std::move(label),
object_ptr<Ui::FlatLabel>(
table,
std::move(value),
st::giveawayGiftCodeValue),
st::giveawayGiftCodeValueMargin);
}
void AddTableRow(
not_null<Ui::TableLayout*> table,
rpl::producer<QString> label,
not_null<Window::SessionController*> controller,
PeerId id) {
if (!id) {
return;
}
AddTableRow(
table,
std::move(label),
MakePeerTableValue(table, controller, id),
st::giveawayGiftCodePeerMargin);
}
} // namespace
GiftPremiumValidator::GiftPremiumValidator(
@ -263,3 +439,148 @@ void GiftPremiumValidator::showBox(not_null<UserData*> user) {
_requestId = 0;
}).send();
}
void GiftCodeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const QString &slug) {
struct State {
rpl::variable<Api::GiftCode> data;
rpl::variable<bool> used;
};
const auto session = &controller->session();
const auto state = box->lifetime().make_state<State>(State{});
state->data = session->api().premium().giftCodeValue(slug);
state->used = state->data.value(
) | rpl::map([=](const Api::GiftCode &data) {
return data.used;
});
box->setWidth(st::boxWideWidth);
box->setStyle(st::giveawayGiftCodeBox);
box->setNoContentMargin(true);
const auto bar = box->setPinnedToTopContent(
object_ptr<Ui::Premium::TopBar>(
box,
st::giveawayGiftCodeCover,
nullptr,
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_title(),
tr::lng_gift_link_title()),
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_about(Ui::Text::RichLangValue),
tr::lng_gift_link_about(Ui::Text::RichLangValue)),
true));
const auto max = st::giveawayGiftCodeTopHeight;
bar->setMaximumHeight(max);
bar->setMinimumHeight(st::infoLayerTopBarHeight);
bar->resize(bar->width(), bar->maximumHeight());
const auto link = MakeGiftCodeLink(&controller->session(), slug);
box->addRow(
MakeLinkLabel(
box,
rpl::single(link.text),
rpl::single(link.link),
box->uiShow()),
st::giveawayGiftCodeLinkMargin);
auto table = box->addRow(
object_ptr<Ui::TableLayout>(
box,
st::giveawayGiftCodeTable),
st::giveawayGiftCodeTableMargin);
const auto current = state->data.current();
AddTableRow(
table,
tr::lng_gift_link_label_from(),
controller,
current.from);
AddTableRow(
table,
tr::lng_gift_link_label_to(),
controller,
current.to);
AddTableRow(
table,
tr::lng_gift_link_label_gift(),
tr::lng_gift_link_gift_premium(
lt_duration,
DurationValue(current.months)));
AddTableRow(
table,
tr::lng_gift_link_label_reason(),
tr::lng_gift_link_reason_giveaway());
AddTableRow(
table,
tr::lng_gift_link_label_date(),
rpl::single(langDateTime(base::unixtime::parse(current.date))));
auto shareLink = tr::lng_gift_link_also_send_link(
) | rpl::map([](const QString &text) {
return Ui::Text::Link(text);
});
auto richDate = [](const Api::GiftCode &data) {
return TextWithEntities{
langDateTime(base::unixtime::parse(data.used)),
};
};
const auto footer = box->addRow(
object_ptr<Ui::FlatLabel>(
box,
rpl::conditional(
state->used.value(),
tr::lng_gift_link_used_footer(
lt_date,
state->data.value() | rpl::map(richDate),
Ui::Text::WithEntities),
tr::lng_gift_link_also_send(
lt_link,
std::move(shareLink),
Ui::Text::WithEntities)),
st::giveawayGiftCodeFooter),
st::giveawayGiftCodeFooterMargin);
footer->setClickHandlerFilter([=](const auto &...) {
box->uiShow()->showToast(u"Sharing..."_q);
return false;
});
const auto close = Ui::CreateChild<Ui::IconButton>(
box.get(),
st::boxTitleClose);
close->setClickedCallback([=] {
box->closeBox();
});
box->widthValue(
) | rpl::start_with_next([=](int width) {
close->moveToRight(0, 0);
}, box->lifetime());
const auto button = box->addButton(rpl::conditional(
state->used.value(),
tr::lng_box_ok(),
tr::lng_gift_link_use()
), [=] {
if (state->used.current()) {
box->closeBox();
} else {
auto copy = state->data.current();
copy.used = base::unixtime::now();
state->data = std::move(copy);
}
});
const auto buttonPadding = st::giveawayGiftCodeBox.buttonPadding;
const auto buttonWidth = st::boxWideWidth
- buttonPadding.left()
- buttonPadding.right();
button->widthValue() | rpl::filter([=] {
return (button->widthNoMargins() != buttonWidth);
}) | rpl::start_with_next([=] {
button->resizeToWidth(buttonWidth);
}, button->lifetime());
}

View File

@ -11,6 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
class UserData;
namespace Api {
struct GiftCode;
} // namespace Api
namespace Ui {
class GenericBox;
} // namespace Ui
namespace Window {
class SessionController;
} // namespace Window
@ -29,3 +37,8 @@ private:
mtpRequestId _requestId = 0;
};
void GiftCodeBox(
not_null<Ui::GenericBox*> box,
not_null<Window::SessionController*> controller,
const QString &slug);

View File

@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "api/api_text_entities.h"
#include "api/api_chat_filters.h"
#include "api/api_chat_invite.h"
#include "api/api_premium.h"
#include "base/qthelp_regex.h"
#include "base/qthelp_url.h"
#include "lang/lang_cloud_manager.h"
@ -23,6 +24,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/boxes/confirm_box.h"
#include "boxes/share_box.h"
#include "boxes/connection_box.h"
#include "boxes/gift_premium_box.h"
#include "boxes/sticker_set_box.h"
#include "boxes/sessions_box.h"
#include "boxes/language_box.h"
@ -348,6 +350,21 @@ bool ResolveUsernameOrPhone(
const auto domainParam = params.value(u"domain"_q);
const auto appnameParam = params.value(u"appname"_q);
if (domainParam == u"giftcode"_q && !appnameParam.isEmpty()) {
const auto done = [=](Api::GiftCode code) {
if (!code) {
controller->showToast(u"Gift code link expired"_q);
} else {
controller->show(
Box(GiftCodeBox, controller, appnameParam));
}
};
controller->session().api().premium().checkGiftCode(
appnameParam,
crl::guard(controller, done));
return true;
}
// Fix t.me/s/username links.
const auto webChannelPreviewLink = (domainParam == u"s"_q)
&& !appnameParam.isEmpty();
@ -1078,7 +1095,7 @@ QString TryConvertUrlToLocal(QString url) {
"("
"/?\\?|"
"/?$|"
"/[a-zA-Z0-9\\.\\_]+/?(\\?|$)|"
"/[a-zA-Z0-9\\.\\_\\-]+/?(\\?|$)|"
"/\\d+/?(\\?|$)|"
"/s/\\d+/?(\\?|$)|"
"/\\d+/\\d+/?(\\?|$)"
@ -1103,7 +1120,7 @@ QString TryConvertUrlToLocal(QString url) {
added = u"&post="_q + postMatch->captured(1);
} else if (const auto storyMatch = regex_match(u"^/s/(\\d+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&story="_q + storyMatch->captured(1);
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
} else if (const auto appNameMatch = regex_match(u"^/([a-zA-Z0-9\\.\\_\\-]+)(/?\\?|/?$)"_q, usernameMatch->captured(2))) {
added = u"&appname="_q + appNameMatch->captured(1);
}
return base + added + (params.isEmpty() ? QString() : '&' + params);

View File

@ -60,8 +60,8 @@ const style::InfoTopBar &TopBarStyle(Wrap wrap) {
[[nodiscard]] bool HasCustomTopBar(not_null<const Controller*> controller) {
const auto section = controller->section();
return (section.type() == Section::Type::Settings
&& (section.settingsType() == ::Settings::PremiumId()));
return (section.type() == Section::Type::Settings)
&& section.settingsType()->hasCustomTopBar();
}
} // namespace
@ -788,8 +788,8 @@ void WrapWidget::showNewContent(
newController->takeStepData(_controller.get());
}
auto newContent = object_ptr<ContentWidget>(nullptr);
const auto enableBackButton = hasBackButton();
const auto createInAdvance = needAnimation || enableBackButton;
const auto withBackButton = willHaveBackButton(params);
const auto createInAdvance = needAnimation || withBackButton;
if (createInAdvance) {
newContent = createContent(memento, newController.get());
}
@ -823,7 +823,7 @@ void WrapWidget::showNewContent(
_historyStack.clear();
}
if (enableBackButton) {
if (withBackButton) {
newContent->enableBackButton();
}
@ -969,6 +969,17 @@ bool WrapWidget::hasBackButton() const {
return (wrap() == Wrap::Narrow || hasStackHistory());
}
bool WrapWidget::willHaveBackButton(
const Window::SectionShow &params) const {
using Way = Window::SectionShow::Way;
const auto willSaveToStack = (_content != nullptr)
&& (params.way == Way::Forward);
const auto willClearStack = (params.way == Way::ClearStack);
const auto willHaveStack = !willClearStack
&& (hasStackHistory() || willSaveToStack);
return (wrap() == Wrap::Narrow) || willHaveStack;
}
WrapWidget::~WrapWidget() = default;
} // namespace Info

View File

@ -176,6 +176,8 @@ private:
void setupShortcuts();
[[nodiscard]] bool hasBackButton() const;
[[nodiscard]] bool willHaveBackButton(
const Window::SectionShow &params) const;
not_null<RpWidget*> topWidget() const;

View File

@ -424,8 +424,6 @@ notifyPreviewBottomSkip: 9px;
settingsPremiumButtonPadding: margins(11px, 11px, 11px, 3px);
settingsPremiumTopBarBackIcon: icon {{ "info/info_back", premiumButtonFg }};
settingsPremiumTopBarBackIconOver: icon {{ "info/info_back", premiumButtonFg }};
settingsPremiumStarSize: size(84px, 81px);
settingsPremiumStarTopSkip: 37px;
settingsPremiumTopBarBack: IconButton(infoTopBarBack) {
icon: settingsPremiumTopBarBackIcon;
iconOver: settingsPremiumTopBarBackIconOver;
@ -467,21 +465,6 @@ settingsPremiumPreviewIconTitlePadding: margins(62px, 13px, 24px, 1px);
settingsPremiumPreviewIconAboutPadding: margins(62px, 0px, 24px, 0px);
settingsPremiumPreviewIconPosition: point(20px, 7px);
settingsPremiumTitlePadding: margins(0px, 18px, 0px, 11px);
settingsPremiumAboutTextStyle: TextStyle(defaultTextStyle) {
font: font(12px);
linkUnderline: kLinkUnderlineAlways;
lineHeight: 18px;
}
settingsPremiumAbout: FlatLabel(defaultFlatLabel) {
style: settingsPremiumAboutTextStyle;
palette: TextPalette(defaultTextPalette) {
linkFg: premiumButtonFg;
}
align: align(top);
textFg: premiumButtonFg;
minWidth: 190px;
}
settingsPremiumArrowShift: point(-5px, -1px);
settingsPremiumArrow: icon{{ "settings/premium/arrow", menuIconFg }};
settingsPremiumArrowOver: icon{{ "settings/premium/arrow", menuIconFgOver }};
@ -499,12 +482,6 @@ settingsPremiumUserTitle: FlatLabel(boxTitle) {
maxHeight: 0px;
align: align(top);
}
settingsPremiumUserAbout: FlatLabel(boxDividerLabel) {
style: settingsPremiumAboutTextStyle;
minWidth: 315px;
maxHeight: 0px;
align: align(top);
}
settingsPremiumLock: icon{{ "emoji/premium_lock", windowActiveTextFg, point(0px, 1px) }};
settingsPremiumLockSkip: 3px;

View File

@ -56,6 +56,9 @@ struct AbstractSectionFactory {
[[nodiscard]] virtual object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller) const = 0;
[[nodiscard]] virtual bool hasCustomTopBar() const {
return false;
}
virtual ~AbstractSectionFactory() = default;
};

View File

@ -518,10 +518,10 @@ TopBarUser::TopBarUser(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
rpl::producer<> showFinished)
: TopBarAbstract(parent)
: TopBarAbstract(parent, st::userPremiumCover)
, _content(this)
, _title(_content, st::settingsPremiumUserTitle)
, _about(_content, st::settingsPremiumUserAbout)
, _about(_content, st::userPremiumCover.about)
, _ministars(_content)
, _smallTop({
.widget = object_ptr<Ui::RpWidget>(this),
@ -1173,6 +1173,7 @@ QPointer<Ui::RpWidget> Premium::createPinnedToTop(
};
return Ui::CreateChild<Ui::Premium::TopBar>(
parent.get(),
st::defaultPremiumCover,
clickContextOther,
std::move(title),
std::move(about));
@ -1371,6 +1372,24 @@ QPointer<Ui::RpWidget> Premium::createPinnedToBottom(
} // namespace
template <>
struct SectionFactory<Premium> : AbstractSectionFactory {
object_ptr<AbstractSection> create(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller
) const final override {
return object_ptr<Premium>(parent, controller);
}
bool hasCustomTopBar() const final override {
return true;
}
[[nodiscard]] static const std::shared_ptr<SectionFactory> &Instance() {
static const auto result = std::make_shared<SectionFactory>();
return result;
}
};
Type PremiumId() {
return Premium::Id();
}

View File

@ -26,6 +26,43 @@ PremiumBubble {
tailSize: size;
font: font;
}
PremiumCover {
starSize: size;
starTopSkip: pixels;
titlePadding: margins;
titleFont: font;
about: FlatLabel;
}
premiumAboutTextStyle: TextStyle(defaultTextStyle) {
font: font(12px);
linkUnderline: kLinkUnderlineAlways;
lineHeight: 18px;
}
defaultPremiumCover: PremiumCover {
starSize: size(84px, 81px);
starTopSkip: 37px;
titlePadding: margins(0px, 18px, 0px, 11px);
titleFont: boxTitleFont;
about: FlatLabel(defaultFlatLabel) {
style: premiumAboutTextStyle;
palette: TextPalette(defaultTextPalette) {
linkFg: premiumButtonFg;
}
align: align(top);
textFg: premiumButtonFg;
minWidth: 190px;
}
}
userPremiumCoverAbout: FlatLabel(boxDividerLabel) {
style: premiumAboutTextStyle;
minWidth: 315px;
maxHeight: 0px;
align: align(top);
}
userPremiumCover: PremiumCover(defaultPremiumCover) {
about: userPremiumCoverAbout;
}
defaultPremiumBoxLabel: FlatLabel(defaultFlatLabel) {
minWidth: 220px;
@ -239,3 +276,69 @@ boostReplaceIconSkip: 3px;
boostReplaceIconOutline: 2px;
boostReplaceIconAdd: point(4px, 2px);
boostReplaceArrow: icon{{ "mediaview/next", windowSubTextFg }};
giveawayGiftCodeTopHeight: 195px;
giveawayGiftCodeLink: FlatLabel(defaultFlatLabel) {
margin: margins(10px, 12px, 10px, 8px);
textFg: menuIconColor;
maxHeight: 24px;
}
giveawayGiftCodeLinkCopy: icon{{ "menu/copy", menuIconColor }};
giveawayGiftCodeLinkHeight: 42px;
giveawayGiftCodeLinkCopyWidth: 40px;
giveawayGiftCodeLinkMargin: margins(24px, 8px, 24px, 12px);
giveawayGiftCodeTable: Table(defaultTable) {
labelMinWidth: 91px;
}
giveawayGiftCodeTableMargin: margins(24px, 4px, 24px, 4px);
giveawayGiftCodeLabel: FlatLabel(defaultFlatLabel) {
textFg: menuIconColor;
maxHeight: 24px;
style: TextStyle(semiboldTextStyle) {
font: font(12px semibold);
}
}
giveawayGiftCodeLabelMargin: margins(13px, 10px, 13px, 10px);
giveawayGiftCodeValue: FlatLabel(defaultFlatLabel) {
maxHeight: 24px;
style: TextStyle(defaultTextStyle) {
font: font(12px);
linkUnderline: kLinkUnderlineNever;
}
}
giveawayGiftCodeValueMargin: margins(13px, 9px, 13px, 9px);
giveawayGiftCodePeerMargin: margins(11px, 6px, 11px, 4px);
giveawayGiftCodeUserpic: UserpicButton(defaultUserpicButton) {
size: size(24px, 24px);
photoSize: 24px;
photoPosition: point(-1px, -1px);
}
giveawayGiftCodeNamePosition: point(32px, 4px);
giveawayGiftCodeCover: PremiumCover(userPremiumCover) {
starSize: size(92px, 90px);
starTopSkip: 20px;
titlePadding: margins(0px, 15px, 0px, 17px);
titleFont: font(15px semibold);
about: FlatLabel(userPremiumCoverAbout) {
textFg: windowBoldFg;
style: TextStyle(premiumAboutTextStyle) {
lineHeight: 17px;
}
}
}
giveawayGiftCodeFooter: FlatLabel(defaultFlatLabel) {
align: align(top);
textFg: windowBoldFg;
}
giveawayGiftCodeFooterMargin: margins(0px, 9px, 0px, 4px);
giveawayGiftCodeBox: Box(defaultBox) {
buttonPadding: margins(22px, 11px, 22px, 22px);
buttonHeight: 42px;
button: RoundButton(defaultActiveButton) {
height: 42px;
textTop: 12px;
font: font(13px semibold);
}
shadowIgnoreTopSkip: true;
}

View File

@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/wrap/fade_wrap.h"
#include "styles/style_layers.h"
#include "styles/style_settings.h"
#include "styles/style_premium.h"
#include <QtCore/QFile>
@ -90,6 +91,13 @@ QImage GenerateStarForLightTopBar(QRectF rect) {
return frame;
}
TopBarAbstract::TopBarAbstract(
QWidget *parent,
const style::PremiumCover &st)
: RpWidget(parent)
, _st(st) {
}
void TopBarAbstract::setRoundEdges(bool value) {
_roundEdges = value;
update();
@ -122,11 +130,11 @@ void TopBarAbstract::paintEdges(QPainter &p) const {
QRectF TopBarAbstract::starRect(
float64 topProgress,
float64 sizeProgress) const {
const auto starSize = st::settingsPremiumStarSize * sizeProgress;
const auto starSize = _st.starSize * sizeProgress;
return QRectF(
QPointF(
(width() - starSize.width()) / 2,
st::settingsPremiumStarTopSkip * topProgress),
_st.starTopSkip * topProgress),
starSize);
};
@ -143,18 +151,16 @@ void TopBarAbstract::computeIsDark() {
TopBar::TopBar(
not_null<QWidget*> parent,
const style::PremiumCover &st,
Fn<QVariant()> clickContextOther,
rpl::producer<QString> title,
rpl::producer<TextWithEntities> about,
bool light)
: TopBarAbstract(parent)
: TopBarAbstract(parent, st)
, _light(light)
, _titleFont(st::boxTitle.style.font)
, _titlePadding(st::settingsPremiumTitlePadding)
, _about(
this,
std::move(about),
_light ? st::settingsPremiumUserAbout : st::settingsPremiumAbout)
, _titleFont(st.titleFont)
, _titlePadding(st.titlePadding)
, _about(this, std::move(about), st.about)
, _ministars(this) {
std::move(
title
@ -219,7 +225,7 @@ void TopBar::setTextPosition(int x, int y) {
rpl::producer<int> TopBar::additionalHeight() const {
return _about->heightValue(
) | rpl::map([l = st::settingsPremiumAbout.style.lineHeight](int height) {
) | rpl::map([l = st().about.style.lineHeight](int height) {
return std::max(height - l * 2, 0);
});
}

View File

@ -11,6 +11,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
#include "ui/effects/premium_stars_colored.h"
namespace style {
struct PremiumCover;
} // namespace style
namespace st {
extern const style::PremiumCover &defaultPremiumCover;
} // namespace st
namespace Ui {
class FlatLabel;
} // namespace Ui
@ -23,7 +31,9 @@ namespace Ui::Premium {
class TopBarAbstract : public RpWidget {
public:
using RpWidget::RpWidget;
TopBarAbstract(
QWidget *parent = nullptr,
const style::PremiumCover &st = st::defaultPremiumCover);
void setRoundEdges(bool value);
@ -32,6 +42,10 @@ public:
[[nodiscard]] virtual rpl::producer<int> additionalHeight() const = 0;
[[nodiscard]] const style::PremiumCover &st() const {
return _st;
}
protected:
void paintEdges(QPainter &p, const QBrush &brush) const;
void paintEdges(QPainter &p) const;
@ -44,6 +58,7 @@ protected:
void computeIsDark();
private:
const style::PremiumCover &_st;
bool _roundEdges = true;
bool _isDark = false;
@ -53,6 +68,7 @@ class TopBar final : public TopBarAbstract {
public:
TopBar(
not_null<QWidget*> parent,
const style::PremiumCover &st,
Fn<QVariant()> clickContextOther,
rpl::producer<QString> title,
rpl::producer<TextWithEntities> about,