diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings index 9632680e4..7d3841211 100644 --- a/Telegram/Resources/langs/lang.strings +++ b/Telegram/Resources/langs/lang.strings @@ -3254,11 +3254,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL "lng_gift_unique_model" = "Model"; "lng_gift_unique_backdrop" = "Backdrop"; "lng_gift_unique_symbol" = "Symbol"; +"lng_gift_unique_rarity" = "Only {percent} of such collectibles have this attribute."; "lng_gift_unique_availability#one" = "{count} of {amount} issued"; "lng_gift_unique_availability#other" = "{count} of {amount} issued"; "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_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\"."; +"lng_gift_unique_info_sender_comment" = "Gifted by {from} to {recipient} on {date} with the comment \"{text}\"."; +"lng_gift_unique_info_reciever" = "Gifted to {recipient} on {date}."; +"lng_gift_unique_info_reciever_comment" = "Gifted to {recipient} on {date} with the comment \"{text}\"."; "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"; diff --git a/Telegram/SourceFiles/api/api_premium.cpp b/Telegram/SourceFiles/api/api_premium.cpp index 3b63568e7..338d7bc05 100644 --- a/Telegram/SourceFiles/api/api_premium.cpp +++ b/Telegram/SourceFiles/api/api_premium.cpp @@ -906,8 +906,10 @@ Data::UniqueGiftOriginalDetails FromTL( const MTPDstarGiftAttributeOriginalDetails &data) { auto result = Data::UniqueGiftOriginalDetails(); result.date = data.vdate().v; - result.senderId = peerFromUser( - UserId(data.vsender_id().value_or_empty())); + result.senderId = data.vsender_id() + ? peerFromUser( + UserId(data.vsender_id().value_or_empty())) + : PeerId(); result.recipientId = peerFromUser( UserId(data.vrecipient_id().v)); result.message = data.vmessage() diff --git a/Telegram/SourceFiles/boxes/gift_premium_box.cpp b/Telegram/SourceFiles/boxes/gift_premium_box.cpp index ee98ff717..88b46e126 100644 --- a/Telegram/SourceFiles/boxes/gift_premium_box.cpp +++ b/Telegram/SourceFiles/boxes/gift_premium_box.cpp @@ -10,6 +10,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "api/api_premium.h" #include "api/api_premium_option.h" #include "apiwrap.h" +#include "base/timer_rpl.h" #include "base/unixtime.h" #include "base/weak_ptr.h" #include "boxes/peer_list_controllers.h" // ContactsBoxController. @@ -44,12 +45,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/layers/generic_box.h" #include "ui/painter.h" #include "ui/rect.h" +#include "ui/ui_utility.h" #include "ui/vertical_list.h" #include "ui/text/text_utilities.h" #include "ui/toast/toast.h" #include "ui/widgets/checkbox.h" #include "ui/widgets/gradient_round_button.h" #include "ui/widgets/label_with_custom_emoji.h" +#include "ui/widgets/tooltip.h" #include "ui/wrap/padding_wrap.h" #include "ui/wrap/slide_wrap.h" #include "ui/wrap/table_layout.h" @@ -65,6 +68,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace { +constexpr auto kRarityTooltipDuration = 3 * crl::time(1000); + [[nodiscard]] QString CreateMessageLink( not_null session, PeerId peerId, @@ -237,6 +242,57 @@ void AddTableRow( valueMargins); } +[[nodiscard]] object_ptr MakeAttributeValue( + not_null parent, + const Data::UniqueGiftAttribute &attribute, + Fn, int)> showTooltip) { + auto result = object_ptr(parent); + const auto raw = result.data(); + + const auto label = Ui::CreateChild( + raw, + attribute.name, + st::giveawayGiftCodeValue); + const auto permille = attribute.rarityPermille; + + const auto text = QString::number(permille / 10.) + '%'; + const auto rarity = Ui::CreateChild( + raw, + rpl::single(text), + st::starGiftSmallButton); + rarity->setTextTransform(Ui::RoundButton::TextTransform::NoTransform); + + rpl::combine( + raw->widthValue(), + rarity->widthValue() + ) | rpl::start_with_next([=](int width, int convertWidth) { + const auto convertSkip = convertWidth + ? (st::normalFont->spacew + convertWidth) + : 0; + label->resizeToNaturalWidth(width - convertSkip); + label->moveToLeft(0, 0, width); + rarity->moveToLeft( + label->width() + st::normalFont->spacew, + (st::giveawayGiftCodeValue.style.font->ascent + - st::starGiftSmallButton.style.font->ascent), + width); + }, label->lifetime()); + + label->heightValue() | rpl::start_with_next([=](int height) { + raw->resize( + raw->width(), + height + st::giveawayGiftCodeValueMargin.bottom()); + }, raw->lifetime()); + + label->setAttribute(Qt::WA_TransparentForMouseEvents); + + rarity->setClickedCallback([=] { + showTooltip(rarity, permille); + }); + + return result; +} + [[nodiscard]] object_ptr MakeStarGiftStarsValue( not_null parent, not_null controller, @@ -1146,7 +1202,15 @@ void AddStarGiftTable( st::giveawayGiftCodeTableMargin); const auto peerId = PeerId(entry.barePeerId); const auto session = &controller->session(); - if (peerId) { + const auto unique = entry.uniqueGift.get(); + if (unique) { + AddTableRow( + table, + tr::lng_gift_unique_owner(), + MakePeerTableValue(table, controller, peerId, false), + st::giveawayGiftCodePeerMargin); + //"lng_gift_unique_owner_change" = "change"; + } else if (peerId) { const auto user = session->data().peer(peerId)->asUser(); const auto withSendButton = entry.in && user && !user->isBot(); AddTableRow( @@ -1161,21 +1225,21 @@ void AddStarGiftTable( MakeHiddenPeerTableValue(table, controller), st::giveawayGiftCodePeerMargin); } - if (!entry.firstSaleDate.isNull()) { + if (!unique && !entry.firstSaleDate.isNull()) { AddTableRow( table, tr::lng_gift_link_label_first_sale(), rpl::single(Ui::Text::WithEntities( langDateTime(entry.firstSaleDate)))); } - if (!entry.lastSaleDate.isNull()) { + if (!unique && !entry.lastSaleDate.isNull()) { AddTableRow( table, tr::lng_gift_link_label_last_sale(), rpl::single(Ui::Text::WithEntities( langDateTime(entry.lastSaleDate)))); } - if (!entry.date.isNull()) { + if (!unique && !entry.date.isNull()) { AddTableRow( table, tr::lng_gift_link_label_date(), @@ -1183,7 +1247,87 @@ void AddStarGiftTable( } const auto marginWithButton = st::giveawayGiftCodeValueMargin - QMargins(0, 0, 0, st::giveawayGiftCodeValueMargin.bottom()); - { + if (unique) { + const auto raw = std::make_shared(nullptr); + const auto showTooltip = [=]( + not_null widget, + int rarity) { + if (*raw) { + (*raw)->toggleAnimated(false); + } + const auto text = QString::number(rarity / 10.) + '%'; + const auto tooltip = Ui::CreateChild( + container, + Ui::MakeNiceTooltipLabel( + container, + tr::lng_gift_unique_rarity( + lt_percent, + rpl::single(TextWithEntities{ text }), + Ui::Text::WithEntities), + 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()); + }; + + AddTableRow( + table, + tr::lng_gift_unique_model(), + MakeAttributeValue(container, unique->model, showTooltip), + marginWithButton); + AddTableRow( + table, + tr::lng_gift_unique_backdrop(), + MakeAttributeValue(container, unique->backdrop, showTooltip), + marginWithButton); + AddTableRow( + table, + tr::lng_gift_unique_symbol(), + MakeAttributeValue(container, unique->pattern, showTooltip), + marginWithButton); + } else { AddTableRow( table, tr::lng_gift_link_label_value(), @@ -1212,19 +1356,21 @@ void AddStarGiftTable( AddTableRow( table, tr::lng_gift_availability(), - ((entry.limitedLeft > 0) - ? tr::lng_gift_availability_left( - lt_count_decimal, - rpl::single(entry.limitedLeft * 1.), + ((!unique && !entry.limitedLeft) + ? tr::lng_gift_availability_none( lt_amount, std::move(amount), Ui::Text::WithEntities) - : tr::lng_gift_availability_none( - lt_amount, - std::move(amount), - Ui::Text::WithEntities))); + : (unique + ? tr::lng_gift_unique_availability + : tr::lng_gift_availability_left)( + lt_count_decimal, + rpl::single(entry.limitedLeft * 1.), + lt_amount, + std::move(amount), + Ui::Text::WithEntities))); } - if (!entry.uniqueGift && startUpgrade) { + if (!unique && startUpgrade) { AddTableRow( table, tr::lng_gift_unique_status(), @@ -1234,7 +1380,72 @@ void AddStarGiftTable( std::move(startUpgrade)), marginWithButton); } - if (!entry.description.empty()) { + if (unique) { + const auto &original = unique->originalDetails; + if (original.recipientId) { + const auto owner = &controller->session().data(); + const auto to = owner->peer(original.recipientId); + const auto from = original.senderId + ? owner->peer(original.senderId).get() + : nullptr; + const auto date = base::unixtime::parse(original.date).date(); + const auto dateText = TextWithEntities{ langDayOfMonth(date) }; + auto label = object_ptr( + table, + (from + ? (original.message.empty() + ? tr::lng_gift_unique_info_sender( + lt_from, + rpl::single(Ui::Text::Link(from->name(), 2)), + lt_recipient, + rpl::single(Ui::Text::Link(to->name(), 1)), + lt_date, + rpl::single(dateText), + Ui::Text::WithEntities) + : tr::lng_gift_unique_info_sender_comment( + lt_from, + rpl::single(Ui::Text::Link(from->name(), 2)), + lt_recipient, + rpl::single(Ui::Text::Link(to->name(), 1)), + lt_date, + rpl::single(dateText), + lt_text, + rpl::single(original.message), + Ui::Text::WithEntities)) + : (original.message.empty() + ? tr::lng_gift_unique_info_reciever( + lt_recipient, + rpl::single(Ui::Text::Link(to->name(), 1)), + lt_date, + rpl::single(dateText), + Ui::Text::WithEntities) + : tr::lng_gift_unique_info_reciever_comment( + lt_recipient, + rpl::single(Ui::Text::Link(to->name(), 1)), + lt_date, + rpl::single(dateText), + lt_text, + rpl::single(original.message), + Ui::Text::WithEntities))), + st::giveawayGiftMessage); + const auto showBoxLink = [=](not_null peer) { + return std::make_shared([=] { + controller->uiShow()->showBox( + PrepareShortInfoBox(peer, controller)); + }); + }; + label->setLink(1, showBoxLink(to)); + if (from) { + label->setLink(2, showBoxLink(from)); + } + label->setSelectable(true); + table->addRow( + std::move(label), + nullptr, + st::giveawayGiftCodeLabelMargin, + st::giveawayGiftCodeValueMargin); + } + } else if (!entry.description.empty()) { const auto makeContext = [=](Fn update) { return Core::MarkedTextContext{ .session = session, diff --git a/Telegram/SourceFiles/boxes/star_gift_box.cpp b/Telegram/SourceFiles/boxes/star_gift_box.cpp index cdb2057f4..4d661f1b1 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.cpp +++ b/Telegram/SourceFiles/boxes/star_gift_box.cpp @@ -13,6 +13,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "base/timer_rpl.h" #include "base/unixtime.h" #include "api/api_premium.h" +#include "boxes/gift_premium_box.h" #include "boxes/peer_list_controllers.h" #include "boxes/send_credits_box.h" #include "chat_helpers/emoji_suggestions_widget.h" @@ -1718,7 +1719,9 @@ void AddUniqueGiftCover( const auto title = CreateChild( cover, - tr::lng_gift_upgrade_title(tr::now), + rpl::duplicate( + data + ) | rpl::map([](const Data::UniqueGift &now) { return now.title; }), st::uniqueGiftTitle); title->setTextColorOverride(QColor(255, 255, 255)); auto subtitleText = subtitleOverride @@ -2072,6 +2075,8 @@ void UpgradeBox( button->moveToLeft(padding.left(), padding.top()); } }, box->lifetime()); + + AddUniqueCloseButton(box); } void PaintPoints( @@ -2155,4 +2160,19 @@ void ShowStarGiftUpgradeBox( }).send(); } +void AddUniqueCloseButton(not_null box) { + const auto button = Ui::CreateChild( + box, + st::uniqueCloseButton); + button->show(); + button->raise(); + box->widthValue() | rpl::start_with_next([=](int width) { + button->moveToRight(0, 0, width); + button->raise(); + }, button->lifetime()); + button->setClickedCallback([=] { + box->closeBox(); + }); +} + } // namespace Ui diff --git a/Telegram/SourceFiles/boxes/star_gift_box.h b/Telegram/SourceFiles/boxes/star_gift_box.h index b9b42e900..4ae5f7704 100644 --- a/Telegram/SourceFiles/boxes/star_gift_box.h +++ b/Telegram/SourceFiles/boxes/star_gift_box.h @@ -9,6 +9,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Data { struct UniqueGift; +struct GiftCode; +struct CreditsHistoryEntry; } // namespace Data namespace Window { @@ -21,6 +23,7 @@ class CustomEmoji; namespace Ui { +class GenericBox; class VerticalLayout; void ChooseStarGiftRecipient( @@ -51,4 +54,6 @@ void ShowStarGiftUpgradeBox( int stars, Fn ready); +void AddUniqueCloseButton(not_null box); + } // namespace Ui diff --git a/Telegram/SourceFiles/data/data_media_types.cpp b/Telegram/SourceFiles/data/data_media_types.cpp index 3b9e81f50..8ba7609bf 100644 --- a/Telegram/SourceFiles/data/data_media_types.cpp +++ b/Telegram/SourceFiles/data/data_media_types.cpp @@ -2380,7 +2380,7 @@ std::unique_ptr MediaGiftBox::createView( message, HistoryView::GenerateUniqueGiftMedia(message, replacing, raw), HistoryView::MediaGenericDescriptor{ - .maxWidth = st::chatIntroWidth, + .maxWidth = st::msgServiceGiftBoxSize.width(), .paintBg = HistoryView::UniqueGiftBg(message, raw), .service = true, }); diff --git a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp index 1a85dab97..bc8c7ecbe 100644 --- a/Telegram/SourceFiles/settings/settings_credits_graphics.cpp +++ b/Telegram/SourceFiles/settings/settings_credits_graphics.cpp @@ -846,6 +846,7 @@ void ReceiptCreditsBox( + controller->session().appConfig().stargiftConvertPeriodMax(); const auto timeLeft = int64(convertLast) - int64(base::unixtime::now()); const auto timeExceeded = (timeLeft <= 0); + const auto uniqueGift = e.uniqueGift.get(); const auto forConvert = gotStarGift && e.starsConverted && !e.converted @@ -859,9 +860,11 @@ void ReceiptCreditsBox( box->setNoContentMargin(true); const auto content = box->verticalLayout(); - Ui::AddSkip(content); - Ui::AddSkip(content); - Ui::AddSkip(content); + if (!uniqueGift) { + Ui::AddSkip(content); + Ui::AddSkip(content); + Ui::AddSkip(content); + } using Type = Data::CreditsHistoryEntry::PeerType; @@ -882,7 +885,15 @@ void ReceiptCreditsBox( : e.barePeerId ? session->data().peer(PeerId(e.barePeerId)).get() : nullptr; - if (const auto callback = Ui::PaintPreviewCallback(session, e)) { + if (uniqueGift) { + box->setNoContentMargin(true); + + AddUniqueGiftCover(content, rpl::single(*uniqueGift)); + + AddSkip(content, st::defaultVerticalListSkip * 2); + + AddUniqueCloseButton(box); + } else if (const auto callback = Ui::PaintPreviewCallback(session, e)) { const auto thumb = content->add(object_ptr>( content, GenericEntryPhoto(content, callback, stUser.photoSize))); @@ -994,45 +1005,46 @@ void ReceiptCreditsBox( }, widget->lifetime()); } - Ui::AddSkip(content); - Ui::AddSkip(content); + if (!uniqueGift) { + Ui::AddSkip(content); + Ui::AddSkip(content); - box->addRow(object_ptr>( - box, - object_ptr( + box->addRow(object_ptr>( box, - rpl::single(!s.title.isEmpty() - ? s.title - : !s.until.isNull() - ? tr::lng_credits_box_subscription_title(tr::now) - : isPrize - ? tr::lng_credits_box_history_entry_giveaway_name(tr::now) - : (!e.subscriptionUntil.isNull() && e.title.isEmpty()) - ? tr::lng_credits_box_history_entry_subscription(tr::now) - : !e.title.isEmpty() - ? e.title - : e.starrefCommission - ? tr::lng_credits_commission( - tr::now, - lt_amount, - Info::BotStarRef::FormatCommission(e.starrefCommission)) - : e.soldOutInfo - ? tr::lng_credits_box_history_entry_gift_unavailable(tr::now) - : sentStarGift - ? tr::lng_credits_box_history_entry_gift_sent(tr::now) - : convertedStarGift - ? tr::lng_credits_box_history_entry_gift_converted(tr::now) - : (isStarGift && !gotStarGift) - ? tr::lng_gift_link_label_gift(tr::now) - : e.gift - ? tr::lng_credits_box_history_entry_gift_name(tr::now) - : (peer && !e.reaction) - ? peer->name() - : Ui::GenerateEntryName(e).text), - st::creditsBoxAboutTitle))); - - Ui::AddSkip(content); + object_ptr( + box, + rpl::single(!s.title.isEmpty() + ? s.title + : !s.until.isNull() + ? tr::lng_credits_box_subscription_title(tr::now) + : isPrize + ? tr::lng_credits_box_history_entry_giveaway_name(tr::now) + : (!e.subscriptionUntil.isNull() && e.title.isEmpty()) + ? tr::lng_credits_box_history_entry_subscription(tr::now) + : !e.title.isEmpty() + ? e.title + : e.starrefCommission + ? tr::lng_credits_commission( + tr::now, + lt_amount, + Info::BotStarRef::FormatCommission(e.starrefCommission)) + : e.soldOutInfo + ? tr::lng_credits_box_history_entry_gift_unavailable(tr::now) + : sentStarGift + ? tr::lng_credits_box_history_entry_gift_sent(tr::now) + : convertedStarGift + ? tr::lng_credits_box_history_entry_gift_converted(tr::now) + : (isStarGift && !gotStarGift) + ? tr::lng_gift_link_label_gift(tr::now) + : e.gift + ? tr::lng_credits_box_history_entry_gift_name(tr::now) + : (peer && !e.reaction) + ? peer->name() + : Ui::GenerateEntryName(e).text), + st::creditsBoxAboutTitle))); + Ui::AddSkip(content); + } if (!isStarGift || creditsHistoryStarGift || e.soldOutInfo) { constexpr auto kMinus = QChar(0x2212); auto &lifetime = content->lifetime(); @@ -1163,7 +1175,7 @@ void ReceiptCreditsBox( rpl::single(e.description), st::creditsBoxAbout))); } - if (gotStarGift) { + if (!uniqueGift && gotStarGift) { Ui::AddSkip(content); const auto about = box->addRow( object_ptr>( @@ -1630,32 +1642,34 @@ void StarGiftViewBox( not_null controller, const Data::GiftCode &data, not_null item) { + const auto entry = Data::CreditsHistoryEntry{ + .id = data.slug, + .description = data.message, + .date = base::unixtime::parse(item->date()), + .credits = StarsAmount(data.count), + .bareMsgId = uint64(item->id.bare), + .barePeerId = item->history()->peer->id.value, + .bareGiftStickerId = data.document ? data.document->id : 0, + .stargiftId = data.stargiftId, + .uniqueGift = data.unique, + .peerType = Data::CreditsHistoryEntry::PeerType::Peer, + .limitedCount = data.limitedCount, + .limitedLeft = data.limitedLeft, + .starsConverted = data.starsConverted, + .starsToUpgrade = data.starsToUpgrade, + .starsUpgradedBySender = data.starsUpgradedBySender, + .converted = data.converted, + .anonymous = data.anonymous, + .stargift = true, + .savedToProfile = data.saved, + .canUpgradeGift = data.upgradable, + .in = true, + .gift = true, + }; Settings::ReceiptCreditsBox( box, controller, - Data::CreditsHistoryEntry{ - .id = data.slug, - .description = data.message, - .date = base::unixtime::parse(item->date()), - .credits = StarsAmount(data.count), - .bareMsgId = uint64(item->id.bare), - .barePeerId = item->history()->peer->id.value, - .bareGiftStickerId = data.document ? data.document->id : 0, - .stargiftId = data.stargiftId, - .peerType = Data::CreditsHistoryEntry::PeerType::Peer, - .limitedCount = data.limitedCount, - .limitedLeft = data.limitedLeft, - .starsConverted = data.starsConverted, - .starsToUpgrade = data.starsToUpgrade, - .starsUpgradedBySender = data.starsUpgradedBySender, - .converted = data.converted, - .anonymous = data.anonymous, - .stargift = true, - .savedToProfile = data.saved, - .canUpgradeGift = data.upgradable, - .in = true, - .gift = true, - }, + entry, Data::SubscriptionEntry()); } diff --git a/Telegram/SourceFiles/ui/effects/credits.style b/Telegram/SourceFiles/ui/effects/credits.style index 1e439afff..a8d480369 100644 --- a/Telegram/SourceFiles/ui/effects/credits.style +++ b/Telegram/SourceFiles/ui/effects/credits.style @@ -185,3 +185,10 @@ uniqueGiftSubtitle: FlatLabel(defaultFlatLabel) { } uniqueGiftSubtitleTop: 170px; uniqueGiftBottom: 20px; +uniqueCloseButton: IconButton(boxTitleClose) { + icon: icon {{ "box_button_close", videoPlayIconFg }}; + iconOver: icon {{ "box_button_close", videoPlayIconFg }}; + ripple: RippleAnimation(defaultRippleAnimation) { + color: shadowFg; + } +} diff --git a/Telegram/lib_ui b/Telegram/lib_ui index b063197b5..daf6a6142 160000 --- a/Telegram/lib_ui +++ b/Telegram/lib_ui @@ -1 +1 @@ -Subproject commit b063197b5d05de96754894630e45c8b3f95ade62 +Subproject commit daf6a614276954bf69c3322ef04985c02560fdf4