diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 49109afebe..35894d3e6a 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -894,9 +894,10 @@ std::optional FromTL( ? session->data().peer(releasedById).get() : nullptr; auto result = Data::StarGift{ - .id = uint64(data.vid().v), + .id = data.vid().v, .unique = std::make_shared(Data::UniqueGift{ .id = data.vid().v, + .initialGiftId = data.vgift_id().v, .slug = qs(data.vslug()), .title = qs(data.vtitle()), .ownerAddress = qs(data.vowner_address().value_or_empty()), diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index 53dbe0bddb..af7077662a 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -78,9 +78,74 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { -constexpr auto kRarityTooltipDuration = 3 * crl::time(1000); +constexpr auto kTooltipDuration = 3 * crl::time(1000); +constexpr auto kPriceTooltipDuration = 6 * crl::time(1000); constexpr auto kHorizontalBar = QChar(0x2015); +struct InfoTooltipData { + not_null parent; + Ui::ImportantTooltip *raw = nullptr; +}; + +void ShowInfoTooltip( + std::shared_ptr data, + not_null target, + rpl::producer text, + int duration) { + if (data->raw) { + data->raw->toggleAnimated(false); + } + const auto parent = data->parent; + const auto tooltip = Ui::CreateChild( + parent, + Ui::MakeNiceTooltipLabel( + parent, + std::move(text), + st::boxWideWidth, + st::defaultImportantTooltipLabel), + st::defaultImportantTooltip); + tooltip->toggleFast(false); + + const auto update = [=] { + const auto geometry = Ui::MapFrom(parent, target, target->rect()); + const auto countPosition = [=](QSize size) { + const auto left = geometry.x() + + (geometry.width() - size.width()) / 2; + const auto right = parent->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); + }; + parent->widthValue( + ) | rpl::start_with_next(update, tooltip->lifetime()); + + update(); + tooltip->toggleAnimated(true); + + data->raw = tooltip; + tooltip->shownValue() | rpl::filter( + !rpl::mappers::_1 + ) | rpl::start_with_next([=] { + crl::on_main(tooltip, [=] { + if (tooltip->isHidden()) { + if (data->raw == tooltip) { + data->raw = nullptr; + } + delete tooltip; + } + }); + }, tooltip->lifetime()); + + base::timer_once( + duration + ) | rpl::start_with_next([=] { + tooltip->toggleAnimated(false); + }, tooltip->lifetime()); +} + [[nodiscard]] QString CreateMessageLink( not_null session, PeerId peerId, @@ -190,9 +255,16 @@ constexpr auto kHorizontalBar = QChar(0x2015); not_null table, not_null value, rpl::producer buttonText, - Fn button)> handler, + Fn button)> handler = nullptr, int topSkip = 0) { - auto result = object_ptr(table); + class MarginedWidget final : public Ui::RpWidget { + public: + using RpWidget::RpWidget; + QMargins getMargins() const override { + return { 0, 0, 0, st::giveawayGiftCodePeerMargin.bottom() }; + } + }; + auto result = object_ptr(table); const auto raw = result.data(); value->setParent(raw); @@ -203,9 +275,13 @@ constexpr auto kHorizontalBar = QChar(0x2015); std::move(buttonText), table->st().smallButton); button->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); - button->setClickedCallback([button, handler = std::move(handler)] { - handler(button); - }); + if (handler) { + button->setClickedCallback([button, handler = std::move(handler)] { + handler(button); + }); + } else { + button->setAttribute(Qt::WA_TransparentForMouseEvents); + } rpl::combine( raw->widthValue(), button->widthValue(), @@ -223,7 +299,8 @@ constexpr auto kHorizontalBar = QChar(0x2015); }, value->lifetime()); value->heightValue() | rpl::start_with_next([=](int height) { - raw->resize(raw->width(), height); + const auto bottom = st::giveawayGiftCodePeerMargin.bottom(); + raw->resize(raw->width(), height + bottom); }, raw->lifetime()); return result; @@ -385,7 +462,7 @@ void AddTableRow( not_null table, rpl::producer label, object_ptr value, - style::margins valueMargins) { + style::margins valueMargins = st::giveawayGiftCodeValueMargin) { table->addRow( (label ? object_ptr( @@ -398,6 +475,91 @@ void AddTableRow( valueMargins); } +[[nodiscard]] object_ptr MakePriceWithChangePercentValue( + not_null table, + const std::shared_ptr &value) { + auto label = object_ptr( + table, + rpl::single(FormatValuePrice(value->lastSalePrice, value->currency)), + table->st().defaultValue); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto initial = value->initialSalePrice; + if (!initial) { + return label; + } + + const auto diff = (100 * (value->lastSalePrice - initial)) + / float64(initial); + const auto use = (std::abs(diff) >= 10.) + ? base::SafeRound(diff) + : (int(base::SafeRound(diff * 100)) / 100.); + const auto prefix = (use > 0) ? u"+"_q : QString(); + const auto percent = Lang::FormatExactCountDecimal(use) + '%'; + auto text = rpl::single(prefix + percent); + return MakeValueWithSmallButton(table, label, std::move(text)); +} + +[[nodiscard]] object_ptr MakePriceValueWithTooltip( + not_null table, + std::shared_ptr data, + TextWithEntities price, + TextWithEntities tooltip) { + const auto label = Ui::CreateChild( + table, + rpl::single(price), + table->st().defaultValue); + label->setAttribute(Qt::WA_TransparentForMouseEvents); + + const auto handler = [=](not_null button) { + ShowInfoTooltip( + data, + button, + rpl::single(tooltip), + kPriceTooltipDuration); + }; + auto text = rpl::single(u"?"_q); + return MakeValueWithSmallButton(table, label, std::move(text), handler); +} + +[[nodiscard]] object_ptr MakeMinimumPriceValue( + not_null table, + std::shared_ptr tooltip, + const std::shared_ptr &unique) { + const auto &value = unique->value; + const auto text = FormatValuePrice(value->minimumPrice, value->currency); + return MakePriceValueWithTooltip( + table, + std::move(tooltip), + text, + tr::lng_gift_value_minimum_price_tooltip( + tr::now, + lt_amount, + Ui::Text::Bold(text.text), + lt_gift, + Ui::Text::Bold(unique->title), + Ui::Text::WithEntities)); +} + +[[nodiscard]] object_ptr MakeAveragePriceValue( + not_null table, + std::shared_ptr tooltip, + const std::shared_ptr &unique) { + const auto &value = unique->value; + const auto text = FormatValuePrice(value->averagePrice, value->currency); + return MakePriceValueWithTooltip( + table, + std::move(tooltip), + text, + tr::lng_gift_value_average_price_tooltip( + tr::now, + lt_amount, + Ui::Text::Bold(text.text), + lt_gift, + Ui::Text::Bold(unique->title), + Ui::Text::WithEntities)); +} + [[nodiscard]] object_ptr MakeAttributeValue( not_null table, const Data::UniqueGiftAttribute &attribute, @@ -423,10 +585,8 @@ void AddTableRow( const Data::CreditsHistoryEntry &entry, Fn convertToStars) { auto helper = Ui::Text::CustomEmojiHelper(); - const auto price = helper.paletteDependent(Ui::Earn::IconCreditsEmoji({ - .size = table->st().defaultValue.style.font->height, - .margin = QMargins(0, st::giftBoxByStarsSkip, 0, 0), - })).append(' ').append(Lang::FormatCreditsAmountDecimal(entry.credits)); + const auto price = helper.paletteDependent(Ui::Earn::IconCreditsEmoji( + )).append(' ').append(Lang::FormatCreditsAmountDecimal(entry.credits)); auto label = object_ptr( table, rpl::single(price), @@ -520,11 +680,7 @@ not_null AddTableRow( st::defaultPopupMenu, context); const auto result = widget.data(); - AddTableRow( - table, - std::move(label), - std::move(widget), - st::giveawayGiftCodeValueMargin); + AddTableRow(table, std::move(label), std::move(widget)); return result; } @@ -1255,64 +1411,13 @@ void AddStarGiftTable( const auto giftToChannel = entry.giftChannelSavedId && peerIsChannel(PeerId(entry.bareEntryOwnerId)); - const auto raw = std::make_shared(nullptr); + const auto tooltip = std::make_shared(InfoTooltipData{ + .parent = container, + }); 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()); + ShowInfoTooltip(tooltip, widget, std::move(text), kTooltipDuration); }; if (unique && entry.bareGiftResaleRecipientId) { @@ -1369,8 +1474,7 @@ void AddStarGiftTable( AddTableRow( table, tr::lng_gift_unique_owner(), - std::move(label), - st::giveawayGiftCodeValueMargin); + std::move(label)); } } else if (giftToChannel) { AddTableRow( @@ -1444,18 +1548,15 @@ void AddStarGiftTable( AddTableRow( table, tr::lng_gift_unique_model(), - MakeAttributeValue(table, unique->model, showRarity), - st::giveawayGiftCodeValueMargin); + MakeAttributeValue(table, unique->model, showRarity)); AddTableRow( table, tr::lng_gift_unique_backdrop(), - MakeAttributeValue(table, unique->backdrop, showRarity), - st::giveawayGiftCodeValueMargin); + MakeAttributeValue(table, unique->backdrop, showRarity)); AddTableRow( table, tr::lng_gift_unique_symbol(), - MakeAttributeValue(table, unique->pattern, showRarity), - st::giveawayGiftCodeValueMargin); + MakeAttributeValue(table, unique->pattern, showRarity)); } else { AddTableRow( table, @@ -1464,8 +1565,7 @@ void AddStarGiftTable( table, show, entry, - std::move(convertToStars)), - st::giveawayGiftCodeValueMargin); + std::move(convertToStars))); } if (entry.limitedCount > 0 && !entry.giftRefunded) { auto amount = rpl::single(TextWithEntities{ @@ -1504,8 +1604,7 @@ void AddStarGiftTable( AddTableRow( table, tr::lng_gift_unique_value(), - MakeUniqueGiftValueValue(table, show, entry, st), - st::giveawayGiftCodeValueMargin); + MakeUniqueGiftValueValue(table, show, entry, st)); } const auto &original = unique->originalDetails; if (original.recipientId) { @@ -1691,8 +1790,7 @@ void AddCreditsHistoryEntryTable( (entry.reaction ? tr::lng_credits_box_history_entry_message : tr::lng_credits_box_history_entry_media)(), - std::move(label), - st::giveawayGiftCodeValueMargin); + std::move(label)); } } using Type = Data::CreditsHistoryEntry::PeerType; @@ -1799,8 +1897,7 @@ void AddCreditsHistoryEntryTable( AddTableRow( table, tr::lng_credits_box_history_entry_id(), - std::move(label), - st::giveawayGiftCodeValueMargin); + std::move(label)); } if (entry.floodSkip) { AddTableRow( @@ -1988,8 +2085,7 @@ void AddChannelEarnTable( AddTableRow( table, tr::lng_credits_box_history_entry_id(), - std::move(label), - st::giveawayGiftCodeValueMargin); + std::move(label)); } } @@ -2004,66 +2100,6 @@ void AddUniqueGiftValueTable( 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, @@ -2078,7 +2114,7 @@ void AddUniqueGiftValueTable( tr::lng_gift_value_initial_price(), tr::lng_gift_value_initial_price_value( lt_stars, - rpl::single(starIcon.append( + rpl::single(starIcon.append(' ').append( Lang::FormatCreditsAmountDecimal(value->initialPriceStars) )), lt_amount, @@ -2098,21 +2134,22 @@ void AddUniqueGiftValueTable( AddTableRow( table, tr::lng_gift_value_last_price(), - rpl::single( - FormatValuePrice(value->lastSalePrice, value->currency))); + MakePriceWithChangePercentValue(table, value)); } + + const auto tooltip = std::make_shared(InfoTooltipData{ + .parent = container, + }); if (value->minimumPrice) { AddTableRow( table, tr::lng_gift_value_minimum_price(), - rpl::single( - FormatValuePrice(value->minimumPrice, value->currency))); + MakeMinimumPriceValue(table, tooltip, entry.uniqueGift)); } if (value->averagePrice) { AddTableRow( table, tr::lng_gift_vlaue_average_price(), - rpl::single( - FormatValuePrice(value->averagePrice, value->currency))); + MakeAveragePriceValue(table, tooltip, entry.uniqueGift)); } } diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index 31c5df9afe..56ac674373 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -1258,7 +1258,7 @@ struct ResaleTabs { object_ptr widget; }; [[nodiscard]] ResaleTabs MakeResaleTabs( - not_null window, + std::shared_ptr show, not_null peer, const ResaleGiftsDescriptor &info, rpl::producer filter) { @@ -1340,7 +1340,7 @@ struct ResaleTabs { action->setClickedCallback(std::move(callback)); menu->addAction(std::move(action)); }; - auto context = Core::TextContext({ .session = &window->session() }); + auto context = Core::TextContext({ .session = &show->session() }); context.customEmojiFactory = [original = context.customEmojiFactory]( QStringView data, const Ui::Text::MarkedContext &context) { @@ -2738,13 +2738,6 @@ void SendGiftBox( }; } -[[nodiscard]] rpl::lifetime ShowStarGiftResale( - not_null controller, - not_null peer, - uint64 giftId, - QString title, - Fn finishRequesting); - [[nodiscard]] object_ptr MakeGiftsList( not_null window, not_null peer, @@ -3362,7 +3355,7 @@ void GiftResaleBox( }, content->lifetime()); auto tabs = MakeResaleTabs( - window, + window->uiShow(), peer, state->data, state->filter.value()); @@ -3448,30 +3441,6 @@ void GiftResaleBox( })); } -[[nodiscard]] rpl::lifetime ShowStarGiftResale( - not_null controller, - not_null peer, - uint64 giftId, - QString title, - Fn finishRequesting) { - const auto weak = base::make_weak(controller); - const auto session = &controller->session(); - return ResaleGiftsSlice( - session, - giftId - ) | rpl::start_with_next([=](ResaleGiftsDescriptor &&info) { - if (const auto onstack = finishRequesting) { - onstack(); - } - if (!info.giftId || !info.count) { - return; - } - info.title = title; - controller->show( - Box(GiftResaleBox, controller, peer, std::move(info))); - }); -} - struct CustomList { object_ptr content = { nullptr }; Fn overrideKey; @@ -5420,4 +5389,30 @@ void ShowResaleGiftBoughtToast( .duration = kUpgradeDoneToastDuration, }); } + +rpl::lifetime ShowStarGiftResale( + not_null controller, + not_null peer, + uint64 giftId, + QString title, + Fn finishRequesting) { + const auto weak = base::make_weak(controller); + const auto session = &controller->session(); + return ResaleGiftsSlice( + session, + giftId + ) | rpl::start_with_next([=](ResaleGiftsDescriptor &&info) { + if (const auto onstack = finishRequesting) { + onstack(); + } + if (!info.giftId || !info.count) { + return; + } + info.title = title; + if (const auto strong = weak.get()) { + strong->show(Box(GiftResaleBox, strong, peer, std::move(info))); + } + }); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index 5f0e305f19..520b1278d4 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -158,4 +158,11 @@ void ShowResaleGiftBoughtToast( not_null to, const Data::UniqueGift &gift); +[[nodiscard]] rpl::lifetime ShowStarGiftResale( + not_null controller, + not_null peer, + uint64 giftId, + QString title, + Fn finishRequesting); + } // namespace Ui diff --git a/Telegram/SourceFiles/data/data_star_gift.h b/Telegram/SourceFiles/data/data_star_gift.h index bf6b02bcb8..cde5609b33 100644 --- a/Telegram/SourceFiles/data/data_star_gift.h +++ b/Telegram/SourceFiles/data/data_star_gift.h @@ -55,6 +55,7 @@ struct UniqueGiftValue { struct UniqueGift { CollectibleId id = 0; + uint64 initialGiftId = 0; QString slug; QString title; QString ownerAddress; diff --git a/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp b/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp index 3ffe9d7905..80bfdc845c 100644 --- a/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp +++ b/Telegram/SourceFiles/info/channel_statistics/earn/earn_icons.cpp @@ -155,9 +155,12 @@ Ui::Text::PaletteDependentEmoji IconCreditsEmoji( IconDescriptor descriptor) { return { .factory = [=] { return Ui::GenerateStars( - descriptor.size ? descriptor.size : st::normalFont->height, + (descriptor.size + ? descriptor.size + : st::defaultTableLabel.style.font->height), 1); - }, .margin = descriptor.margin.value_or(QMargins()) }; + }, .margin = descriptor.margin.value_or( + { 0, st::giftBoxByStarsSkip, 0, 0 }) }; } Ui::Text::PaletteDependentEmoji IconCurrencyEmoji( diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index cb71bf5a10..cd39281bfe 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -2261,6 +2261,7 @@ void UniqueGiftValueBox( std::shared_ptr media; std::unique_ptr lottie; rpl::lifetime downloadLifetime; + rpl::lifetime buyLifetime; }; Ui::AddSkip(content, st::creditsHistoryEntryStarGiftSpace); @@ -2369,6 +2370,54 @@ void UniqueGiftValueBox( Ui::AddSkip(content); + const auto addAvailability = [&](int count, tr::phrase<> platform) { + return box->addRow( + object_ptr( + box, + tr::lng_gift_value_availability( + lt_count_decimal, + rpl::single(count * 1.), + lt_emoji, + rpl::single(Data::SingleCustomEmoji(document)), + lt_platform, + platform(Ui::Text::WithEntities), + lt_arrow, + rpl::single(Ui::Text::IconEmoji(&st::textMoreIconEmoji)), + [](const QString &text) { return Ui::Text::Link(text); }), + st::uniqueGiftValueAvailableLink, + st::defaultPopupMenu, + Core::TextContext({ .session = &show->session() })), + st::boxRowPadding + st::uniqueGiftValueAvailableMargin, + style::al_top); + }; + + if (const auto count = value->forSaleOnTelegram; count > 0) { + addAvailability( + count, + tr::lng_gift_value_telegram + )->setClickHandlerFilter([=](const auto &...) { + if (const auto window = show->resolveWindow()) { + state->buyLifetime = Ui::ShowStarGiftResale( + window, + window->session().user(), + unique->initialGiftId, + unique->title, + crl::guard(box, [=] { state->buyLifetime.destroy(); })); + } + return false; + }); + } + if (const auto count = value->forSaleOnFragment; count > 0) { + const auto url = value->fragmentUrl; + addAvailability( + count, + tr::lng_gift_value_fragment + )->setClickHandlerFilter([=](const auto &...) { + UrlClickHandler::Open(url); + return false; + }); + } + box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); }); diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 3dc7336307..630e90409d 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -371,3 +371,9 @@ uniqueGiftValueAbout: FlatLabel(defaultFlatLabel) { minWidth: 128px; align: align(top); } +uniqueGiftValueAvailableLink: FlatLabel(boxLabel) { + style: TextStyle(defaultTextStyle) { + font: font(14px); + } +} +uniqueGiftValueAvailableMargin: margins(0px, 4px, 0px, 4px);