Lang pack rating / premium gift features.

This commit is contained in:
John Preston
2025-07-17 11:33:06 +04:00
parent be6dbefe7c
commit 1cb4413869
6 changed files with 217 additions and 58 deletions
+12
View File
@@ -1876,6 +1876,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_star_ref_revoked_title" = "Link removed";
"lng_star_ref_revoked_text" = "It will no longer work.";
"lng_stars_rating_tooltip" = "Profile level reflects the user's payment reliability.";
"lng_stars_rating_learn_more" = "Learn More";
"lng_stars_rating_about_url" = "https://telegram.org/";
"lng_manage_discussion_group" = "Discussion";
"lng_manage_discussion_group_add" = "Add a group";
"lng_manage_linked_channel" = "Linked channel";
@@ -2589,6 +2593,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_premium_unlock_emoji" = "Unlock Animated Emoji";
"lng_premium_unlock_status" = "Unlock Emoji Status";
"lng_premium_subscribe_months_24" = "2-Year";
"lng_premium_subscribe_months_12" = "Annual";
"lng_premium_subscribe_months_6" = "Semiannual";
"lng_premium_subscribe_months_1" = "Monthly";
@@ -3508,6 +3513,9 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_ton_amount#one" = "{count} TON";
"lng_gift_ton_amount#other" = "{count} TON";
"lng_gift_premium_title" = "Premium Gift";
"lng_gift_premium_text#one" = "Subscribe to **Telegram Premium** to send up to **{count}** of these gifts and unlock access to multiple additional features.";
"lng_gift_premium_text#other" = "Subscribe to **Telegram Premium** to send up to **{count}** of these gifts and unlock access to multiple additional features.";
"lng_gift_premium_or_stars" = "Gift Premium or Stars";
"lng_gift_premium_subtitle" = "Gift Premium";
"lng_gift_premium_about" = "Give {name} access to exclusive features with Telegram Premium. {features}";
@@ -3559,6 +3567,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_gift_sent_resale_done_self" = "{gift} is now yours.";
"lng_gift_sent_about#one" = "You spent **{count}** Star from your balance.";
"lng_gift_sent_about#other" = "You spent **{count}** Stars from your balance.";
"lng_gift_sent_finished#one" = "You've already sent **{count}** of these gifts, and it's the limit.";
"lng_gift_sent_finished#other" = "You've already sent **{count}** of these gifts, and it's the limit.";
"lng_gift_sent_remains#one" = "You can send **{count}** more.";
"lng_gift_sent_remains#other" = "You can send **{count}** more.";
"lng_gift_limited_of_one" = "unique";
"lng_gift_limited_of_count" = "1 of {amount}";
"lng_gift_collectible_tag" = "gift";
+19 -1
View File
@@ -777,6 +777,19 @@ void ShowSentToast(
tr::now,
Text::RichLangValue);
}, [&](const GiftTypeStars &gift) {
if (gift.info.perUserTotal && gift.info.perUserRemains < 2) {
return tr::lng_gift_sent_finished(
tr::now,
lt_count,
gift.info.perUserTotal,
Text::RichLangValue);
} else if (gift.info.perUserTotal) {
return tr::lng_gift_sent_remains(
tr::now,
lt_count,
gift.info.perUserRemains - 1,
Text::RichLangValue);
}
const auto amount = gift.info.stars
+ (details.upgraded ? gift.info.starsToUpgrade : 0);
return tr::lng_gift_sent_about(
@@ -2749,7 +2762,12 @@ void SendGiftBox(
button->setClickedCallback([=] {
const auto star = std::get_if<GiftTypeStars>(&descriptor);
const auto unique = star ? star->info.unique : nullptr;
if (unique && star->mine && !peer->isSelf()) {
if (star
&& star->info.requirePremium
&& !peer->session().premium()) {
Settings::ShowPremiumGiftPremium(window, star->info);
return;
} else if (unique && star->mine && !peer->isSelf()) {
if (ShowTransferGiftLater(window->uiShow(), unique)) {
return;
}
@@ -421,6 +421,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
const auto onsale = (unique && unique->starsForResale && _small);
const auto requirePremium = v::is<GiftTypeStars>(_descriptor)
&& !v::get<GiftTypeStars>(_descriptor).userpic
&& !v::get<GiftTypeStars>(_descriptor).info.unique
&& v::get<GiftTypeStars>(_descriptor).info.requirePremium;
const auto hidden = v::is<GiftTypeStars>(_descriptor)
&& v::get<GiftTypeStars>(_descriptor).hidden;
@@ -539,7 +540,9 @@ void GiftButton::paintEvent(QPaintEvent *e) {
? tr::lng_gift_stars_resale(tr::now)
: soldOut
? tr::lng_gift_stars_sold_out(tr::now)
: (!data.userpic && data.info.requirePremium)
: (!data.userpic
&& !data.info.unique
&& data.info.requirePremium)
? tr::lng_gift_stars_premium(tr::now)
: (!data.userpic && !data.info.unique)
? tr::lng_gift_stars_limited(tr::now)
@@ -559,7 +562,7 @@ void GiftButton::paintEvent(QPaintEvent *e) {
? st::boxTextFgGood->c
: soldOut
? st::attentionButtonFg->c
: data.info.requirePremium
: (!data.userpic && data.info.requirePremium)
? st::creditsFg->c
: st::windowActiveTextFg->c),
.bg2 = (onsale
@@ -91,6 +91,8 @@ using SectionCustomTopBarData = Info::Settings::SectionCustomTopBarData;
option.duration = tr::lng_premium_subscribe_months_6(tr::now);
} else if (option.duration == tr::lng_years(tr::now, lt_count, 1)) {
option.duration = tr::lng_premium_subscribe_months_12(tr::now);
} else if (option.duration == tr::lng_years(tr::now, lt_count, 2)) {
option.duration = tr::lng_premium_subscribe_months_24(tr::now);
}
}
return result;
@@ -164,6 +166,45 @@ struct Data {
}
} // namespace EmojiStatus
namespace PremiumGift {
struct Data {
DocumentId documentId = 0;
int perUserTotal = 0;
explicit operator bool() const {
return documentId != 0;
}
};
[[nodiscard]] QString Serialize(const Data &gift) {
return u"premiumgift_:%1,peruser_:%2"_q
.arg(gift.documentId)
.arg(gift.perUserTotal);
}
[[nodiscard]] Data Parse(QStringView data) {
if (data.startsWith(u"premiumgift_:"_q)) {
const auto components = data.split(',');
if (components.size() != 2) {
return {};
}
const auto first = components[0].split(':');
const auto second = components[1].split(':');
if (first.size() != 2 || second.size() != 2) {
return {};
}
return {
.documentId = DocumentId(first[1].toULongLong()),
.perUserTotal = second[1].toInt(),
};
}
return {};
}
} // namespace PremiumGift
} // namespace Ref
struct Entry {
@@ -374,7 +415,6 @@ using Order = std::vector<QString>;
tr::lng_premium_summary_subtitle_business(),
tr::lng_premium_summary_about_business(),
PremiumFeature::Business,
true,
},
},
{
@@ -393,6 +433,7 @@ using Order = std::vector<QString>;
tr::lng_premium_summary_subtitle_todo_lists(),
tr::lng_premium_summary_about_todo_lists(),
PremiumFeature::TodoLists,
true,
},
},
};
@@ -542,13 +583,30 @@ void EmojiStatusTopBar::paint(QPainter &p) {
}
}
class TopBarUser final : public Ui::Premium::TopBarAbstract {
enum class TopBarWithStickerType {
EmojiStatus,
PremiumGift,
};
struct TopBarWithStickerArgs {
rpl::producer<DocumentData*> stickerValue;
rpl::producer<QString> nameValue;
rpl::producer<TextWithEntities> aboutValue;
TopBarWithStickerType type = TopBarWithStickerType::EmojiStatus;
};
class TopBarWithSticker final : public Ui::Premium::TopBarAbstract {
public:
TopBarUser(
TopBarWithSticker(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
rpl::producer<> showFinished);
TopBarWithSticker(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
TopBarWithStickerArgs args,
rpl::producer<> showFinished);
void setPaused(bool paused) override;
void setTextPosition(int x, int y) override;
@@ -562,10 +620,13 @@ protected:
private:
void updateTitle(
DocumentData *document,
TextWithEntities name,
const TextWithEntities &name,
not_null<Window::SessionController*> controller);
void updateAbout(DocumentData *document) const;
void updateAbout(
DocumentData *document,
const TextWithEntities &about) const;
TopBarWithStickerType _type = TopBarWithStickerType::EmojiStatus;
object_ptr<Ui::RpWidget> _content;
object_ptr<Ui::FlatLabel> _title;
object_ptr<Ui::FlatLabel> _about;
@@ -586,12 +647,35 @@ private:
};
TopBarUser::TopBarUser(
TopBarWithSticker::TopBarWithSticker(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer,
rpl::producer<> showFinished)
: TopBarWithSticker(parent, controller, {
.stickerValue = Info::Profile::EmojiStatusIdValue(
peer
) | rpl::map([=](EmojiStatusId id) -> DocumentData* {
const auto documentId = id.collectible
? id.collectible->documentId
: id.documentId;
const auto document = documentId
? controller->session().data().document(documentId).get()
: nullptr;
return (document && document->sticker()) ? document : nullptr;
}),
.nameValue = Info::Profile::NameValue(peer),
.type = TopBarWithStickerType::EmojiStatus,
}, std::move(showFinished)) {
}
TopBarWithSticker:: TopBarWithSticker(
not_null<QWidget*> parent,
not_null<Window::SessionController*> controller,
TopBarWithStickerArgs args,
rpl::producer<> showFinished)
: TopBarAbstract(parent, st::userPremiumCover)
, _type(args.type)
, _content(this)
, _title(_content, st::settingsPremiumUserTitle)
, _about(_content, st::userPremiumCover.about)
@@ -604,6 +688,10 @@ TopBarUser::TopBarUser(
}) {
_starRect = TopBarAbstract::starRect(1., 1.);
if (_type == TopBarWithStickerType::PremiumGift) {
_ministars.setColorOverride(Ui::Premium::CreditsIconGradientStops());
}
rpl::single() | rpl::then(
style::PaletteChanged()
) | rpl::start_with_next([=] {
@@ -611,24 +699,14 @@ TopBarUser::TopBarUser(
update();
}, lifetime());
auto documentValue = Info::Profile::EmojiStatusIdValue(
peer
) | rpl::map([=](EmojiStatusId id) -> DocumentData* {
const auto documentId = id.collectible
? id.collectible->documentId
: id.documentId;
const auto document = documentId
? controller->session().data().document(documentId).get()
: nullptr;
return (document && document->sticker()) ? document : nullptr;
});
rpl::combine(
std::move(documentValue),
Info::Profile::NameValue(peer)
std::move(args.stickerValue),
std::move(args.nameValue),
std::move(args.aboutValue)
) | rpl::start_with_next([=](
DocumentData *document,
const QString &name) {
const QString &name,
const TextWithEntities &about) {
if (document) {
_emojiStatus = std::make_unique<EmojiStatusTopBar>(
document,
@@ -641,7 +719,7 @@ TopBarUser::TopBarUser(
}
updateTitle(document, { name }, controller);
updateAbout(document);
updateAbout(document, about);
auto event = QResizeEvent(size(), size());
resizeEvent(&event);
@@ -746,14 +824,15 @@ TopBarUser::TopBarUser(
p.drawImage(_starRect.topLeft(), _imageStar);
}
}, lifetime());
}
void TopBarUser::updateTitle(
void TopBarWithSticker::updateTitle(
DocumentData *document,
TextWithEntities name,
const TextWithEntities &name,
not_null<Window::SessionController*> controller) {
if (!document) {
if (_type == TopBarWithStickerType::PremiumGift) {
return _title->setMarkedText(name);
} else if (!document) {
return _title->setMarkedText(
tr::lng_premium_summary_user_title(
tr::now,
@@ -816,36 +895,40 @@ void TopBarUser::updateTitle(
_title->setLink(linkIndex, std::move(link));
}
void TopBarUser::updateAbout(DocumentData *document) const {
_about->setMarkedText((document
? tr::lng_premium_emoji_status_about
: tr::lng_premium_summary_user_about)(
tr::now,
Ui::Text::RichLangValue));
void TopBarWithSticker::updateAbout(
DocumentData *document,
const TextWithEntities &about) const {
_about->setMarkedText((_type == TopBarWithStickerType::PremiumGift)
? about
: (document
? tr::lng_premium_emoji_status_about
: tr::lng_premium_summary_user_about)(
tr::now,
Ui::Text::RichLangValue));
}
void TopBarUser::setPaused(bool paused) {
void TopBarWithSticker::setPaused(bool paused) {
_ministars.setPaused(paused);
if (_emojiStatus) {
_emojiStatus->setPaused(paused);
}
}
void TopBarUser::setTextPosition(int x, int y) {
void TopBarWithSticker::setTextPosition(int x, int y) {
_smallTop.position = { x, y };
}
rpl::producer<int> TopBarUser::additionalHeight() const {
rpl::producer<int> TopBarWithSticker::additionalHeight() const {
return rpl::never<int>();
}
void TopBarUser::paintEvent(QPaintEvent *e) {
void TopBarWithSticker::paintEvent(QPaintEvent *e) {
auto p = QPainter(this);
TopBarAbstract::paintEdges(p);
}
void TopBarUser::resizeEvent(QResizeEvent *e) {
void TopBarWithSticker::resizeEvent(QResizeEvent *e) {
_starRect = TopBarAbstract::starRect(1., 1.);
_ministars.setCenter(_starRect.toRect());
@@ -1053,7 +1136,9 @@ base::weak_qptr<Ui::RpWidget> Premium::createPinnedToTop(
}();
const auto emojiStatusData = Ref::EmojiStatus::Parse(_ref);
const auto premiumGiftData = Ref::PremiumGift::Parse(_ref);
const auto isEmojiStatus = (!!emojiStatusData);
const auto isPremiumGift = (!!premiumGiftData);
auto peerWithPremium = [&]() -> PeerData* {
if (isEmojiStatus) {
@@ -1064,14 +1149,35 @@ base::weak_qptr<Ui::RpWidget> Premium::createPinnedToTop(
}
return nullptr;
}();
auto premiumGift = [&]() -> DocumentData* {
if (isPremiumGift) {
auto &data = _controller->session().data();
return data.document(premiumGiftData.documentId);
}
return nullptr;
}();
const auto content = [&]() -> Ui::Premium::TopBarAbstract* {
if (peerWithPremium) {
return Ui::CreateChild<TopBarUser>(
return Ui::CreateChild<TopBarWithSticker>(
parent.get(),
_controller,
peerWithPremium,
_showFinished.events());
} else if (premiumGift) {
return Ui::CreateChild<TopBarWithSticker>(
parent.get(),
_controller,
TopBarWithStickerArgs{
.stickerValue = rpl::single(premiumGift),
.nameValue = tr::lng_gift_premium_title(),
.aboutValue = tr::lng_gift_premium_text(
lt_count,
rpl::single(premiumGiftData.perUserTotal * 1.),
Ui::Text::RichLangValue),
.type = TopBarWithStickerType::PremiumGift,
},
_showFinished.events());
}
const auto weak = base::make_weak(_controller);
const auto clickContextOther = [=] {
@@ -1102,7 +1208,7 @@ base::weak_qptr<Ui::RpWidget> Premium::createPinnedToTop(
}, content->lifetime());
const auto calculateMaximumHeight = [=] {
return isEmojiStatus
return (isEmojiStatus || isPremiumGift)
? st::settingsPremiumUserHeight + TopTransitionSkip()
: st::settingsPremiumTopHeight;
};
@@ -1352,6 +1458,15 @@ void ShowEmojiStatusPremium(
}
}
void ShowPremiumGiftPremium(
not_null<Window::SessionController*> controller,
const Data::StarGift &gift) {
ShowPremium(controller, Ref::PremiumGift::Serialize({
.documentId = gift.document->id,
.perUserTotal = gift.perUserTotal,
}));
}
void StartPremiumPayment(
not_null<Window::SessionController*> controller,
const QString &ref) {
@@ -20,6 +20,10 @@ namespace ChatHelpers {
class Show;
} // namespace ChatHelpers
namespace Data {
struct StarGift;
} // namespace Data
namespace Ui {
class RpWidget;
class RoundButton;
@@ -52,6 +56,9 @@ void ShowGiftPremium(
void ShowEmojiStatusPremium(
not_null<Window::SessionController*> controller,
not_null<PeerData*> peer);
void ShowPremiumGiftPremium(
not_null<Window::SessionController*> controller,
const Data::StarGift &gift);
void StartPremiumPayment(
not_null<Window::SessionController*> controller,
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/controls/stars_rating.h"
#include "lang/lang_keys.h"
#include "ui/text/text_utilities.h"
#include "ui/toast/toast.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
@@ -42,7 +43,8 @@ StarsRating::StarsRating(
StarsRating::~StarsRating() = default;
void StarsRating::init() {
_expanded.value() | rpl::start_with_next([=](bool expanded) {
_widget->setPointerCursor(true);
_expanded.changes() | rpl::start_with_next([=](bool expanded) {
_widget->setPointerCursor(!expanded);
const auto from = expanded ? 0. : 1.;
const auto till = expanded ? 1. : 0.;
@@ -168,15 +170,15 @@ void StarsRating::toggleTooltips(bool shown) {
stars->toggleAnimated(true);
_aboutSt = std::make_unique<style::Toast>(st::defaultMultilineToast);
const auto learn = u"Learn More"_q;
const auto learn = tr::lng_stars_rating_learn_more(tr::now);
_aboutSt->padding.setRight(
(st::infoStarsRatingLearn.style.font->width(learn)
- st::infoStarsRatingLearn.width));
_about = Ui::Toast::Show(parent, {
.text = TextWithEntities{
u"Profile level reflects the user's payment reliability."_q,
},
.text = tr::lng_stars_rating_tooltip(
tr::now,
Ui::Text::WithEntities),
.st = _aboutSt.get(),
.attach = RectPart::Top,
.dark = true,
@@ -211,7 +213,7 @@ void StarsRating::toggleTooltips(bool shown) {
outer.width());
}, widget->lifetime());
button->setClickedCallback([=] {
UrlClickHandler::Open(u"https://telegram.org/"_q);
UrlClickHandler::Open(tr::lng_stars_rating_about_url(tr::now));
});
}
@@ -283,16 +285,18 @@ void StarsRating::paint(QPainter &p) {
? ((value.currentStars - value.levelStars)
/ float64(value.nextLevelStars - value.levelStars))
: 1.;
const auto expandedFilled = _st.padding.left()
+ _expandedText.maxWidth()
+ _st.padding.right()
+ expandedRatio * (middle.width()
- _st.padding.left()
- _expandedText.maxWidth()
- _st.padding.right()
- _st.padding.left()
- _nextText.maxWidth()
- _st.padding.right());
const auto expandedFilled = (expandedRatio < 1.)
? (_st.padding.left()
+ _expandedText.maxWidth()
+ _st.padding.right()
+ expandedRatio * (middle.width()
- _st.padding.left()
- _expandedText.maxWidth()
- _st.padding.right()
- _st.padding.left()
- _nextText.maxWidth()
- _st.padding.right()))
: middle.width();
const auto collapsedFilled = _collapsedWidthValue.current()
- _st.margin.right()
- 2 * _st.border;