From 9f5d24bbc91ca207c0b01fb59762f67c4698f027 Mon Sep 17 00:00:00 2001 From: John Preston Date: Wed, 6 Aug 2025 09:30:36 +0400 Subject: [PATCH] Start gift value information display. --- Telegram/Resources/langs/lang.strings | 17 ++ Telegram/SourceFiles/api/api_premium.cpp | 9 + .../SourceFiles/boxes/gift_premium_box.cpp | 230 +++++++++++++++++- Telegram/SourceFiles/boxes/gift_premium_box.h | 6 + Telegram/SourceFiles/data/data_credits.h | 1 + Telegram/SourceFiles/data/data_star_gift.h | 17 ++ .../dialogs/ui/posts_search_intro.cpp | 2 +- .../settings/settings_credits_graphics.cpp | 148 ++++++++++- .../settings/settings_credits_graphics.h | 5 + .../ui/boxes/edit_birthday_box.cpp | 2 +- Telegram/SourceFiles/ui/effects/credits.style | 12 + 11 files changed, 429 insertions(+), 20 deletions(-) diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 2374510777..ade5b22f81 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3675,6 +3675,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_unique_availability_label" = "Quantity"; "lng_gift_unique_availability#one" = "{count} of {amount} issued"; "lng_gift_unique_availability#other" = "{count} of {amount} issued"; +"lng_gift_unique_value" = "Value"; +"lng_gift_unique_value_learn_more" = "learn more"; "lng_gift_unique_info" = "Gifted to {recipient} on {date}."; "lng_gift_unique_info_sender" = "Gifted by {from} to {recipient} on {date}."; "lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\"."; @@ -3683,6 +3685,21 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_availability_left#one" = "{count} of {amount} left"; "lng_gift_availability_left#other" = "{count} of {amount} left"; "lng_gift_availability_none" = "None of {amount} left"; +"lng_gift_value_about_average" = "This is the average sale price of {gift} gifts on Telegram and Fragment over the past month."; +"lng_gift_value_about_last" = "This is the price at which {gift} was last sold on {platform}."; +"lng_gift_value_initial_sale" = "Initial Sale"; +"lng_gift_value_initial_price" = "Initial Price"; +"lng_gift_value_initial_price_value" = "{stars} ({amount})"; +"lng_gift_value_last_sale" = "Last Sale"; +"lng_gift_value_last_price" = "Last Price"; +"lng_gift_value_minimum_price" = "Minimum Price"; +"lng_gift_value_minimum_price_tooltip" = "{amount} is the floor price for {gift} gifts listed on Telegram and Fragment."; +"lng_gift_vlaue_average_price" = "Average Price"; +"lng_gift_value_average_price_tooltip" = "{amount} is the average sale price of {gift} gifts on Telegram and Fragment over the past month."; +"lng_gift_value_availability#one" = "{count} {emoji} for sale on {platform} {arrow}"; +"lng_gift_value_availability#other" = "{count} {emoji} for sale on {platform} {arrow}"; +"lng_gift_value_telegram" = "Telegram"; +"lng_gift_value_fragment" = "Fragment"; "lng_gift_convert_to_stars#one" = "Convert to {count} Star"; "lng_gift_convert_to_stars#other" = "Convert to {count} Stars"; "lng_gift_convert_sure_title" = "Convert Gift to Stars"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 309f20261c..49109afebe 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -911,6 +911,15 @@ std::optional FromTL( .onlyAcceptTon = data.is_resale_ton_only(), .model = *model, .pattern = *pattern, + .value = (data.vvalue_amount() + ? std::make_shared( + Data::UniqueGiftValue{ + .currency = qs( + data.vvalue_currency().value_or_empty()), + .valuePrice = int64( + data.vvalue_amount().value_or_empty()), + }) + : nullptr), }), .document = model->document, .releasedBy = releasedBy, diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index f4567e7961..71ee3719c2 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "data/data_user.h" #include "data/stickers/data_custom_emoji.h" #include "info/channel_statistics/boosts/giveaway/boost_badge.h" // InfiniteRadialAnimationWidget. +#include "info/channel_statistics/earn/earn_icons.h" #include "info/profile/info_profile_badge.h" #include "info/profile/info_profile_values.h" #include "lang/lang_keys.h" @@ -54,6 +55,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/rect.h" #include "ui/ui_utility.h" #include "ui/vertical_list.h" +#include "ui/text/custom_emoji_helper.h" +#include "ui/text/format_values.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" @@ -110,6 +113,33 @@ constexpr auto kHorizontalBar = QChar(0x2015); }; } +[[nodiscard]] TextWithEntities FormatValuePrice( + int64 price, + QString currency, + bool approximately = false) { + auto result = TextWithEntities(); + if (approximately) { + result.append('~'); + } + return result.append(Ui::FillAmountAndCurrency(price, currency)); +} + +[[nodiscard]] TextWithEntities FormatValueDate(TimeId date) { + const auto parsed = base::unixtime::parse(date).date(); + const auto day = parsed.day(); + const auto month = parsed.month(); + const auto year = parsed.year(); + return { tr::lng_month_day_year( + tr::now, + lt_month, + Lang::MonthDay(month)(tr::now), + lt_day, + QString::number(day), + lt_year, + QString::number(year)) + }; +} + [[nodiscard]] object_ptr MakeLinkCopyIcon( not_null parent) { auto result = object_ptr(parent); @@ -465,21 +495,75 @@ void AddTableRow( return result; } -[[nodiscard]] object_ptr MakeNonUniqueStatusTableValue( - not_null table) { +[[nodiscard]] object_ptr MakeUniqueGiftValueValue( + not_null table, + std::shared_ptr show, + const Data::CreditsHistoryEntry &entry, + Settings::CreditsEntryBoxStyleOverrides st) { auto result = object_ptr(table); const auto raw = result.data(); + const auto unique = entry.uniqueGift; + const auto value = unique ? unique->value : nullptr; + const auto loading = std::make_shared(false); + const auto label = Ui::CreateChild( raw, - tr::lng_gift_unique_status_non(), + rpl::single( + FormatValuePrice(value->valuePrice, value->currency, true)), table->st().defaultValue, st::defaultPopupMenu); - raw->widthValue( - ) | rpl::start_with_next([=](int width) { - label->resizeToNaturalWidth(width); + const auto learn = Ui::CreateChild( + raw, + tr::lng_gift_unique_value_learn_more(), + table->st().smallButton); + learn->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + learn->setClickedCallback([=] { + if (value->initialPriceStars) { + show->show(Box(Settings::UniqueGiftValueBox, show, entry, st)); + } else if (*loading) { + return; + } + *loading = true; + show->session().api().request(MTPpayments_GetUniqueStarGiftValueInfo( + MTP_string(unique->slug) + )).done([=](const MTPpayments_UniqueStarGiftValueInfo &result) { + *loading = false; + + const auto &data = result.data(); + value->currency = qs(data.vcurrency()); + value->valuePrice = data.vvalue().v; + value->initialSaleDate = data.vinitial_sale_date().v; + value->initialPriceStars = CreditsAmount( + data.vinitial_sale_stars().v); + value->initialSalePrice = data.vinitial_sale_price().v; + value->lastSaleDate = data.vlast_sale_date().value_or_empty(); + value->lastSalePrice = data.vlast_sale_price().value_or_empty(); + value->lastSaleFragment = data.is_last_sale_on_fragment(); + value->minimumPrice = data.vfloor_price().value_or_empty(); + value->averagePrice = data.vaverage_price().value_or_empty(); + value->forSaleOnTelegram = data.vlisted_count().value_or_empty(); + value->forSaleOnFragment = int( + data.vfragment_listed_count().value_or_empty()); + value->fragmentUrl = qs( + data.vfragment_listed_url().value_or_empty()); + + show->show(Box(Settings::UniqueGiftValueBox, show, entry, st)); + }).send(); + }); + rpl::combine( + raw->widthValue(), + learn->widthValue() + ) | rpl::start_with_next([=](int width, int learnWidth) { + const auto learnSkip = st::normalFont->spacew + learnWidth; + label->resizeToNaturalWidth(width - learnSkip); label->moveToLeft(0, 0, width); + learn->moveToLeft( + rect::right(label) + st::normalFont->spacew, + (table->st().defaultValue.style.font->ascent + - table->st().smallButton.style.font->ascent), + width); }, label->lifetime()); label->heightValue() | rpl::start_with_next([=](int height) { @@ -1484,10 +1568,16 @@ void AddStarGiftTable( AddTableRow( table, tr::lng_gift_unique_status(), - MakeNonUniqueStatusTableValue(table), - marginWithButton); + tr::lng_gift_unique_status_non(Ui::Text::WithEntities)); } if (unique) { + if (unique->value) { + AddTableRow( + table, + tr::lng_gift_unique_value(), + MakeUniqueGiftValueValue(table, show, entry, st), + marginWithButton); + } const auto &original = unique->originalDetails; if (original.recipientId) { const auto owner = &show->session().data(); @@ -1973,3 +2063,127 @@ void AddChannelEarnTable( st::giveawayGiftCodeValueMargin); } } + +void AddUniqueGiftValueTable( + std::shared_ptr show, + not_null container, + Settings::CreditsEntryBoxStyleOverrides st, + const Data::CreditsHistoryEntry &entry) { + const auto value = entry.uniqueGift ? entry.uniqueGift->value : nullptr; + auto table = container->add( + object_ptr( + container, + st.table ? *st.table : st::giveawayGiftCodeTable), + st::giveawayGiftCodeTableMargin); + const auto raw = std::make_shared(nullptr); + const auto showTooltip = [=]( + not_null widget, + rpl::producer text) { + if (*raw) { + (*raw)->toggleAnimated(false); + } + const auto tooltip = Ui::CreateChild( + container, + Ui::MakeNiceTooltipLabel( + container, + std::move(text), + st::boxWideWidth, + st::defaultImportantTooltipLabel), + st::defaultImportantTooltip); + tooltip->toggleFast(false); + + const auto update = [=] { + const auto geometry = Ui::MapFrom( + container, + widget, + widget->rect()); + const auto countPosition = [=](QSize size) { + const auto left = geometry.x() + + (geometry.width() - size.width()) / 2; + const auto right = container->width() + - st::normalFont->spacew; + return QPoint( + std::max(std::min(left, right - size.width()), 0), + geometry.y() - size.height() - st::normalFont->descent); + }; + tooltip->pointAt(geometry, RectPart::Top, countPosition); + }; + container->widthValue( + ) | rpl::start_with_next(update, tooltip->lifetime()); + + update(); + tooltip->toggleAnimated(true); + + *raw = tooltip; + tooltip->shownValue() | rpl::filter( + !rpl::mappers::_1 + ) | rpl::start_with_next([=] { + crl::on_main(tooltip, [=] { + if (tooltip->isHidden()) { + if (*raw == tooltip) { + *raw = nullptr; + } + delete tooltip; + } + }); + }, tooltip->lifetime()); + + base::timer_once( + kRarityTooltipDuration + ) | rpl::start_with_next([=] { + tooltip->toggleAnimated(false); + }, tooltip->lifetime()); + }; + + if (value->initialSaleDate) { + AddTableRow( + table, + tr::lng_gift_value_initial_sale(), + rpl::single(FormatValueDate(value->initialSaleDate))); + } + auto helper = Ui::Text::CustomEmojiHelper(); + auto starIcon = helper.paletteDependent( + Ui::Earn::IconCreditsEmoji()); + AddTableRow( + table, + tr::lng_gift_value_initial_price(), + tr::lng_gift_value_initial_price_value( + lt_stars, + rpl::single(starIcon.append( + Lang::FormatCreditsAmountDecimal(value->initialPriceStars) + )), + lt_amount, + rpl::single(FormatValuePrice( + value->initialSalePrice, + value->currency, + true)), + Ui::Text::WithEntities), + helper.context()); + if (value->lastSaleDate) { + AddTableRow( + table, + tr::lng_gift_value_last_sale(), + rpl::single(FormatValueDate(value->lastSaleDate))); + } + if (value->lastSalePrice) { + AddTableRow( + table, + tr::lng_gift_value_last_price(), + rpl::single( + FormatValuePrice(value->lastSalePrice, value->currency))); + } + if (value->minimumPrice) { + AddTableRow( + table, + tr::lng_gift_value_minimum_price(), + rpl::single( + FormatValuePrice(value->minimumPrice, value->currency))); + } + if (value->averagePrice) { + AddTableRow( + table, + tr::lng_gift_vlaue_average_price(), + rpl::single( + FormatValuePrice(value->averagePrice, value->currency))); + } +} diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.h b/Telegram/SourceFiles/boxes/gift_premium_box.h index df0c17c1c1..a46bf92c64 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.h +++ b/Telegram/SourceFiles/boxes/gift_premium_box.h @@ -106,3 +106,9 @@ void AddChannelEarnTable( std::shared_ptr show, not_null container, const Data::CreditsHistoryEntry &entry); + +void AddUniqueGiftValueTable( + std::shared_ptr show, + not_null container, + Settings::CreditsEntryBoxStyleOverrides st, + const Data::CreditsHistoryEntry &entry); diff --git a/Telegram/SourceFiles/data/data_credits.h b/Telegram/SourceFiles/data/data_credits.h index 45d1f9d9bd..1cf1670408 100644 --- a/Telegram/SourceFiles/data/data_credits.h +++ b/Telegram/SourceFiles/data/data_credits.h @@ -12,6 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct UniqueGift; +struct UniqueGiftValue; struct CreditTopupOption final { uint64 credits = 0; diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index becf3777c6..bf6b02bcb8 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -37,6 +37,22 @@ struct UniqueGiftOriginalDetails { TextWithEntities message; }; +struct UniqueGiftValue { + QString currency; + int64 valuePrice = 0; + CreditsAmount initialPriceStars; + int64 initialSalePrice = 0; + TimeId initialSaleDate = 0; + int64 lastSalePrice = 0; + TimeId lastSaleDate = 0; + int64 averagePrice = 0; + int64 minimumPrice = 0; + int forSaleOnTelegram = 0; + int forSaleOnFragment = 0; + QString fragmentUrl; + bool lastSaleFragment = false; +}; + struct UniqueGift { CollectibleId id = 0; QString slug; @@ -57,6 +73,7 @@ struct UniqueGift { UniqueGiftPattern pattern; UniqueGiftBackdrop backdrop; UniqueGiftOriginalDetails originalDetails; + std::shared_ptr value; }; [[nodiscard]] QString UniqueGiftName(const UniqueGift &gift); diff --git a/Telegram/SourceFiles/dialogs/ui/posts_search_intro.cpp b/Telegram/SourceFiles/dialogs/ui/posts_search_intro.cpp index 3ad1f3662e..87bda8fe71 100644 --- a/Telegram/SourceFiles/dialogs/ui/posts_search_intro.cpp +++ b/Telegram/SourceFiles/dialogs/ui/posts_search_intro.cpp @@ -199,7 +199,7 @@ void PostsSearchIntro::setup() { _state.value( ) | rpl::start_with_next([=](const PostsSearchIntroState &state) { - if (state.query.trimmed().isEmpty()) { + if (state.query.trimmed().isEmpty() && !state.needsPremium) { _button->resize(_button->width(), 0); _content->resizeToWidth(width()); return; diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index e1845fc39f..cb71bf5a10 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -2087,9 +2087,6 @@ void GenericCreditsEntryBox( const auto toRejoin = (s.cancelled || s.expired) && rejoinBySlug && !s.cancelledByBot; - //const auto suggestUpgradeNext = uniqueGift - // && canToggle - // && e.savedToProfile; auto confirmText = rpl::conditional( state->confirmButtonBusy.value(), rpl::single(QString()), @@ -2236,14 +2233,145 @@ void GenericCreditsEntryBox( AddChildToWidgetCenter(button, loadingAnimation); loadingAnimation->showOn(state->confirmButtonBusy.value()); } - const auto buttonWidth = st::boxWideWidth - - rect::m::sum::h(st::giveawayGiftCodeBox.buttonPadding); +} - button->widthValue() | rpl::filter([=] { - return (button->widthNoMargins() != buttonWidth); - }) | rpl::start_with_next([=] { - button->resizeToWidth(buttonWidth); - }, button->lifetime()); +void UniqueGiftValueBox( + not_null box, + std::shared_ptr show, + const Data::CreditsHistoryEntry &e, + CreditsEntryBoxStyleOverrides st) { + box->setStyle(st.box ? *st.box : st::giveawayGiftCodeBox); + box->setWidth(st::boxWideWidth); + box->setNoContentMargin(true); + + const auto unique = e.uniqueGift; + const auto value = unique ? unique->value : nullptr; + Assert(unique && value); + + const auto showLastPrice = (value->lastSalePrice > value->averagePrice); + + const auto content = box->verticalLayout(); + + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + + struct State final { + DocumentData *sticker = nullptr; + std::shared_ptr media; + std::unique_ptr lottie; + rpl::lifetime downloadLifetime; + }; + Ui::AddSkip(content, st::creditsHistoryEntryStarGiftSpace); + + const auto icon = Ui::CreateChild(content); + icon->resize(Size(st::creditsHistoryEntryStarGiftSize)); + const auto state = icon->lifetime().make_state(); + const auto document = unique->model.document; + if (document && document->sticker()) { + const auto origin = document->stickerOrGifOrigin(); + state->sticker = document; + state->media = document->createMediaView(); + state->media->thumbnailWanted(origin); + state->media->automaticLoad(origin, nullptr); + rpl::single() | rpl::then( + document->session().downloaderTaskFinished() + ) | rpl::filter([=] { + return state->media->loaded(); + }) | rpl::start_with_next([=] { + state->lottie = ChatHelpers::LottiePlayerFromDocument( + state->media.get(), + ChatHelpers::StickerLottieSize::MessageHistory, + icon->size(), + Lottie::Quality::High); + state->lottie->updates() | rpl::start_with_next([=] { + icon->update(); + }, icon->lifetime()); + state->downloadLifetime.destroy(); + }, state->downloadLifetime); + } + icon->paintRequest( + ) | rpl::start_with_next([=] { + auto p = Painter(icon); + const auto &lottie = state->lottie; + const auto factor = style::DevicePixelRatio(); + const auto request = Lottie::FrameRequest{ + .box = icon->size() * factor, + }; + const auto frame = (lottie && lottie->ready()) + ? lottie->frameInfo(request) + : Lottie::Animation::FrameInfo(); + if (!frame.image.isNull()) { + p.drawImage( + QRect(QPoint(), frame.image.size() / factor), + frame.image); + if (lottie->frameIndex() < lottie->framesCount() - 1) { + lottie->markFrameShown(); + } + } + }, icon->lifetime()); + content->sizeValue( + ) | rpl::start_with_next([=](const QSize &size) { + icon->move( + (size.width() - icon->width()) / 2, + st::creditsHistoryEntryStarGiftSkip); + }, icon->lifetime()); + + Ui::AddSkip(content); + Ui::AddSkip(content); + + const auto bubble = box->addRow( + object_ptr( + box, + rpl::single( + Ui::FillAmountAndCurrency(value->valuePrice, value->currency)), + st::uniqueGiftValuePrice), + style::al_top); + bubble->paintRequest() | rpl::start_with_next([=] { + auto p = QPainter(bubble); + auto hq = PainterHighQualityEnabler(p); + p.setBrush(st::windowBgActive); + p.setPen(Qt::NoPen); + const auto rect = bubble->rect(); + const auto radius = std::min(rect.width(), rect.height()) / 2.; + p.drawRoundedRect(rect, radius, radius); + }, bubble->lifetime()); + + Ui::AddSkip(content); + + const auto arrow = Ui::Text::IconEmoji(&st::textMoreIconEmoji); + Ui::AddSkip(content); + box->addRow( + object_ptr( + box, + (showLastPrice + ? tr::lng_gift_value_about_last( + lt_gift, + rpl::single(Ui::Text::Bold( + Data::UniqueGiftName(*unique))), + lt_platform, + (value->lastSaleFragment + ? tr::lng_gift_value_fragment + : tr::lng_gift_value_telegram)( + Ui::Text::WithEntities), + Ui::Text::RichLangValue) + : tr::lng_gift_value_about_average( + lt_gift, + rpl::single(Ui::Text::Bold(unique->title)), + Ui::Text::RichLangValue)), + st::uniqueGiftValueAbout) + )->setTryMakeSimilarLines(true); + + Ui::AddSkip(content); + Ui::AddSkip(content); + + AddUniqueGiftValueTable(show, content, st, e); + + Ui::AddSkip(content); + + box->addButton(tr::lng_box_ok(), [=] { + box->closeBox(); + }); } void ReceiptCreditsBox( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.h b/Telegram/SourceFiles/settings/settings_credits_graphics.h index 46c408c5af..db7cdbe066 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.h +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.h @@ -135,6 +135,11 @@ void GenericCreditsEntryBox( const Data::CreditsHistoryEntry &e, const Data::SubscriptionEntry &s, CreditsEntryBoxStyleOverrides st = {}); +void UniqueGiftValueBox( + not_null box, + std::shared_ptr show, + const Data::CreditsHistoryEntry &e, + CreditsEntryBoxStyleOverrides st = {}); void ReceiptCreditsBox( not_null box, not_null controller, diff --git a/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp b/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp index 94d63e6fe6..e217bb1a19 100644 --- a/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp +++ b/Telegram/SourceFiles/ui/boxes/edit_birthday_box.cpp @@ -161,7 +161,7 @@ void EditBirthdayBox( const auto daysCount = (year == maxYear && month == max.month()) ? max.day() : (month == 2) - ? ((!year || ((year % 4) && (!(year % 100) || (year % 400)))) + ? ((!year || (!(year % 4) && ((year % 100) || !(year % 400)))) ? 29 : 28) : ((month == 4) || (month == 6) || (month == 9) || (month == 11)) diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 1dcc081231..3dc7336307 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -359,3 +359,15 @@ resaleConfirmTonOnly: FlatLabel(boxLabel) { textFg: windowSubTextFg; } resaleConfirmTonOnlyMargin: margins(0px, 12px, 0px, 12px); + +uniqueGiftValuePrice: FlatLabel(defaultFlatLabel) { + style: TextStyle(defaultTextStyle) { + font: font(20px semibold); + } + margin: margins(12px, 6px, 12px, 6px); + textFg: windowFgActive; +} +uniqueGiftValueAbout: FlatLabel(defaultFlatLabel) { + minWidth: 128px; + align: align(top); +}